Testing concurrent code
How to test threaded and asyncio Python so the result is the same on every run: a common start line, an order forced through a seam, a bound on every wait, an injected clock, and the shutdown and error paths.
My threaded test passed a thousand times in a loop. Is the code thread-safe?
Not on that evidence. Which thread runs when is the operating system's choice, and the interpreter offers a switch only every few milliseconds, so a short test can run each thread to the end without ever overlapping another. A thousand passes are a thousand runs of the orders your machine happened to pick. Either assert something that no order can change, or hold the threads inside the window you care about with a Barrier or a pair of Events so the dangerous order happens on every run.
Why does every join and wait in these tests have a timeout of five seconds?
So that a bug fails the test instead of hanging the run. A passing test never waits out the bound, because the thing it waits for happens at once, so a large bound costs nothing when the code is right. When the code is wrong, the join returns after five seconds, the test asserts that the thread is no longer alive, and the failure has a name. Thread.join returns None whether or not it timed out, so the assertion is on is_alive(), never on what join returned.
How do I test code that depends on time without calling sleep?
Give the class its clock as a parameter, with time.monotonic as the default, and pass a clock the test controls. The test then moves time by assigning to it: sixty seconds pass in one line and take no real time. A blocking call that computes its own deadline from that clock can be tested the same way, by handing it readings that are already past the deadline.
Is a stress test with many threads worth writing?
Once, as a net, and never as your only test. A pass is weak evidence, because the orders that break the code may not have come up. A failure is strong evidence, because the assertion is one no correct interleaving can break. Keep it fast, point it at correct code, and make its assertion a total or an invariant, so a pass on one run means the same as a pass on any other.
Do I need a plugin to test asyncio code?
No. Write an ordinary test function that calls asyncio.run on a coroutine and asserts on what it returns. One thread runs every task, and tasks switch only at an await, so an order forced with asyncio.Event repeats exactly from run to run. Wrap the awaits in asyncio.timeout so that a task that never finishes fails the test with TimeoutError.