Repository navigation
test(train): patch mocks at the lookup site so unit tests stop calling AWS - #6491
Conversation
…g AWS
Five tests in sagemaker-train's unit suite issued real AWS requests. They
pass in CI only because the build role carries credentials, so the mocks
were silently ineffective rather than visibly broken.
Root cause in three shapes:
1. Patch targeting the defining module instead of the lookup site.
base_trainer does `from ...finetune_utils import get_recipe_s3_uri` at
module level, so patching finetune_utils.get_recipe_s3_uri never rebinds
the reference base_trainer already holds and the real function ran,
calling DescribeHubContent. Same for get_training_image, and for
TrainDefaults, which handed back a real Session so download_file issued a
live HeadObject.
Only two of the four finetune_utils patches in that block were affected:
_render_recipe_placeholders and _get_smtj_override_spec are imported
inside _train_serverful_smtj, so that import re-executes under patch and
finetune_utils is the correct target for them. Those are left as they are.
2. Missing mock. base_evaluator imports verify_evaluation_caller_permissions
in the same statement as resolve_and_validate_role, but only the latter
was ever patched, so _get_aws_execution_context reached STS
GetCallerIdentity and IAM.
3. Incomplete mock. test_model_trainer_no_from_recipe_raises patched
TrainDefaults.get_role for exactly this reason, but STS was also reached
by a second route: model_post_init builds a default OutputDataConfig,
which calls session.default_bucket(). The session is stubbed rather than
get_output_data_config so that the defaulting logic stays under test.
Verified with deliberately invalid credentials plus a transport-level
recorder that counts swallowed calls:
- the three files go from 5 failed / 97 passed to 102 passed
- the full sagemaker-train unit suite goes from 19 to 14 failures; the
remaining 14 are a local botocore shape mismatch, not these tests
- zero outbound AWS requests from the three files
Coverage moves by one line, 27402 to 27401, with no real loss. The 12 lines
no longer covered are all sagemaker-core session_helper AWS plumbing
(default_bucket, generate_default_sagemaker_bucket_name, botocore_resolver,
sts_regional_endpoint) that these tests only covered by genuinely calling
STS; all 12 are covered by sagemaker-core's own unit suite. Offsetting that,
11 lines become newly covered in base_trainer, base_evaluator,
benchmark_evaluator, custom_scorer_evaluator and model_trainer, because the
tests no longer abort partway through on an AWS error.
No production code changes.
There was a problem hiding this comment.
🤖 AI code review
This is a test-only PR that fixes five sagemaker-train unit tests that silently issued real AWS calls (STS, IAM, SageMaker, S3). The fix moves @patch targets from the modules where names are defined to the modules where they are looked up (base_trainer, base_evaluator), adds a missing verify_evaluation_caller_permissions patch, and stubs the session in test_model_trainer_no_from_recipe_raises. I verified the import patterns in base_trainer.py (module-level from finetune_utils import ... and from ...defaults import TrainDefaults), the inside-function imports of _render_recipe_placeholders/_get_smtj_override_spec left correctly untouched, and base_evaluator.py's combined import of resolve_and_validate_role/verify_evaluation_caller_permissions. The changes are correct and align with established team guidance to patch at the import site in the module under test (Source: #6272). No issues found.
Reviewed commit 0a917e7. Automated review; verify before acting.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #6491 +/- ##
==========================================
+ Coverage 64.26% 72.89% +8.62%
==========================================
Files 570 570
Lines 71686 71686
==========================================
+ Hits 46070 52252 +6182
+ Misses 25616 19434 -6182 🚀 New features to boost your workflow:
|
Problem
Five tests in
sagemaker-train's unit suite issue real AWS requests — STSGetCallerIdentity, IAM, SageMakerDescribeHubContent, and S3. They pass in CI only because the build role carries credentials and permissions, so the mocks are silently ineffective rather than visibly broken.Reproduce with deliberately invalid credentials (note
AWS_PROFILEmust be unset, not empty —jumpstart/constants.py:114builds aboto3.Session()at import time):Root cause — three shapes
1. Patch targeting the defining module instead of the lookup site.
base_trainer.py:39-47doesfrom ...finetune_utils import get_recipe_s3_uri, get_training_imageand:64doesfrom sagemaker.train.defaults import TrainDefaults, all at module level. Patching those names where they are defined never rebinds the referencebase_traineralready holds, so the real functions ran:get_recipe_s3_uricalledDescribeHubContent, andTrainDefaultsreturned a realSession, sodownload_fileissued a liveHeadObject.Only two of the four
finetune_utilspatches in that block were affected._render_recipe_placeholdersand_get_smtj_override_specare imported inside_train_serverful_smtj(base_trainer.py:1060-1064), so that import re-executes while the patch is active andfinetune_utilsis the correct target for them. They are deliberately left unchanged.2. Missing mock.
base_evaluator.py:20-23importsverify_evaluation_caller_permissionsin the same statement asresolve_and_validate_role, but only the latter was ever patched — so_get_aws_execution_context(base_evaluator.py:827) reached STS and IAM.3. Incomplete mock.
test_model_trainer_no_from_recipe_raisesalready patchedTrainDefaults.get_rolefor exactly this reason, but STS was reached by a second route:model_post_initbuilds a defaultOutputDataConfig, which callssession.default_bucket(). The session is stubbed rather thanget_output_data_config, so the defaulting logic stays under test.spec=Sessionis needed becauseModelTrainervalidates the field as a realSession.Verification
Measured with invalid credentials and a transport-level recorder wrapping
botocore.httpsession.URLLib3Session.send+requests.adapters.HTTPAdapter.send, so calls swallowed bytry/exceptare counted too.sagemaker-trainunit suiteThe remaining 14 failures are pre-existing and unrelated — a local
botocorethat lacks the SageMaker training-session shapes. They are identical before and after.Coverage
Net −1 line (27402 → 27401), with no real loss:
sagemaker-core/helper/session_helper.py—default_bucket(),generate_default_sagemaker_bucket_name(),botocore_resolver(),sts_regional_endpoint(). These were covered only because a unit test genuinely called STS. All 12 are covered bysagemaker-core's own unit suite (verified).base_trainer,base_evaluator,benchmark_evaluator,custom_scorer_evaluator,model_trainer— production paths that previously never executed because the tests aborted partway through on an AWS error.Mock-drift risk is covered:
get_recipe_s3_uriis asserted directly attests/unit/train/common_utils/test_finetune_utils.py:1563-1583, andverify_evaluation_caller_permissionshas 77 tests insagemaker-core/tests/unit/helper/test_iam_role_resolver.py.Scope
Test files only — no production code changes.
Note for reviewers
Escapes here are layered: fixing the
get_recipe_s3_uritarget madeDescribeHubContentdisappear and revealed a liveHeadObjectunderneath from the still-realTrainDefaultssession. If you spot further unmocked paths in these files, that is the shape to look for. The zero-outbound-request measurement is the check that closed it out.The same class of bug exists more widely — notably
sagemaker-serve, where 65 unit tests fail under invalid credentials (23 of them because constructingModelBuilder(model=Mock())calls STS via__post_init__). That is intentionally out of scope here and worth separate PRs.