Skip to content

Commit 5284482

Browse files
committed
Document end to end testing.
1 parent 97014f3 commit 5284482

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# End to end testing
2+
3+
The `DirectoryWatcher` implementations combine information from OS events and
4+
filesystem polling, which leads to plenty of opportunities for data races
5+
between the two. There are also various data races related to OS event
6+
ordering and batching.
7+
8+
The tests in `end_to_end_tests.dart` protect against both logical errors and
9+
races.
10+
11+
All the tests work the same way: `ClientSimulator` uses a directory watcher to
12+
track the state of a directory, a series of filesystem changes are made in that
13+
directory using `FileChanger`, then `ClientSimulator` compares its inferred
14+
state with the actual state on disk.
15+
16+
File contents vary only by length, so the file contents can be given as a single
17+
number in the logs.
18+
19+
There are three types of error possible:
20+
21+
- `ClientSimulator` thinks a file exists on disk, but it doesn't; "missing delete event"
22+
- `ClientSimulator` does not know about a file that exists on disk; "missing add event"
23+
- `ClientSimulator` knows about a file that exists on disk, but has not read it after it was updated, meaning it has a wrong value for its contents/length; "missing modify event"
24+
25+
## Example data race
26+
27+
An example sequence of file operations that can cause a data race is moving a
28+
directory then making further modifications inside it. The OS events report the
29+
"new" directory but not its contents, so `DirectoryWatcher` has to list the
30+
contents. The list results and the OS events from subsequent operations can
31+
give contradictory information about the same file, with no way to know which
32+
is more recent and so correct. The implementations created with the help of
33+
these tests aim to detect such ambiguity and resolve it by polling again after
34+
the event arrives.
35+
36+
## Standalone tests
37+
38+
The end to end tests that run on CI include a series of seeded pseudorandom file
39+
operation batches and a set of hardcoded tests that were derived from
40+
interesting random runs. These guard against common data races.
41+
42+
But, they don't run for long enough on CI to give high confidence that there are
43+
no data races.
44+
45+
So, when making changes that might affect data races it is recommended to run
46+
a longer "standalone" end to end test run. This should be done on whichever
47+
platform(s) are affected, Windows, Mac and/or Linux, by running the end to end
48+
test multiple times in parallel and overnight.
49+
50+
```
51+
# Launch in multiple terminals, enough to use 100% CPU.
52+
dart test/directory_watcher/end_to_end_test_runner.dart random
53+
54+
# Or on Linux, install `parallel` and use that to run any number in parallel.
55+
parallel --ungroup --halt now,done=1 \
56+
-j 100 ::: \
57+
$(for i in (seq 1 100); do echo 'dart test/directory_watcher/end_to_end_test_runner.dart'; end)
58+
```
59+
60+
Run in this way the test runs until it hits a failure. If it does, it prints
61+
a link to a log which shows a combination of file operations `F`, watcher
62+
internals `W` and the events seen by the `ClientSimulator` marked `C`.
63+
It also prints the seed of the failure, which can be used to run the same
64+
pseudorandom batch of file operations to see if the exact same failure can
65+
be reproduced:
66+
67+
```
68+
dart test/directory_watcher/end_to_end_test_runner.dart seed 42
69+
```
70+
71+
Another way to rerun the exact same sequence of operations that failed is to
72+
copy the failure log into `end_to_end_tests.dart` as a test case. Only the lines
73+
that are file operations marked with `F` are needed. Then run:
74+
75+
```
76+
dart test/directory_watcher/end_to_end_test_runner.dart replay <test name>
77+
```
78+
79+
If a failure can be reproduced in this way then you can try removing
80+
irrelevant-seeming parts of the log until you have a minimal repro case. Note
81+
that a file operation that can't be carried out, for example a move into a
82+
directory that does not exist, is silently skipped over and does nothing.
83+
84+
### False negatives
85+
86+
The standalone end to end tests have one known "false negative" issue, which
87+
is that very occasionally and under heavy load the test might not wait long
88+
enough before deciding that `ClientWatcher` has incorrect state. This can be
89+
noticed in the failure log if all the wrong tracking is about file events at the
90+
end of the run, and with no watcher log entries afterwards. Such failures can
91+
be ignored.
92+
93+
TODO(davidmorgan): detect this automatically and wait longer instead of failing
94+
the test.

pkgs/watcher/test/directory_watcher/end_to_end_tests.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import 'file_changer.dart';
1919
///
2020
/// `end_to_end_test_runner` can be run as a binary to try random file changes
2121
/// until a failure, it outputs a log which can be turned into a test case here.
22+
///
23+
/// See `README.md` for more detail.
2224
void endToEndTests() {
2325
// Random test to cover a wide range of cases.
2426
test('end to end test: random', timeout: const Timeout(Duration(minutes: 10)),

0 commit comments

Comments
 (0)