• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from contextlib import suppress
2from datetime import datetime, timedelta
3from unittest import mock
4from unittest.mock import MagicMock, patch
5
6import ci_post_gantt
7import pytest
8from ci_gantt_chart import generate_gantt_chart
9from ci_post_gantt import Gitlab, MockGanttExit
10
11
12def create_mock_job(
13    name, id, status, created_at, queued_duration, started_at, finished_at=None
14):
15    mock_job = MagicMock()
16    mock_job.name = name
17    mock_job.status = status
18    mock_job.id = id
19    mock_job.created_at = created_at
20    mock_job.queued_duration = queued_duration
21    mock_job.started_at = started_at
22    mock_job.finished_at = finished_at
23    return mock_job
24
25
26@pytest.fixture
27def fake_pipeline():
28    current_time = datetime.fromisoformat("2024-12-17 23:54:13.940091+00:00")
29    created_at = current_time - timedelta(minutes=10)
30
31    job1 = create_mock_job(
32        name="job1",
33        id="1",
34        status="success",
35        created_at=created_at.isoformat(),
36        queued_duration=1,  # seconds
37        started_at=(created_at + timedelta(seconds=2)).isoformat(),
38        finished_at=(created_at + timedelta(minutes=1)).isoformat(),
39    )
40
41    mock_pipeline = MagicMock()
42    mock_pipeline.web_url = "https://gitlab.freedesktop.org/mesa/mesa/-/pipelines/9999"
43    mock_pipeline.duration = 600  # Total pipeline duration in seconds
44    mock_pipeline.created_at = created_at.isoformat()
45    mock_pipeline.yaml_errors = False
46    mock_pipeline.jobs.list.return_value = [job1]
47    return mock_pipeline
48
49
50def test_generate_gantt_chart(fake_pipeline):
51    fig = generate_gantt_chart(fake_pipeline)
52
53    fig_dict = fig.to_dict()
54    assert "data" in fig_dict
55
56    # Extract all job names from the "y" axis in the Gantt chart data
57    all_job_names = set()
58    for trace in fig_dict["data"]:
59        if "y" in trace:
60            all_job_names.update(trace["y"])
61
62    assert any(
63        "job1" in job for job in all_job_names
64    ), "job1 should be present in the Gantt chart"
65
66
67def test_ci_timeout(fake_pipeline):
68    fig = generate_gantt_chart(fake_pipeline, ci_timeout=1)
69
70    fig_dict = fig.to_dict()
71
72    timeout_line = None
73    for shape in fig_dict.get("layout", {}).get("shapes", []):
74        if shape.get("line", {}).get("dash") == "dash":
75            timeout_line = shape
76            break
77
78    assert timeout_line is not None, "Timeout line should exist in the Gantt chart"
79    timeout_x = timeout_line["x0"]
80
81    # Check that the timeout line is 1 minute after the pipeline creation time
82    pipeline_created_at = datetime.fromisoformat(fake_pipeline.created_at)
83    expected_timeout = pipeline_created_at + timedelta(minutes=1)
84    assert (
85        timeout_x == expected_timeout
86    ), f"Timeout should be at {expected_timeout}, got {timeout_x}"
87
88
89def test_marge_bot_user_id():
90    with patch("ci_post_gantt.Gitlab") as MockGitlab:
91        mock_gitlab_instance = MagicMock(spec=Gitlab)
92        mock_gitlab_instance.users = MagicMock()
93        MockGitlab.return_value = mock_gitlab_instance
94
95        marge_bot_user_id = 12345
96        ci_post_gantt.main("fake_token", None, marge_bot_user_id)
97        mock_gitlab_instance.users.get.assert_called_once_with(marge_bot_user_id)
98
99
100def test_project_ids():
101    current_time = datetime.now()
102    project_id_1 = 176
103    event_1 = MagicMock()
104    event_1.project_id = project_id_1
105    event_1.created_at = (current_time - timedelta(days=1)).isoformat()
106    event_1.note = {"body": f"Event for project {project_id_1}"}
107
108    project_id_2 = 166
109    event_2 = MagicMock()
110    event_2.project_id = project_id_2
111    event_2.created_at = (current_time - timedelta(days=2)).isoformat()
112    event_2.note = {"body": f"Event for project {project_id_2}"}
113
114    with patch("ci_post_gantt.Gitlab") as MockGitlab:
115        mock_user = MagicMock()
116        mock_user.events = MagicMock()
117        mock_user.events.list.return_value = [event_1, event_2]
118
119        mock_gitlab_instance = MagicMock(spec=Gitlab)
120        mock_gitlab_instance.users = MagicMock()
121        mock_gitlab_instance.users.get.return_value = mock_user
122        MockGitlab.return_value = mock_gitlab_instance
123
124        last_event_date = (current_time - timedelta(days=3)).isoformat()
125
126        # Test a single project id
127        ci_post_gantt.main("fake_token", last_event_date)
128        marge_bot_single_project_scope = [
129            event.note["body"]
130            for event in mock_user.events.list.return_value
131            if event.project_id == project_id_1
132        ]
133        assert f"Event for project {project_id_1}" in marge_bot_single_project_scope
134        assert f"Event for project {project_id_2}" not in marge_bot_single_project_scope
135
136        # Test multiple project ids
137        ci_post_gantt.main(
138            "fake_token", last_event_date, 9716, [project_id_1, project_id_2]
139        )
140
141        marge_bot_multiple_project_scope = [
142            event.note["body"] for event in mock_user.events.list.return_value
143        ]
144        assert f"Event for project {project_id_1}" in marge_bot_multiple_project_scope
145        assert f"Event for project {project_id_2}" in marge_bot_multiple_project_scope
146
147
148def test_add_gantt_after_pipeline_message():
149    current_time = datetime.now()
150
151    plain_url = "https://gitlab.freedesktop.org/mesa/mesa/-/pipelines/12345"
152    plain_message = (
153        f"I couldn't merge this branch: CI failed! See pipeline {plain_url}."
154    )
155    event_plain = MagicMock()
156    event_plain.project_id = 176
157    event_plain.created_at = (current_time - timedelta(days=1)).isoformat()
158    event_plain.note = {"body": plain_message}
159
160    summary_url = "https://gitlab.freedesktop.org/mesa/mesa/-/pipelines/99999"
161    summary_message = (
162        "I couldn't merge this branch: "
163        f"CI failed! See pipeline {summary_url}.<br>There were problems with job:"
164        "[lavapipe](https://gitlab.freedesktop.org/mesa/mesa/-/jobs/68141218)<details><summary> "
165        "3 crashed tests</summary>dEQP-VK.ray_query.builtin.instancecustomindex.frag.aabbs,Crash<br>dEQP"
166        "-VK.ray_query.builtin.objecttoworld.frag.aabbs,Crash<br>dEQP-VK.sparse_resources.shader_intrinsics."
167        "2d_array_sparse_fetch.g16_b16r16_2plane_444_unorm.11_37_3_nontemporal,Crash<br></details>"
168    )
169    event_with_summary = MagicMock()
170    event_with_summary.project_id = 176
171    event_with_summary.created_at = (current_time - timedelta(days=1)).isoformat()
172    event_with_summary.note = {"body": summary_message}
173
174    with patch("ci_post_gantt.Gitlab") as MockGitlab, patch(
175        "ci_post_gantt.get_gitlab_pipeline_from_url", return_value=None
176    ) as mock_get_gitlab_pipeline_from_url:
177
178        def safe_mock(*args, **kwargs):
179            with suppress(TypeError):
180                raise MockGanttExit("Exiting for test purposes")
181
182        mock_get_gitlab_pipeline_from_url.side_effect = safe_mock
183
184        mock_user = MagicMock()
185        mock_user.events = MagicMock()
186        mock_user.events.list.return_value = [event_plain, event_with_summary]
187
188        mock_gitlab_instance = MagicMock(spec=Gitlab)
189        mock_gitlab_instance.users = MagicMock()
190        mock_gitlab_instance.users.get.return_value = mock_user
191        MockGitlab.return_value = mock_gitlab_instance
192
193        last_event_date = (current_time - timedelta(days=3)).isoformat()
194        ci_post_gantt.main("fake_token", last_event_date, 12345)
195        mock_get_gitlab_pipeline_from_url.assert_has_calls(
196            [
197                mock.call(mock_gitlab_instance, plain_url),
198                mock.call(mock_gitlab_instance, summary_url),
199            ],
200            any_order=True,
201        )
202