1from xdist.dsession import DSession, get_default_max_worker_restart
2from xdist.report import report_collection_diff
3from xdist.scheduler import EachScheduling, LoadScheduling
4
5import py
6import pytest
7import execnet
8
9XSpec = execnet.XSpec
10
11
12def run(item, node, excinfo=None):
13    runner = item.config.pluginmanager.getplugin("runner")
14    rep = runner.ItemTestReport(item=item, excinfo=excinfo, when="call")
15    rep.node = node
16    return rep
17
18
19class MockGateway:
20    _count = 0
21
22    def __init__(self):
23        self.id = str(self._count)
24        self._count += 1
25
26
27class MockNode:
28    def __init__(self):
29        self.sent = []
30        self.gateway = MockGateway()
31        self._shutdown = False
32
33    def send_runtest_some(self, indices):
34        self.sent.extend(indices)
35
36    def send_runtest_all(self):
37        self.sent.append("ALL")
38
39    def shutdown(self):
40        self._shutdown = True
41
42    @property
43    def shutting_down(self):
44        return self._shutdown
45
46
47def dumpqueue(queue):
48    while queue.qsize():
49        print(queue.get())
50
51
52class TestEachScheduling:
53    def test_schedule_load_simple(self, testdir):
54        node1 = MockNode()
55        node2 = MockNode()
56        config = testdir.parseconfig("--tx=2*popen")
57        sched = EachScheduling(config)
58        sched.add_node(node1)
59        sched.add_node(node2)
60        collection = ["a.py::test_1"]
61        assert not sched.collection_is_completed
62        sched.add_node_collection(node1, collection)
63        assert not sched.collection_is_completed
64        sched.add_node_collection(node2, collection)
65        assert sched.collection_is_completed
66        assert sched.node2collection[node1] == collection
67        assert sched.node2collection[node2] == collection
68        sched.schedule()
69        assert sched.tests_finished
70        assert node1.sent == ["ALL"]
71        assert node2.sent == ["ALL"]
72        sched.mark_test_complete(node1, 0)
73        assert sched.tests_finished
74        sched.mark_test_complete(node2, 0)
75        assert sched.tests_finished
76
77    def test_schedule_remove_node(self, testdir):
78        node1 = MockNode()
79        config = testdir.parseconfig("--tx=popen")
80        sched = EachScheduling(config)
81        sched.add_node(node1)
82        collection = ["a.py::test_1"]
83        assert not sched.collection_is_completed
84        sched.add_node_collection(node1, collection)
85        assert sched.collection_is_completed
86        assert sched.node2collection[node1] == collection
87        sched.schedule()
88        assert sched.tests_finished
89        crashitem = sched.remove_node(node1)
90        assert crashitem
91        assert sched.tests_finished
92        assert not sched.nodes
93
94
95class TestLoadScheduling:
96    def test_schedule_load_simple(self, testdir):
97        config = testdir.parseconfig("--tx=2*popen")
98        sched = LoadScheduling(config)
99        sched.add_node(MockNode())
100        sched.add_node(MockNode())
101        node1, node2 = sched.nodes
102        collection = ["a.py::test_1", "a.py::test_2"]
103        assert not sched.collection_is_completed
104        sched.add_node_collection(node1, collection)
105        assert not sched.collection_is_completed
106        sched.add_node_collection(node2, collection)
107        assert sched.collection_is_completed
108        assert sched.node2collection[node1] == collection
109        assert sched.node2collection[node2] == collection
110        sched.schedule()
111        assert not sched.pending
112        assert sched.tests_finished
113        assert len(node1.sent) == 1
114        assert len(node2.sent) == 1
115        assert node1.sent == [0]
116        assert node2.sent == [1]
117        sched.mark_test_complete(node1, node1.sent[0])
118        assert sched.tests_finished
119
120    def test_schedule_batch_size(self, testdir):
121        config = testdir.parseconfig("--tx=2*popen")
122        sched = LoadScheduling(config)
123        sched.add_node(MockNode())
124        sched.add_node(MockNode())
125        node1, node2 = sched.nodes
126        col = ["xyz"] * 6
127        sched.add_node_collection(node1, col)
128        sched.add_node_collection(node2, col)
129        sched.schedule()
130        # assert not sched.tests_finished
131        sent1 = node1.sent
132        sent2 = node2.sent
133        assert sent1 == [0, 2]
134        assert sent2 == [1, 3]
135        assert sched.pending == [4, 5]
136        assert sched.node2pending[node1] == sent1
137        assert sched.node2pending[node2] == sent2
138        assert len(sched.pending) == 2
139        sched.mark_test_complete(node1, 0)
140        assert node1.sent == [0, 2, 4]
141        assert sched.pending == [5]
142        assert node2.sent == [1, 3]
143        sched.mark_test_complete(node1, 2)
144        assert node1.sent == [0, 2, 4, 5]
145        assert not sched.pending
146
147    def test_schedule_fewer_tests_than_nodes(self, testdir):
148        config = testdir.parseconfig("--tx=2*popen")
149        sched = LoadScheduling(config)
150        sched.add_node(MockNode())
151        sched.add_node(MockNode())
152        sched.add_node(MockNode())
153        node1, node2, node3 = sched.nodes
154        col = ["xyz"] * 2
155        sched.add_node_collection(node1, col)
156        sched.add_node_collection(node2, col)
157        sched.schedule()
158        # assert not sched.tests_finished
159        sent1 = node1.sent
160        sent2 = node2.sent
161        sent3 = node3.sent
162        assert sent1 == [0]
163        assert sent2 == [1]
164        assert sent3 == []
165        assert not sched.pending
166
167    def test_schedule_fewer_than_two_tests_per_node(self, testdir):
168        config = testdir.parseconfig("--tx=2*popen")
169        sched = LoadScheduling(config)
170        sched.add_node(MockNode())
171        sched.add_node(MockNode())
172        sched.add_node(MockNode())
173        node1, node2, node3 = sched.nodes
174        col = ["xyz"] * 5
175        sched.add_node_collection(node1, col)
176        sched.add_node_collection(node2, col)
177        sched.schedule()
178        # assert not sched.tests_finished
179        sent1 = node1.sent
180        sent2 = node2.sent
181        sent3 = node3.sent
182        assert sent1 == [0, 3]
183        assert sent2 == [1, 4]
184        assert sent3 == [2]
185        assert not sched.pending
186
187    def test_add_remove_node(self, testdir):
188        node = MockNode()
189        config = testdir.parseconfig("--tx=popen")
190        sched = LoadScheduling(config)
191        sched.add_node(node)
192        collection = ["test_file.py::test_func"]
193        sched.add_node_collection(node, collection)
194        assert sched.collection_is_completed
195        sched.schedule()
196        assert not sched.pending
197        crashitem = sched.remove_node(node)
198        assert crashitem == collection[0]
199
200    def test_different_tests_collected(self, testdir):
201        """
202        Test that LoadScheduling is reporting collection errors when
203        different test ids are collected by workers.
204        """
205
206        class CollectHook(object):
207            """
208            Dummy hook that stores collection reports.
209            """
210
211            def __init__(self):
212                self.reports = []
213
214            def pytest_collectreport(self, report):
215                self.reports.append(report)
216
217        collect_hook = CollectHook()
218        config = testdir.parseconfig("--tx=2*popen")
219        config.pluginmanager.register(collect_hook, "collect_hook")
220        node1 = MockNode()
221        node2 = MockNode()
222        sched = LoadScheduling(config)
223        sched.add_node(node1)
224        sched.add_node(node2)
225        sched.add_node_collection(node1, ["a.py::test_1"])
226        sched.add_node_collection(node2, ["a.py::test_2"])
227        sched.schedule()
228        assert len(collect_hook.reports) == 1
229        rep = collect_hook.reports[0]
230        assert "Different tests were collected between" in rep.longrepr
231
232
233class TestDistReporter:
234    @py.test.mark.xfail
235    def test_rsync_printing(self, testdir, linecomp):
236        config = testdir.parseconfig()
237        from _pytest.pytest_terminal import TerminalReporter
238
239        rep = TerminalReporter(config, file=linecomp.stringio)
240        config.pluginmanager.register(rep, "terminalreporter")
241        dsession = DSession(config)
242
243        class gw1:
244            id = "X1"
245            spec = execnet.XSpec("popen")
246
247        class gw2:
248            id = "X2"
249            spec = execnet.XSpec("popen")
250
251        # class rinfo:
252        #    version_info = (2, 5, 1, 'final', 0)
253        #    executable = "hello"
254        #    platform = "xyz"
255        #    cwd = "qwe"
256
257        # dsession.pytest_xdist_newgateway(gw1, rinfo)
258        # linecomp.assert_contains_lines([
259        #     "*X1*popen*xyz*2.5*"
260        # ])
261        dsession.pytest_xdist_rsyncstart(source="hello", gateways=[gw1, gw2])
262        linecomp.assert_contains_lines(["[X1,X2] rsyncing: hello"])
263
264
265def test_report_collection_diff_equal():
266    """Test reporting of equal collections."""
267    from_collection = to_collection = ["aaa", "bbb", "ccc"]
268    assert report_collection_diff(from_collection, to_collection, 1, 2) is None
269
270
271def test_default_max_worker_restart():
272    class config:
273        class option:
274            maxworkerrestart = None
275            numprocesses = 0
276
277    assert get_default_max_worker_restart(config) is None
278
279    config.option.numprocesses = 2
280    assert get_default_max_worker_restart(config) == 8
281
282    config.option.maxworkerrestart = "1"
283    assert get_default_max_worker_restart(config) == 1
284
285    config.option.maxworkerrestart = "0"
286    assert get_default_max_worker_restart(config) == 0
287
288
289def test_report_collection_diff_different():
290    """Test reporting of different collections."""
291    from_collection = ["aaa", "bbb", "ccc", "YYY"]
292    to_collection = ["aZa", "bbb", "XXX", "ccc"]
293    error_message = (
294        "Different tests were collected between 1 and 2. The difference is:\n"
295        "--- 1\n"
296        "\n"
297        "+++ 2\n"
298        "\n"
299        "@@ -1,4 +1,4 @@\n"
300        "\n"
301        "-aaa\n"
302        "+aZa\n"
303        " bbb\n"
304        "+XXX\n"
305        " ccc\n"
306        "-YYY"
307    )
308
309    msg = report_collection_diff(from_collection, to_collection, "1", "2")
310    assert msg == error_message
311
312
313@pytest.mark.xfail(reason="duplicate test ids not supported yet")
314def test_pytest_issue419(testdir):
315    testdir.makepyfile(
316        """
317        import pytest
318
319        @pytest.mark.parametrize('birth_year', [1988, 1988, ])
320        def test_2011_table(birth_year):
321            pass
322    """
323    )
324    reprec = testdir.inline_run("-n1")
325    reprec.assertoutcome(passed=2)
326    assert 0
327