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