Files
pytest-xdist/OVERVIEW.md

65 lines
3.8 KiB
Markdown

# Overview #
`xdist` works by spawning one or more **worker nodes**, which are controlled
by the **master node**. Each **worker node** is responsible for performing
a full test collection and afterwards running tests as dictated by the **master node**.
The execution flow is:
1. **master node** spawns one or more **worker nodes** at the beginning of
the test session. The communication between **master** and **worker** nodes makes use of
[execnet](http://codespeak.net/execnet/) and its [gateways](http://codespeak.net/execnet/basics.html#gateways-bootstrapping-python-interpreters).
The actual interpreters executing the code for the **worker nodes** might
be remote or local.
1. Each **worker node** itself is a mini pytest runner. **workers** at this
point perform a full test collection, sending back the collected
test-ids back to the **master node** which does not
perform any collection itself.
1. The **master node** receives the result of the collection from all nodes.
At this point the **master node** performs some sanity check to ensure that
all **worker nodes** collected the same tests (including order), bailing out otherwise.
If all is well, it converts the list of test-ids into a list of simple
indexes, where each index corresponds to the position of that test in the
original collection list. This works because all nodes have the same
collection list, and saves bandwidth because the **master** can now tell
one of the workers to just *execute test index 3* index of passing the
full test id.
1. If **dist-mode** is **each**: the **master node** just sends the full list
of test indexes to each node at this moment.
1. If **dist-mode** is **load**: the **master node** takes around 25% of the
tests and sends them one by one to each **worker node** in a round robin
fashion. The rest of the tests will be distributed later as **worker nodes**
finish tests (see below).
1. **worker nodes** re-implement `pytest_runtestloop`: pytest's default implementation
basically loops over all collected items in the `session` object and executes
the `pytest_runtest_protocol` for each test item, but in xdist **workers** sit idly
waiting for **master node** to send tests for execution. As tests are
received by **workers**, `pytest_runtest_protocol` is executed for each test.
Here it worth noting an implementation detail: **workers** always must keep at
least one test item on their queue due to how the `pytest_runtest_protocol(item, nextitem)`
hook is defined: in order to pass the `nextitem` to the hook, the worker must wait for more
instructions from master before executing that remaining test. If it receives more tests,
then it can safely call `pytest_runtest_protocol` because it knows what the `nextitem` parameter will be.
If it receives a "shutdown" signal, then it can execute the hook passing `nextitem` as `None`.
1. As tests are started and completed at the **workers**, the results are sent
back to the **master node**, which then just forwards the results to
the appropriate pytest hooks: `pytest_runtest_logstart` and
`pytest_runtest_logreport`. This way other plugins (for example `junitxml`)
can work normally. The **master node** (when in dist-mode **load**)
decides to send more tests to a node when a test completes, using
some heuristics such as test durations and how many tests each **worker node**
still has to run.
1. When the **master node** has no more pending tests it will
send a "shutdown" signal to all **workers**, which will then run their
remaining tests to completion and shut down. At this point the
**master node** will sit waiting for **workers** to shut down, still
processing events such as `pytest_runtest_logreport`.