Skip to content

Commit fbfc0c7

Browse files
committed
fix(test): add mlflow_resource_arn fixture that auto-discovers or creates app
Replace hard-coded MLflow app ARN with a conftest fixture that finds an existing ready app or creates a temporary one (cleaned up after tests). Prevents failures when the hard-coded app is deleted or quota is full. X-AI-Prompt: add self-healing mlflow fixture for llm_as_judge integ tests X-AI-Tool: kiro-cli
1 parent 059ac41 commit fbfc0c7

2 files changed

Lines changed: 93 additions & 2 deletions

File tree

‎sagemaker-train/tests/integ/train/conftest.py‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,92 @@ def sagemaker_session_us_east_1():
4848
"""Create a SageMaker session in us-east-1 for Nova model tests."""
4949
boto_session = boto3.Session(region_name=NOVA_REGION)
5050
return Session(boto_session=boto_session)
51+
52+
53+
import time
54+
import logging
55+
56+
logger = logging.getLogger(__name__)
57+
58+
59+
@pytest.fixture(scope="module")
60+
def mlflow_resource_arn():
61+
"""Discover or create an MLflow app for integ tests, clean up if created.
62+
63+
Looks for an existing MLflow app in Created/Updated state. If none exists,
64+
creates one and deletes it after the test module finishes.
65+
"""
66+
region = os.environ.get("AWS_DEFAULT_REGION", DEFAULT_REGION)
67+
sm_client = boto3.client("sagemaker", region_name=region)
68+
created_arn = None
69+
70+
# Try to find an existing ready app
71+
try:
72+
paginator = sm_client.get_paginator("list_mlflow_apps")
73+
for page in paginator.paginate():
74+
for app in page.get("MlflowApps", []):
75+
if app.get("Status") in ("Created", "Updated"):
76+
logger.info(f"Using existing MLflow app: {app['Arn']}")
77+
yield app["Arn"]
78+
return
79+
except Exception as e:
80+
logger.warning(f"Failed to list MLflow apps: {e}")
81+
82+
# No ready app found — create one
83+
logger.info("No ready MLflow app found. Creating one for integ tests...")
84+
sts_client = boto3.client("sts", region_name=region)
85+
account_id = sts_client.get_caller_identity()["Account"]
86+
app_name = f"integ-test-mlflow-{int(time.time())}"
87+
artifact_store_uri = f"s3://sagemaker-{region}-{account_id}/mlflow-artifacts"
88+
89+
# Ensure bucket/prefix exists
90+
s3_client = boto3.client("s3", region_name=region)
91+
bucket_name = f"sagemaker-{region}-{account_id}"
92+
try:
93+
s3_client.head_bucket(Bucket=bucket_name)
94+
except Exception:
95+
if region == "us-east-1":
96+
s3_client.create_bucket(Bucket=bucket_name)
97+
else:
98+
s3_client.create_bucket(
99+
Bucket=bucket_name,
100+
CreateBucketConfiguration={"LocationConstraint": region},
101+
)
102+
try:
103+
s3_client.put_object(Bucket=bucket_name, Key="mlflow-artifacts/")
104+
except Exception:
105+
pass
106+
107+
# Get execution role
108+
from sagemaker.train.defaults import TrainDefaults
109+
boto_session = boto3.Session(region_name=region)
110+
sagemaker_session = Session(boto_session=boto_session)
111+
role_arn = TrainDefaults.get_role(role=None, sagemaker_session=sagemaker_session)
112+
113+
resp = sm_client.create_mlflow_app(
114+
Name=app_name,
115+
ArtifactStoreUri=artifact_store_uri,
116+
RoleArn=role_arn,
117+
AccountDefaultStatus="DISABLED",
118+
)
119+
created_arn = resp["Arn"]
120+
logger.info(f"Created MLflow app: {created_arn}")
121+
122+
# Wait for it to become ready
123+
for _ in range(60):
124+
desc = sm_client.describe_mlflow_app(Arn=created_arn)
125+
status = desc.get("Status")
126+
if status in ("Created", "Updated"):
127+
break
128+
if status in ("Failed", "CreateFailed", "DeleteFailed"):
129+
pytest.skip(f"MLflow app creation failed: {desc.get('FailureReason')}")
130+
time.sleep(10)
131+
132+
yield created_arn
133+
134+
# Cleanup
135+
logger.info(f"Cleaning up MLflow app: {created_arn}")
136+
try:
137+
sm_client.delete_mlflow_app(Arn=created_arn)
138+
except Exception as e:
139+
logger.warning(f"Failed to delete MLflow app {created_arn}: {e}")

‎sagemaker-train/tests/integ/train/test_llm_as_judge_base_model_fix.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
class TestLLMAsJudgeBaseModelFix:
8282
"""Integration test for base model fix in LLMAsJudgeEvaluator"""
8383

84-
def test_base_model_evaluation_uses_correct_weights(self):
84+
def test_base_model_evaluation_uses_correct_weights(self, mlflow_resource_arn):
8585
"""
8686
Test that base model evaluation uses original base model weights.
8787
@@ -115,6 +115,7 @@ def test_base_model_evaluation_uses_correct_weights(self):
115115
custom_metrics=TEST_CONFIG["custom_metrics_json"],
116116
s3_output_path=TEST_CONFIG["s3_output_path"],
117117
evaluate_base_model=TEST_CONFIG["evaluate_base_model"],
118+
mlflow_resource_arn=mlflow_resource_arn,
118119
)
119120

120121
# Verify evaluator configuration
@@ -250,7 +251,7 @@ def test_base_model_evaluation_uses_correct_weights(self):
250251
# Re-raise to fail the test
251252
raise
252253

253-
def test_base_model_false_still_works(self):
254+
def test_base_model_false_still_works(self, mlflow_resource_arn):
254255
"""
255256
Test that evaluate_base_model=False still works correctly (backward compatibility).
256257
@@ -271,6 +272,7 @@ def test_base_model_false_still_works(self):
271272
builtin_metrics=TEST_CONFIG["builtin_metrics"],
272273
s3_output_path=TEST_CONFIG["s3_output_path"],
273274
evaluate_base_model=False, # Only evaluate custom model
275+
mlflow_resource_arn=mlflow_resource_arn,
274276
)
275277

276278
# Verify evaluator configuration

0 commit comments

Comments
 (0)