1# Copyright © 2019-2020 Intel Corporation
2
3# Permission is hereby granted, free of charge, to any person obtaining a copy
4# of this software and associated documentation files (the "Software"), to deal
5# in the Software without restriction, including without limitation the rights
6# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7# copies of the Software, and to permit persons to whom the Software is
8# furnished to do so, subject to the following conditions:
9
10# The above copyright notice and this permission notice shall be included in
11# all copies or substantial portions of the Software.
12
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19# SOFTWARE.
20
21"""Tests for pick's core data structures and routines."""
22
23from unittest import mock
24import textwrap
25import typing
26
27import attr
28import pytest
29
30from . import core
31
32
33class TestCommit:
34
35    @pytest.fixture
36    def unnominated_commit(self) -> 'core.Commit':
37        return core.Commit('abc123', 'sub: A commit', main_sha='45678')
38
39    @pytest.fixture
40    def nominated_commit(self) -> 'core.Commit':
41        return core.Commit('abc123', 'sub: A commit', True,
42                           core.NominationType.CC, core.Resolution.UNRESOLVED)
43
44    class TestToJson:
45
46        def test_not_nominated(self, unnominated_commit: 'core.Commit'):
47            c = unnominated_commit
48            v = c.to_json()
49            assert v == {'sha': 'abc123', 'description': 'sub: A commit', 'nominated': False,
50                         'nomination_type': None, 'resolution': core.Resolution.UNRESOLVED.value,
51                         'main_sha': '45678', 'because_sha': None}
52
53        def test_nominated(self, nominated_commit: 'core.Commit'):
54            c = nominated_commit
55            v = c.to_json()
56            assert v == {'sha': 'abc123',
57                         'description': 'sub: A commit',
58                         'nominated': True,
59                         'nomination_type': core.NominationType.CC.value,
60                         'resolution': core.Resolution.UNRESOLVED.value,
61                         'main_sha': None,
62                         'because_sha': None}
63
64    class TestFromJson:
65
66        def test_not_nominated(self, unnominated_commit: 'core.Commit'):
67            c = unnominated_commit
68            v = c.to_json()
69            c2 = core.Commit.from_json(v)
70            assert c == c2
71
72        def test_nominated(self, nominated_commit: 'core.Commit'):
73            c = nominated_commit
74            v = c.to_json()
75            c2 = core.Commit.from_json(v)
76            assert c == c2
77
78
79class TestRE:
80
81    """Tests for the regular expressions used to identify commits."""
82
83    class TestFixes:
84
85        def test_simple(self):
86            message = textwrap.dedent("""\
87                etnaviv: fix vertex buffer state emission for single stream GPUs
88
89                GPUs with a single supported vertex stream must use the single state
90                address to program the stream.
91
92                Fixes: 3d09bb390a39 (etnaviv: GC7000: State changes for HALTI3..5)
93                Signed-off-by: Lucas Stach <l.stach@pengutronix.de>
94                Reviewed-by: Jonathan Marek <jonathan@marek.ca>
95            """)
96
97            m = core.IS_FIX.search(message)
98            assert m is not None
99            assert m.group(1) == '3d09bb390a39'
100
101    class TestCC:
102
103        def test_single_branch(self):
104            """Tests commit meant for a single branch, ie, 19.1"""
105            message = textwrap.dedent("""\
106                radv: fix DCC fast clear code for intensity formats
107
108                This fixes a rendering issue with DiRT 4 on GFX10. Only GFX10 was
109                affected because intensity formats are different.
110
111                Cc: 19.2 <mesa-stable@lists.freedesktop.org>
112                Closes: https://gitlab.freedesktop.org/mesa/mesa/-/issues/1923
113                Signed-off-by: Samuel Pitoiset <samuel.pitoiset@gmail.com>
114                Reviewed-by: Bas Nieuwenhuizen <bas@basnieuwenhuizen.nl>
115            """)
116
117            m = core.IS_CC.search(message)
118            assert m is not None
119            assert m.group(1) == '19.2'
120
121        def test_multiple_branches(self):
122            """Tests commit with more than one branch specified"""
123            message = textwrap.dedent("""\
124                radeonsi: enable zerovram for Rocket League
125
126                Fixes corruption on game startup.
127                Closes: https://gitlab.freedesktop.org/mesa/mesa/-/issues/1888
128
129                Cc: 19.1 19.2 <mesa-stable@lists.freedesktop.org>
130                Reviewed-by: Pierre-Eric Pelloux-Prayer <pierre-eric.pelloux-prayer@amd.com>
131            """)
132
133            m = core.IS_CC.search(message)
134            assert m is not None
135            assert m.group(1) == '19.1'
136            assert m.group(2) == '19.2'
137
138        def test_no_branch(self):
139            """Tests commit with no branch specification"""
140            message = textwrap.dedent("""\
141                anv/android: fix images created with external format support
142
143                This fixes a case where user first creates image and then later binds it
144                with memory created from AHW buffer.
145
146                Cc: <mesa-stable@lists.freedesktop.org>
147                Signed-off-by: Tapani Pälli <tapani.palli@intel.com>
148                Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
149            """)
150
151            m = core.IS_CC.search(message)
152            assert m is not None
153
154        def test_quotes(self):
155            """Tests commit with quotes around the versions"""
156            message = textwrap.dedent("""\
157                 anv: Always fill out the AUX table even if CCS is disabled
158
159                 Cc: "20.0" mesa-stable@lists.freedesktop.org
160                 Reviewed-by: Kenneth Graunke <kenneth@whitecape.org>
161                 Tested-by: Marge Bot <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
162                 Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
163            """)
164
165            m = core.IS_CC.search(message)
166            assert m is not None
167            assert m.group(1) == '20.0'
168
169        def test_multiple_quotes(self):
170            """Tests commit with quotes around the versions"""
171            message = textwrap.dedent("""\
172                 anv: Always fill out the AUX table even if CCS is disabled
173
174                 Cc: "20.0" "20.1" mesa-stable@lists.freedesktop.org
175                 Reviewed-by: Kenneth Graunke <kenneth@whitecape.org>
176                 Tested-by: Marge Bot <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
177                 Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
178            """)
179
180            m = core.IS_CC.search(message)
181            assert m is not None
182            assert m.group(1) == '20.0'
183            assert m.group(2) == '20.1'
184
185        def test_single_quotes(self):
186            """Tests commit with quotes around the versions"""
187            message = textwrap.dedent("""\
188                 anv: Always fill out the AUX table even if CCS is disabled
189
190                 Cc: '20.0' mesa-stable@lists.freedesktop.org
191                 Reviewed-by: Kenneth Graunke <kenneth@whitecape.org>
192                 Tested-by: Marge Bot <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
193                 Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
194            """)
195
196            m = core.IS_CC.search(message)
197            assert m is not None
198            assert m.group(1) == '20.0'
199
200        def test_multiple_single_quotes(self):
201            """Tests commit with quotes around the versions"""
202            message = textwrap.dedent("""\
203                 anv: Always fill out the AUX table even if CCS is disabled
204
205                 Cc: '20.0' '20.1' mesa-stable@lists.freedesktop.org
206                 Reviewed-by: Kenneth Graunke <kenneth@whitecape.org>
207                 Tested-by: Marge Bot <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
208                 Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/3454>
209            """)
210
211            m = core.IS_CC.search(message)
212            assert m is not None
213            assert m.group(1) == '20.0'
214            assert m.group(2) == '20.1'
215
216    class TestRevert:
217
218        def test_simple(self):
219            message = textwrap.dedent("""\
220                Revert "radv: do not emit PKT3_CONTEXT_CONTROL with AMDGPU 3.6.0+"
221
222                This reverts commit 2ca8629fa9b303e24783b76a7b3b0c2513e32fbd.
223
224                This was initially ported from RadeonSI, but in the meantime it has
225                been reverted because it might hang. Be conservative and re-introduce
226                this packet emission.
227
228                Unfortunately this doesn't fix anything known.
229
230                Cc: 19.2 <mesa-stable@lists.freedesktop.org>
231                Signed-off-by: Samuel Pitoiset <samuel.pitoiset@gmail.com>
232                Reviewed-by: Bas Nieuwenhuizen <bas@basnieuwenhuizen.nl>
233            """)
234
235            m = core.IS_REVERT.search(message)
236            assert m is not None
237            assert m.group(1) == '2ca8629fa9b303e24783b76a7b3b0c2513e32fbd'
238
239
240class TestResolveNomination:
241
242    @attr.s(slots=True)
243    class FakeSubprocess:
244
245        """A fake asyncio.subprocess like classe for use with mock."""
246
247        out: typing.Optional[bytes] = attr.ib(None)
248        returncode: int = attr.ib(0)
249
250        async def mock(self, *_, **__):
251            """A dirtly little helper for mocking."""
252            return self
253
254        async def communicate(self) -> typing.Tuple[bytes, bytes]:
255            assert self.out is not None
256            return self.out, b''
257
258        async def wait(self) -> int:
259            return self.returncode
260
261    @staticmethod
262    async def return_true(*_, **__) -> bool:
263        return True
264
265    @staticmethod
266    async def return_false(*_, **__) -> bool:
267        return False
268
269    @pytest.mark.asyncio
270    async def test_fix_is_nominated(self):
271        s = self.FakeSubprocess(b'Fixes: 3d09bb390a39 (etnaviv: GC7000: State changes for HALTI3..5)')
272        c = core.Commit('abcdef1234567890', 'a commit')
273
274        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
275            with mock.patch('bin.pick.core.is_commit_in_branch', self.return_true):
276                await core.resolve_nomination(c, '')
277
278        assert c.nominated
279        assert c.nomination_type is core.NominationType.FIXES
280
281    @pytest.mark.asyncio
282    async def test_fix_is_not_nominated(self):
283        s = self.FakeSubprocess(b'Fixes: 3d09bb390a39 (etnaviv: GC7000: State changes for HALTI3..5)')
284        c = core.Commit('abcdef1234567890', 'a commit')
285
286        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
287            with mock.patch('bin.pick.core.is_commit_in_branch', self.return_false):
288                await core.resolve_nomination(c, '')
289
290        assert not c.nominated
291        assert c.nomination_type is core.NominationType.FIXES
292
293    @pytest.mark.asyncio
294    async def test_cc_is_nominated(self):
295        s = self.FakeSubprocess(b'Cc: 16.2 <mesa-stable@lists.freedesktop.org>')
296        c = core.Commit('abcdef1234567890', 'a commit')
297
298        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
299            await core.resolve_nomination(c, '16.2')
300
301        assert c.nominated
302        assert c.nomination_type is core.NominationType.CC
303
304    @pytest.mark.asyncio
305    async def test_cc_is_nominated2(self):
306        s = self.FakeSubprocess(b'Cc: mesa-stable@lists.freedesktop.org')
307        c = core.Commit('abcdef1234567890', 'a commit')
308
309        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
310            await core.resolve_nomination(c, '16.2')
311
312        assert c.nominated
313        assert c.nomination_type is core.NominationType.CC
314
315    @pytest.mark.asyncio
316    async def test_cc_is_not_nominated(self):
317        s = self.FakeSubprocess(b'Cc: 16.2 <mesa-stable@lists.freedesktop.org>')
318        c = core.Commit('abcdef1234567890', 'a commit')
319
320        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
321            await core.resolve_nomination(c, '16.1')
322
323        assert not c.nominated
324        assert c.nomination_type is None
325
326    @pytest.mark.asyncio
327    async def test_revert_is_nominated(self):
328        s = self.FakeSubprocess(b'This reverts commit 1234567890123456789012345678901234567890.')
329        c = core.Commit('abcdef1234567890', 'a commit')
330
331        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
332            with mock.patch('bin.pick.core.is_commit_in_branch', self.return_true):
333                await core.resolve_nomination(c, '')
334
335        assert c.nominated
336        assert c.nomination_type is core.NominationType.REVERT
337
338    @pytest.mark.asyncio
339    async def test_revert_is_not_nominated(self):
340        s = self.FakeSubprocess(b'This reverts commit 1234567890123456789012345678901234567890.')
341        c = core.Commit('abcdef1234567890', 'a commit')
342
343        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
344            with mock.patch('bin.pick.core.is_commit_in_branch', self.return_false):
345                await core.resolve_nomination(c, '')
346
347        assert not c.nominated
348        assert c.nomination_type is core.NominationType.REVERT
349
350    @pytest.mark.asyncio
351    async def test_is_fix_and_cc(self):
352        s = self.FakeSubprocess(
353            b'Fixes: 3d09bb390a39 (etnaviv: GC7000: State changes for HALTI3..5)\n'
354            b'Cc: 16.1 <mesa-stable@lists.freedesktop.org>'
355        )
356        c = core.Commit('abcdef1234567890', 'a commit')
357
358        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
359            with mock.patch('bin.pick.core.is_commit_in_branch', self.return_true):
360                await core.resolve_nomination(c, '16.1')
361
362        assert c.nominated
363        assert c.nomination_type is core.NominationType.FIXES
364
365    @pytest.mark.asyncio
366    async def test_is_fix_and_revert(self):
367        s = self.FakeSubprocess(
368            b'Fixes: 3d09bb390a39 (etnaviv: GC7000: State changes for HALTI3..5)\n'
369            b'This reverts commit 1234567890123456789012345678901234567890.'
370        )
371        c = core.Commit('abcdef1234567890', 'a commit')
372
373        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
374            with mock.patch('bin.pick.core.is_commit_in_branch', self.return_true):
375                await core.resolve_nomination(c, '16.1')
376
377        assert c.nominated
378        assert c.nomination_type is core.NominationType.FIXES
379
380    @pytest.mark.asyncio
381    async def test_is_cc_and_revert(self):
382        s = self.FakeSubprocess(
383            b'This reverts commit 1234567890123456789012345678901234567890.\n'
384            b'Cc: 16.1 <mesa-stable@lists.freedesktop.org>'
385        )
386        c = core.Commit('abcdef1234567890', 'a commit')
387
388        with mock.patch('bin.pick.core.asyncio.create_subprocess_exec', s.mock):
389            with mock.patch('bin.pick.core.is_commit_in_branch', self.return_true):
390                await core.resolve_nomination(c, '16.1')
391
392        assert c.nominated
393        assert c.nomination_type is core.NominationType.CC
394
395
396class TestResolveFixes:
397
398    @pytest.mark.asyncio
399    async def test_in_new(self):
400        """Because commit abcd is nominated, so f123 should be as well."""
401        c = [
402            core.Commit('f123', 'desc', nomination_type=core.NominationType.FIXES, because_sha='abcd'),
403            core.Commit('abcd', 'desc', True),
404        ]
405        await core.resolve_fixes(c, [])
406        assert c[1].nominated
407
408    @pytest.mark.asyncio
409    async def test_not_in_new(self):
410        """Because commit abcd is not nominated, commit f123 shouldn't be either."""
411        c = [
412            core.Commit('f123', 'desc', nomination_type=core.NominationType.FIXES, because_sha='abcd'),
413            core.Commit('abcd', 'desc'),
414        ]
415        await core.resolve_fixes(c, [])
416        assert not c[0].nominated
417
418    @pytest.mark.asyncio
419    async def test_in_previous(self):
420        """Because commit abcd is nominated, so f123 should be as well."""
421        p = [
422            core.Commit('abcd', 'desc', True),
423        ]
424        c = [
425            core.Commit('f123', 'desc', nomination_type=core.NominationType.FIXES, because_sha='abcd'),
426        ]
427        await core.resolve_fixes(c, p)
428        assert c[0].nominated
429
430    @pytest.mark.asyncio
431    async def test_not_in_previous(self):
432        """Because commit abcd is not nominated, commit f123 shouldn't be either."""
433        p = [
434            core.Commit('abcd', 'desc'),
435        ]
436        c = [
437            core.Commit('f123', 'desc', nomination_type=core.NominationType.FIXES, because_sha='abcd'),
438        ]
439        await core.resolve_fixes(c, p)
440        assert not c[0].nominated
441
442
443class TestIsCommitInBranch:
444
445    @pytest.mark.asyncio
446    async def test_no(self):
447        # Hopefully this is never true?
448        value = await core.is_commit_in_branch('ffffffffffffffffffffffffffffff')
449        assert not value
450
451    @pytest.mark.asyncio
452    async def test_yes(self):
453        # This commit is from 2000, it better always be in the branch
454        value = await core.is_commit_in_branch('88f3b89a2cb77766d2009b9868c44e03abe2dbb2')
455        assert value
456
457
458class TestFullSha:
459
460    @pytest.mark.asyncio
461    async def test_basic(self):
462        # This commit is from 2000, it better always be in the branch
463        value = await core.full_sha('88f3b89a2cb777')
464        assert value
465
466    @pytest.mark.asyncio
467    async def test_invalid(self):
468        # This commit is from 2000, it better always be in the branch
469        with pytest.raises(core.PickUIException):
470            await core.full_sha('fffffffffffffffffffffffffffffffffff')
471