def pytest_runtestloop(self): """pytest runtest loop - Disable the master terminal reporter hooks, so we can add our own handlers that include the slaveid in the output - Send tests to slaves when they ask - Log the starting of tests and test results, including slave id - Handle clean slave shutdown when they finish their runtest loops - Restore the master terminal reporter after testing so we get the final report """ # Build master collection for slave diffing and distribution for item in self.session.items: is_serial = bool(item.get_closest_marker("serial")) if is_serial: self.serial_collection.append(item.nodeid) else: self.collection.append(item.nodeid) # Fire up the workers after master collection is complete # master and the first slave share an appliance, this is a workaround to prevent a slave # from altering an appliance while master collection is still taking place for slave in self.slaves.values(): slave.start() try: self.print_message( f"Waiting for collection on {len(self.slaves)} pytest instances", red=True) # Turn off the terminal reporter to suppress the builtin logstart printing terminalreporter.disable() while True: # spawn/kill/replace slaves if needed self._slave_audit() if not self.slaves: # All slaves are killed or errored, we're done with tests self.print_message('all slaves have exited', yellow=True) self.session_finished = True if self.session_finished: break slave, event_data, event_name = self.recv() if event_name == 'message': message = event_data.pop('message') markup = event_data.pop('markup') # messages are special, handle them immediately self.print_message(message, slave, **markup) self.ack(slave, event_name) elif event_name == 'collectionfinish': slave_collection = event_data['node_ids'] # compare slave collection to the master, all test ids must be the same self.log.debug(f'diffing {slave.id} collection') diff_err = report_collection_diff( slave.id, self.collection + self.serial_collection, slave_collection) if diff_err: self.print_message('collection differs, respawning', slave.id, purple=True) self.print_message(diff_err, purple=True) self.log.error(diff_err) self.kill(slave) slave.start() else: self.ack(slave, event_name) elif event_name == 'need_tests': self.send_tests(slave) self.log.info('starting master test distribution') elif event_name == 'runtest_logstart': self.ack(slave, event_name) self.trdist.runtest_logstart(slave.id, event_data['nodeid'], event_data['location']) elif event_name == 'runtest_logreport': self.ack(slave, event_name) report = unserialize_report(event_data['report']) if report.when in ('call', 'teardown'): slave.tests.discard(report.nodeid) self.trdist.runtest_logreport(slave.id, report) elif event_name == 'internalerror': self.ack(slave, event_name) self.print_message(event_data['message'], slave, purple=True) self.kill(slave) elif event_name == 'shutdown': self.config.hook.pytest_miq_node_shutdown( config=self.config, nodeinfo=slave.appliance.url) self.ack(slave, event_name) del self.slaves[slave.id] self.monitor_shutdown(slave) # total slave spawn count * 3, to allow for each slave's initial spawn # and then each slave (on average) can fail two times if self.slave_spawn_count >= len(self.appliances) * 3: self.print_message('too many slave respawns, exiting', red=True, bold=True) raise KeyboardInterrupt( 'Interrupted due to slave failures') except Exception as ex: self.log.exception('Exception in runtest loop:') self.print_message(str(ex)) raise finally: terminalreporter.enable() # Suppress other runtestloop calls return True
def pytest_runtestloop(self): """pytest runtest loop - Disable the master terminal reporter hooks, so we can add our own handlers that include the slaveid in the output - Send tests to slaves when they ask - Log the starting of tests and test results, including slave id - Handle clean slave shutdown when they finish their runtest loops - Restore the master terminal reporter after testing so we get the final report """ # Build master collection for slave diffing and distribution self.collection = [item.nodeid for item in self.session.items] # Fire up the workers after master collection is complete # master and the first slave share an appliance, this is a workaround to prevent a slave # from altering an appliance while master collection is still taking place for slave in self.slaves.values(): slave.start() try: self.print_message("Waiting for {} slave collections".format(len(self.slaves)), red=True) # Turn off the terminal reporter to suppress the builtin logstart printing terminalreporter.disable() while True: # spawn/kill/replace slaves if needed self._slave_audit() if not self.slaves: # All slaves are killed or errored, we're done with tests self.print_message('all slaves have exited', yellow=True) self.session_finished = True if self.session_finished: break slave, event_data, event_name = self.recv() if event_name == 'message': message = event_data.pop('message') markup = event_data.pop('markup') # messages are special, handle them immediately self.print_message(message, slave, **markup) self.ack(slave, event_name) elif event_name == 'collectionfinish': slave_collection = event_data['node_ids'] # compare slave collection to the master, all test ids must be the same self.log.debug('diffing {} collection'.format(slave.id)) diff_err = report_collection_diff( slave.id, self.collection, slave_collection) if diff_err: self.print_message( 'collection differs, respawning', slave.id, purple=True) self.print_message(diff_err, purple=True) self.log.error('{}'.format(diff_err)) self.kill(slave) slave.start() else: self.ack(slave, event_name) elif event_name == 'need_tests': self.send_tests(slave) self.log.info('starting master test distribution') elif event_name == 'runtest_logstart': self.ack(slave, event_name) self.trdist.runtest_logstart( slave.id, event_data['nodeid'], event_data['location']) elif event_name == 'runtest_logreport': self.ack(slave, event_name) report = unserialize_report(event_data['report']) if report.when in ('call', 'teardown'): slave.tests.discard(report.nodeid) self.trdist.runtest_logreport(slave.id, report) elif event_name == 'internalerror': self.ack(slave, event_name) self.print_message(event_data['message'], slave, purple=True) self.kill(slave) elif event_name == 'shutdown': self.config.hook.pytest_miq_node_shutdown( config=self.config, nodeinfo=slave.appliance.url) self.ack(slave, event_name) del self.slaves[slave.id] self.monitor_shutdown(slave) # total slave spawn count * 3, to allow for each slave's initial spawn # and then each slave (on average) can fail two times if self.slave_spawn_count >= len(self.appliances) * 3: self.print_message( 'too many slave respawns, exiting', red=True, bold=True) raise KeyboardInterrupt('Interrupted due to slave failures') except Exception as ex: self.log.error('Exception in runtest loop:') self.log.exception(ex) self.print_message(str(ex)) raise finally: terminalreporter.enable() # Suppress other runtestloop calls return True