|
16 | 16 | import os |
17 | 17 | from pathlib import Path |
18 | 18 | import sys |
| 19 | +import threading |
| 20 | +from threading import Thread |
19 | 21 | from typing import final |
20 | 22 | from typing import Literal |
21 | 23 | from typing import overload |
@@ -581,6 +583,8 @@ def __init__(self, config: Config) -> None: |
581 | 583 | self._initial_parts: list[CollectionArgument] = [] |
582 | 584 | self._collection_cache: dict[nodes.Collector, CollectReport] = {} |
583 | 585 | self.items: list[nodes.Item] = [] |
| 586 | + # track the thread in which each item started execution in |
| 587 | + self._item_to_thread: dict[nodes.Item, Thread] = {} |
584 | 588 |
|
585 | 589 | self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) |
586 | 590 |
|
@@ -643,6 +647,22 @@ def startpath(self) -> Path: |
643 | 647 | """ |
644 | 648 | return self.config.invocation_params.dir |
645 | 649 |
|
| 650 | + def _readable_thread_id(self, thread: Thread) -> int: |
| 651 | + # Returns a 0-indexed id of `thread`, corresponding to the order in |
| 652 | + # which we saw that thread start executing tests. The main thread always |
| 653 | + # has value 0, so non-main threads start at 1, even if the first thread |
| 654 | + # we saw was a non-main thread, or even if we never saw the main thread |
| 655 | + # execute tests. |
| 656 | + |
| 657 | + # relying on item_to_thread to be sorted for stable ordering |
| 658 | + threads = list(self._item_to_thread.values()) |
| 659 | + assert thread in threads |
| 660 | + |
| 661 | + if thread is threading.main_thread(): |
| 662 | + return 0 |
| 663 | + |
| 664 | + return threads.index(thread) + 1 |
| 665 | + |
646 | 666 | def _node_location_to_relpath(self, node_path: Path) -> str: |
647 | 667 | # bestrelpath is a quite slow function. |
648 | 668 | return self._bestrelpathcache[node_path] |
|
0 commit comments