|
| 1 | +"""Tests for training job observability prints in script/terminal mode.""" |
| 2 | +import time |
| 3 | +from unittest.mock import MagicMock, patch |
| 4 | + |
| 5 | +import pytest |
| 6 | + |
| 7 | +from sagemaker.train.common_utils.trainer_wait import wait, _is_unassigned_attribute |
| 8 | + |
| 9 | + |
| 10 | +class MockUnassigned: |
| 11 | + pass |
| 12 | + |
| 13 | + |
| 14 | +class MockTrainingJob: |
| 15 | + def __init__(self, status="Completed", failure_reason=None): |
| 16 | + self.training_job_name = "test-sft-job-2026" |
| 17 | + self.training_job_arn = "arn:aws:sagemaker:us-west-2:123456789:training-job/test-sft-job-2026" |
| 18 | + self.training_job_status = status |
| 19 | + self.secondary_status = "Training" |
| 20 | + self.secondary_status_transitions = [] |
| 21 | + self.progress_info = MockUnassigned() |
| 22 | + self.failure_reason = failure_reason |
| 23 | + self.mlflow_config = MockUnassigned() |
| 24 | + self._call_count = 0 |
| 25 | + |
| 26 | + def refresh(self): |
| 27 | + self._call_count += 1 |
| 28 | + |
| 29 | + |
| 30 | +class TestTrainingObservabilityAtStart: |
| 31 | + """Test that job info is printed at start in terminal mode.""" |
| 32 | + |
| 33 | + @patch("sagemaker.train.common_utils.trainer_wait._is_jupyter_environment", return_value=False) |
| 34 | + @patch("sagemaker.train.common_utils.trainer_wait._setup_mlflow_integration", return_value=(None, None, None)) |
| 35 | + def test_prints_job_info_at_start(self, mock_mlflow, mock_jupyter, capsys): |
| 36 | + job = MockTrainingJob(status="Completed") |
| 37 | + wait(job, poll=0, timeout=1) |
| 38 | + captured = capsys.readouterr() |
| 39 | + assert "Training job started: test-sft-job-2026" in captured.out |
| 40 | + assert "Log group: /aws/sagemaker/TrainingJobs" in captured.out |
| 41 | + assert "Log stream prefix: test-sft-job-2026" in captured.out |
| 42 | + |
| 43 | + |
| 44 | +class TestTrainingObservabilityOnFailure: |
| 45 | + """Test that debug info is printed on failure.""" |
| 46 | + |
| 47 | + @patch("sagemaker.train.common_utils.trainer_wait._is_jupyter_environment", return_value=False) |
| 48 | + @patch("sagemaker.train.common_utils.trainer_wait._setup_mlflow_integration", return_value=(None, None, None)) |
| 49 | + def test_prints_debug_info_on_failure(self, mock_mlflow, mock_jupyter, capsys): |
| 50 | + job = MockTrainingJob(status="Failed", failure_reason="OOM error") |
| 51 | + with pytest.raises(Exception): |
| 52 | + wait(job, poll=0, timeout=1) |
| 53 | + captured = capsys.readouterr() |
| 54 | + assert "Failure reason: OOM error" in captured.out |
| 55 | + assert "Log group: /aws/sagemaker/TrainingJobs" in captured.out |
| 56 | + assert "Log stream prefix: test-sft-job-2026" in captured.out |
| 57 | + assert "CloudWatch Logs:" in captured.out |
| 58 | + |
| 59 | + @patch("sagemaker.train.common_utils.trainer_wait._is_jupyter_environment", return_value=False) |
| 60 | + @patch("sagemaker.train.common_utils.trainer_wait._setup_mlflow_integration", return_value=(None, None, None)) |
| 61 | + def test_prints_cloudwatch_url_on_failure(self, mock_mlflow, mock_jupyter, capsys): |
| 62 | + job = MockTrainingJob(status="Failed", failure_reason="ClientError") |
| 63 | + with pytest.raises(Exception): |
| 64 | + wait(job, poll=0, timeout=1) |
| 65 | + captured = capsys.readouterr() |
| 66 | + assert "us-west-2.console.aws.amazon.com/cloudwatch" in captured.out |
| 67 | + |
| 68 | + |
| 69 | +class TestTrainingObservabilityOnSuccess: |
| 70 | + """Test that MLflow link is printed on success (existing behavior preserved).""" |
| 71 | + |
| 72 | + @patch("sagemaker.train.common_utils.trainer_wait._is_jupyter_environment", return_value=False) |
| 73 | + @patch("sagemaker.train.common_utils.trainer_wait._setup_mlflow_integration", return_value=("https://mlflow.example.com", None, None)) |
| 74 | + def test_prints_mlflow_on_success(self, mock_mlflow, mock_jupyter, capsys): |
| 75 | + job = MockTrainingJob(status="Completed") |
| 76 | + wait(job, poll=0, timeout=1) |
| 77 | + captured = capsys.readouterr() |
| 78 | + assert "mlflow.example.com" in captured.out |
0 commit comments