from contextlib import suppress from datetime import datetime, timedelta from unittest import mock from unittest.mock import MagicMock, patch import ci_post_gantt import pytest from ci_gantt_chart import generate_gantt_chart from ci_post_gantt import Gitlab, MockGanttExit def create_mock_job( name, id, status, created_at, queued_duration, started_at, finished_at=None ): mock_job = MagicMock() mock_job.name = name mock_job.status = status mock_job.id = id mock_job.created_at = created_at mock_job.queued_duration = queued_duration mock_job.started_at = started_at mock_job.finished_at = finished_at return mock_job @pytest.fixture def fake_pipeline(): current_time = datetime.fromisoformat("2024-12-17 23:54:13.940091+00:00") created_at = current_time - timedelta(minutes=10) job1 = create_mock_job( name="job1", id="1", status="success", created_at=created_at.isoformat(), queued_duration=1, # seconds started_at=(created_at + timedelta(seconds=2)).isoformat(), finished_at=(created_at + timedelta(minutes=1)).isoformat(), ) mock_pipeline = MagicMock() mock_pipeline.web_url = "https://gitlab.freedesktop.org/mesa/mesa/-/pipelines/9999" mock_pipeline.duration = 600 # Total pipeline duration in seconds mock_pipeline.created_at = created_at.isoformat() mock_pipeline.yaml_errors = False mock_pipeline.jobs.list.return_value = [job1] return mock_pipeline def test_generate_gantt_chart(fake_pipeline): fig = generate_gantt_chart(fake_pipeline) fig_dict = fig.to_dict() assert "data" in fig_dict # Extract all job names from the "y" axis in the Gantt chart data all_job_names = set() for trace in fig_dict["data"]: if "y" in trace: all_job_names.update(trace["y"]) assert any( "job1" in job for job in all_job_names ), "job1 should be present in the Gantt chart" def test_ci_timeout(fake_pipeline): fig = generate_gantt_chart(fake_pipeline, ci_timeout=1) fig_dict = fig.to_dict() timeout_line = None for shape in fig_dict.get("layout", {}).get("shapes", []): if shape.get("line", {}).get("dash") == "dash": timeout_line = shape break assert timeout_line is not None, "Timeout line should exist in the Gantt chart" timeout_x = timeout_line["x0"] # Check that the timeout line is 1 minute after the pipeline creation time pipeline_created_at = datetime.fromisoformat(fake_pipeline.created_at) expected_timeout = pipeline_created_at + timedelta(minutes=1) assert ( timeout_x == expected_timeout ), f"Timeout should be at {expected_timeout}, got {timeout_x}" def test_marge_bot_user_id(): with patch("ci_post_gantt.Gitlab") as MockGitlab: mock_gitlab_instance = MagicMock(spec=Gitlab) mock_gitlab_instance.users = MagicMock() MockGitlab.return_value = mock_gitlab_instance marge_bot_user_id = 12345 ci_post_gantt.main("fake_token", None, marge_bot_user_id) mock_gitlab_instance.users.get.assert_called_once_with(marge_bot_user_id) def test_project_ids(): current_time = datetime.now() project_id_1 = 176 event_1 = MagicMock() event_1.project_id = project_id_1 event_1.created_at = (current_time - timedelta(days=1)).isoformat() event_1.note = {"body": f"Event for project {project_id_1}"} project_id_2 = 166 event_2 = MagicMock() event_2.project_id = project_id_2 event_2.created_at = (current_time - timedelta(days=2)).isoformat() event_2.note = {"body": f"Event for project {project_id_2}"} with patch("ci_post_gantt.Gitlab") as MockGitlab: mock_user = MagicMock() mock_user.events = MagicMock() mock_user.events.list.return_value = [event_1, event_2] mock_gitlab_instance = MagicMock(spec=Gitlab) mock_gitlab_instance.users = MagicMock() mock_gitlab_instance.users.get.return_value = mock_user MockGitlab.return_value = mock_gitlab_instance last_event_date = (current_time - timedelta(days=3)).isoformat() # Test a single project id ci_post_gantt.main("fake_token", last_event_date) marge_bot_single_project_scope = [ event.note["body"] for event in mock_user.events.list.return_value if event.project_id == project_id_1 ] assert f"Event for project {project_id_1}" in marge_bot_single_project_scope assert f"Event for project {project_id_2}" not in marge_bot_single_project_scope # Test multiple project ids ci_post_gantt.main( "fake_token", last_event_date, 9716, [project_id_1, project_id_2] ) marge_bot_multiple_project_scope = [ event.note["body"] for event in mock_user.events.list.return_value ] assert f"Event for project {project_id_1}" in marge_bot_multiple_project_scope assert f"Event for project {project_id_2}" in marge_bot_multiple_project_scope def test_add_gantt_after_pipeline_message(): current_time = datetime.now() plain_url = "https://gitlab.freedesktop.org/mesa/mesa/-/pipelines/12345" plain_message = ( f"I couldn't merge this branch: CI failed! See pipeline {plain_url}." ) event_plain = MagicMock() event_plain.project_id = 176 event_plain.created_at = (current_time - timedelta(days=1)).isoformat() event_plain.note = {"body": plain_message} summary_url = "https://gitlab.freedesktop.org/mesa/mesa/-/pipelines/99999" summary_message = ( "I couldn't merge this branch: " f"CI failed! See pipeline {summary_url}.
There were problems with job:" "[lavapipe](https://gitlab.freedesktop.org/mesa/mesa/-/jobs/68141218)
" "3 crashed testsdEQP-VK.ray_query.builtin.instancecustomindex.frag.aabbs,Crash
dEQP" "-VK.ray_query.builtin.objecttoworld.frag.aabbs,Crash
dEQP-VK.sparse_resources.shader_intrinsics." "2d_array_sparse_fetch.g16_b16r16_2plane_444_unorm.11_37_3_nontemporal,Crash
" ) event_with_summary = MagicMock() event_with_summary.project_id = 176 event_with_summary.created_at = (current_time - timedelta(days=1)).isoformat() event_with_summary.note = {"body": summary_message} with patch("ci_post_gantt.Gitlab") as MockGitlab, patch( "ci_post_gantt.get_gitlab_pipeline_from_url", return_value=None ) as mock_get_gitlab_pipeline_from_url: def safe_mock(*args, **kwargs): with suppress(TypeError): raise MockGanttExit("Exiting for test purposes") mock_get_gitlab_pipeline_from_url.side_effect = safe_mock mock_user = MagicMock() mock_user.events = MagicMock() mock_user.events.list.return_value = [event_plain, event_with_summary] mock_gitlab_instance = MagicMock(spec=Gitlab) mock_gitlab_instance.users = MagicMock() mock_gitlab_instance.users.get.return_value = mock_user MockGitlab.return_value = mock_gitlab_instance last_event_date = (current_time - timedelta(days=3)).isoformat() ci_post_gantt.main("fake_token", last_event_date, 12345) mock_get_gitlab_pipeline_from_url.assert_has_calls( [ mock.call(mock_gitlab_instance, plain_url), mock.call(mock_gitlab_instance, summary_url), ], any_order=True, )