@@ -7,6 +7,7 @@ import 'dart:io';
77
88import 'package:path/path.dart' as p;
99import 'package:test/test.dart' as package_test;
10+ import 'package:watcher/src/utils.dart' ;
1011import 'package:watcher/watcher.dart' ;
1112
1213import '../utils.dart' as utils;
@@ -31,12 +32,16 @@ void endToEndTests() {
3132/// and [printOnFailure] replacements.
3233///
3334/// To run until failure, set [endlessMode] to `true` .
35+ ///
36+ /// To fix the seed, set [seed] . The failure message prints the seed, so this
37+ /// can be used to run just the events that triggered the failure.
3438Future <void > _runTest ({
3539 void Function (void Function ())? addTearDown,
3640 Watcher Function ({required String path})? createWatcher,
3741 void Function (String )? fail,
3842 void Function (String )? printOnFailure,
3943 bool endlessMode = false ,
44+ int ? seed,
4045}) async {
4146 addTearDown ?? = package_test.addTearDown;
4247 createWatcher ?? = utils.createWatcher;
@@ -46,22 +51,28 @@ Future<void> _runTest({
4651 final temp = Directory .systemTemp.createTempSync ();
4752 addTearDown (() => temp.deleteSync (recursive: true ));
4853
54+ // Turn on logging of the watchers.
55+ final log = < LogEntry > [];
56+ logForTesting = (message) => log.add (LogEntry ('W $message ' ));
57+
4958 // Create the watcher and [ClientSimulator].
5059 final watcher = createWatcher (path: temp.path);
5160 final client = await ClientSimulator .watch (
52- watcher: watcher, printOnFailure : printOnFailure );
61+ watcher: watcher, log : (message) => log. add ( LogEntry ( 'C $ message ' )) );
5362 addTearDown (client.close);
5463
5564 // 40 iterations of making changes, waiting for events to settle, and
5665 // checking for consistency.
5766 final changer = FileChanger (temp.path);
5867 for (var i = 0 ; endlessMode || i != 40 ; ++ i) {
68+ final runSeed = seed ?? i;
69+ log.clear ();
5970 if (endlessMode) stdout.write ('.' );
6071 for (final entity in temp.listSync ()) {
6172 entity.deleteSync (recursive: true );
6273 }
6374 // File changes.
64- final messages = await changer.changeFiles (times: 200 );
75+ log. addAll ( await changer.changeFiles (times: 200 , seed : runSeed) );
6576
6677 // Give time for events to arrive. To allow tests to run quickly when the
6778 // events are handled quickly, poll and continue if verification passes.
@@ -80,24 +91,30 @@ Future<void> _runTest({
8091 client.verify (printOnFailure: printOnFailure);
8192 // Write the file operations before the failure to a log, fail the test.
8293 final logTemp = Directory .systemTemp.createTempSync ();
83- final fileChangesLogPath = p.join (logTemp.path, 'changes .txt' );
84- File (fileChangesLogPath)
85- . writeAsStringSync (messages. map ((m) => '$ m \n ' ). join ( '' ));
86- final clientLogPath = p. join (logTemp.path, 'client.txt' );
87- File (clientLogPath)
88- .writeAsStringSync (client.messages .map ((m) => '$m \n ' ).join ('' ));
94+ final logPath = p.join (logTemp.path, 'log .txt' );
95+
96+ // Sort the log entries by timestamp.
97+ log. sort ( );
98+
99+ File (logPath) .writeAsStringSync (log .map ((m) => '$m \n ' ).join ('' ));
89100 fail ('''
90- Failed on run $i .
91- Files changes: $fileChangesLogPath
92- Client log: $clientLogPath ''' );
101+ Failed on run $i , seed $runSeed . Run in a loop with that seed using:
102+
103+ dart test/directory_watcher/end_to_end_tests.dart $runSeed
104+
105+ Changes/watcher/client log: $logPath
106+ ''' );
93107 }
94108 }
95109}
96110
97111/// Main method for running the e2e test without `package:test` .
98112///
113+ /// Optionally, pass the seed to run with as the only argument.
114+ ///
99115/// Exits on failure, or runs forever.
100- Future <void > main () async {
116+ Future <void > main (List <String > arguments) async {
117+ final seed = arguments.isNotEmpty ? int .parse (arguments.first) : null ;
101118 final teardowns = < void Function ()> [];
102119 try {
103120 await _runTest (
@@ -109,10 +126,28 @@ Future<void> main() async {
109126 },
110127 printOnFailure: print,
111128 endlessMode: true ,
129+ seed: seed,
112130 );
113131 } finally {
114132 for (final teardown in teardowns) {
115133 teardown ();
116134 }
117135 }
118136}
137+
138+ /// Log entry with timestamp.
139+ ///
140+ /// Because file events happen on a different isolate the merged log uses
141+ /// timestamps to put entries in the correct order.
142+ class LogEntry implements Comparable <LogEntry > {
143+ final DateTime timestamp;
144+ final String message;
145+
146+ LogEntry (this .message) : timestamp = DateTime .now ();
147+
148+ @override
149+ int compareTo (LogEntry other) => timestamp.compareTo (other.timestamp);
150+
151+ @override
152+ String toString () => message;
153+ }
0 commit comments