def fix_timestamp(ts_str: str) -> str: """Read the timestamp text and get a minimized, formatted value.""" try: when = datetime.datetime.strptime(ts_str, '%Y-%m-%d %H:%M:%S.%f') except ValueError: return '' return output.get_relative_timestamp(when)
def build_local(self, tests, max_threads, mb_tracker, build_verbosity): """Build all tests that request for their build to occur on the kickoff host. :param list[TestRun] tests: The list of tests to potentially build. :param int max_threads: Maximum number of build threads to start. :param int build_verbosity: How much info to print during building. See the -b/--build-verbose argument for more info. :param MultiBuildTracker mb_tracker: The tracker for all builds. """ test_threads = [] # type: [(threading.Thread, None)] remote_builds = [] cancel_event = threading.Event() # Generate new build names for each test that is rebuilding. # We do this here, even for non_local tests, because otherwise the # non-local tests can't tell what was built fresh either on a # front-end or by other tests rebuilding on nodes. for test in tests: if test.rebuild and test.builder.exists(): test.builder.deprecate() test.builder.rename_build() test.save_build_name() # We don't want to start threads that are just going to wait on a lock, # so we'll rearrange the builds so that the uniq build names go first. # We'll use this as a stack, so tests that should build first go at # the end of the list. build_order = [] # If we've seen a build name, the build can go later. seen_build_names = set() for test in tests: if not test.build_local: remote_builds.append(test) elif test.builder.name not in seen_build_names: build_order.append(test) seen_build_names.add(test.builder.name) else: build_order.insert(0, test) # Keep track of what the last message printed per build was. # This is for double build verbosity. message_counts = {test.id: 0 for test in tests} # Used to track which threads are for which tests. test_by_threads = {} # The length of the last line printed when verbosity == 0. last_line_len = None if build_verbosity > 0: fprint(self.BUILD_STATUS_PREAMBLE .format(when='When', test_id='TestID', state_len=STATES.max_length, state='State'), 'Message', file=self.outfile, width=None) builds_running = 0 # Run and track <max_threads> build threads, giving output according # to the verbosity level. As threads finish, new ones are started until # either all builds complete or a build fails, in which case all tests # are aborted. while build_order or test_threads: # Start a new thread if we haven't hit our limit. if build_order and builds_running < max_threads: test = build_order.pop() test_thread = threading.Thread( target=test.build, args=(cancel_event,) ) test_threads.append(test_thread) test_by_threads[test_thread] = test test_thread.start() # Check if all our threads are alive, and join those that aren't. for i in range(len(test_threads)): thread = test_threads[i] if not thread.is_alive(): thread.join() builds_running -= 1 test_threads[i] = None test = test_by_threads[thread] # Only output test status after joining a thread. if build_verbosity == 1: notes = mb_tracker.get_notes(test.builder) when, state, msg = notes[-1] when = output.get_relative_timestamp(when) preamble = (self.BUILD_STATUS_PREAMBLE .format(when=when, test_id=test.id, state_len=STATES.max_length, state=state)) fprint(preamble, msg, wrap_indent=len(preamble), file=self.outfile, width=None) test_threads = [thr for thr in test_threads if thr is not None] if cancel_event.is_set(): for thread in test_threads: thread.join() for test in build_order + remote_builds: test.status.set(STATES.ABORTED, "Build aborted due to failures in other " "builds.") fprint("Build error while building tests. Cancelling runs.", color=output.RED, file=self.outfile) for failed_build in mb_tracker.failures(): fprint( "Build error for test {f.test.name} (#{f.test.id})." .format(f=failed_build), file=self.errfile) fprint( "See test status file (pav cat {id} status) and/or " "the test build log (pav log build {id})" .format(id=failed_build.test.id), file=self.errfile) return errno.EINVAL state_counts = mb_tracker.state_counts() if build_verbosity == 0: # Print a self-clearing one-liner of the counts of the # build statuses. parts = [] for state in sorted(state_counts.keys()): parts.append("{}: {}".format(state, state_counts[state])) line = ' | '.join(parts) if last_line_len is not None: fprint(' '*last_line_len, end='\r', file=self.outfile, width=None) last_line_len = len(line) fprint(line, end='\r', file=self.outfile, width=None) elif build_verbosity > 1: for test in tests: seen = message_counts[test.id] msgs = mb_tracker.messages[test.builder][seen:] for when, state, msg in msgs: when = output.get_relative_timestamp(when) state = '' if state is None else state preamble = self.BUILD_STATUS_PREAMBLE.format( when=when, test_id=test.id, state_len=STATES.max_length, state=state) fprint(preamble, msg, wrap_indent=len(preamble), file=self.outfile, width=None) message_counts[test.id] += len(msgs) time.sleep(self.BUILD_SLEEP_TIME) if build_verbosity == 0: # Print a newline after our last status update. fprint(width=None, file=self.outfile) return 0