| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:clock/clock.dart'; |
| import 'package:fake_async/fake_async.dart'; |
| import 'package:test/test.dart'; |
| import 'package:watcher/src/event_batching.dart'; |
| import 'package:watcher/src/testing.dart'; |
| |
| void main() { |
| group('batchAndConvertEvents', () { |
| setUp(() { |
| overridableDateTimeNow = () => clock.now(); |
| }); |
| tearDown(() { |
| overridableDateTimeNow = DateTime.now; |
| }); |
| |
| group('without buffering', () { |
| test('splits into expected batches', () { |
| var expectationsRan = false; |
| fakeAsync((async) { |
| final controller = StreamController<FileSystemEvent>(); |
| final stream = |
| controller.stream.batchNearbyMicrotasksAndConvertEvents(); |
| final batchesFuture = stream.toList(); |
| |
| // Send events in ten batches of size 1, 2, 3, ..., 10. |
| for (var i = 0; i != 10; ++i) { |
| for (var j = 0; j != i + 1; ++j) { |
| controller.add(FileSystemCreateEvent('$i,$j', true)); |
| } |
| async.elapse(const Duration(milliseconds: 1)); |
| } |
| |
| controller.close(); |
| batchesFuture.then((batches) { |
| // Check for the exact expected batches. |
| for (var i = 0; i != 10; ++i) { |
| expect(batches[i].length, i + 1); |
| for (var j = 0; j != i + 1; ++j) { |
| expect(batches[i][j].path, '$i,$j'); |
| } |
| } |
| expectationsRan = true; |
| }); |
| |
| // Cause `batchesFuture` to complete. |
| async.flushMicrotasks(); |
| }); |
| |
| // Expectations are at the end of a fake async future, check it actually |
| // completed. |
| expect(expectationsRan, true); |
| }); |
| }); |
| |
| group('buffered by path', () { |
| test('splits into expected batches', () { |
| var expecationsRan = false; |
| fakeAsync((async) { |
| final controller = StreamController<FileSystemEvent>(); |
| final stream = controller.stream.batchBufferedByPathAndConvertEvents( |
| duration: const Duration(milliseconds: 10)); |
| final batchesFuture = stream.toList(); |
| |
| controller.add(FileSystemCreateEvent('1', true)); |
| controller.add(FileSystemCreateEvent('2', true)); |
| |
| // Don't send "2" again, it should be emitted. |
| async.elapse(const Duration(milliseconds: 10)); |
| controller.add(FileSystemCreateEvent('1', true)); |
| controller.add(FileSystemCreateEvent('3', true)); |
| controller.add(FileSystemCreateEvent('4', true)); |
| controller.add(FileSystemCreateEvent('5', true)); |
| |
| // Don't send "1", "3" or "4" again, they should be emitted. |
| async.elapse(const Duration(milliseconds: 10)); |
| controller.add(FileSystemCreateEvent('5', true)); |
| controller.add(FileSystemCreateEvent('6', true)); |
| controller.add(FileSystemCreateEvent('7', true)); |
| controller.add(FileSystemCreateEvent('8', true)); |
| controller.add(FileSystemCreateEvent('9', true)); |
| controller.add(FileSystemCreateEvent('10', true)); |
| |
| // Everything except "9" and "10" should be emitted. |
| async.elapse(const Duration(milliseconds: 10)); |
| controller.add(FileSystemCreateEvent('9', true)); |
| controller.add(FileSystemCreateEvent('10', true)); |
| |
| // Close of the controller should force emit of "9" with the "10". |
| async.elapse(const Duration(milliseconds: 10)); |
| controller.add(FileSystemCreateEvent('9', true)); |
| |
| controller.close(); |
| batchesFuture.then((batches) { |
| expect(batches.map((b) => b.map((e) => e.path).toList()).toList(), [ |
| ['2'], |
| ['1', '1', '3', '4'], |
| ['5', '5', '6', '7', '8'], |
| ['9', '9', '9', '10', '10'], |
| ]); |
| expecationsRan = true; |
| }); |
| |
| // Cause `batchesFuture` to complete. |
| async.flushMicrotasks(); |
| }); |
| |
| // Expectations are at the end of a fake async future, check it actually |
| // completed. |
| expect(expecationsRan, true); |
| }); |
| |
| test('continues batching after pause', () async { |
| var expectationsRan = false; |
| |
| fakeAsync((async) { |
| final controller = StreamController<FileSystemEvent>(); |
| final stream = controller.stream.batchBufferedByPathAndConvertEvents( |
| duration: const Duration(milliseconds: 5)); |
| final batchesFuture = stream.toList(); |
| |
| controller.add(FileSystemCreateEvent('1', true)); |
| async.elapse(const Duration(milliseconds: 2)); |
| controller.add(FileSystemCreateEvent('1', true)); |
| async.elapse(const Duration(milliseconds: 10)); |
| controller.add(FileSystemCreateEvent('2', true)); |
| async.elapse(const Duration(milliseconds: 2)); |
| controller.add(FileSystemCreateEvent('2', true)); |
| async.elapse(const Duration(milliseconds: 10)); |
| |
| controller.close(); |
| batchesFuture.then((batches) { |
| expect(batches.map((b) => b.map((e) => e.path).toList()).toList(), [ |
| ['1', '1'], |
| ['2', '2'], |
| ]); |
| expectationsRan = true; |
| }); |
| |
| // Cause `batchesFuture` to complete. |
| async.flushMicrotasks(); |
| }); |
| |
| // Expectations are at the end of a fake async future, check it actually |
| // completed. |
| expect(expectationsRan, true); |
| }); |
| |
| test('converts moves into separate create and delete', |
| // Move events aren't used on MacOS, so the `Event` conversion rejects |
| // them. |
| skip: Platform.isMacOS, () { |
| var expectationsRan = false; |
| |
| fakeAsync((async) { |
| final controller = StreamController<FileSystemEvent>(); |
| final stream = controller.stream.batchBufferedByPathAndConvertEvents( |
| duration: const Duration(milliseconds: 50)); |
| final batchesFuture = stream.toList(); |
| |
| // Delete of a, delete of b, create of b, create of c should end up in |
| // one batch. |
| controller.add(FileSystemMoveEvent('a', false, 'b')); |
| async.elapse(const Duration(milliseconds: 1)); |
| controller.add(FileSystemMoveEvent('b', false, 'c')); |
| |
| // Then a second batch with delete of c, create of d. |
| async.elapse(const Duration(milliseconds: 100)); |
| controller.add(FileSystemMoveEvent('c', false, 'd')); |
| |
| controller.close(); |
| batchesFuture.then((batches) { |
| expect( |
| batches |
| .map((b) => |
| b.map((e) => '${e.runtimeType} ${e.path}').toList()) |
| .toList(), |
| [ |
| { |
| 'FileSystemCreateEvent b', |
| 'FileSystemCreateEvent c', |
| 'FileSystemDeleteEvent a', |
| 'FileSystemDeleteEvent b', |
| }, |
| { |
| 'FileSystemCreateEvent d', |
| 'FileSystemDeleteEvent c', |
| }, |
| ]); |
| expectationsRan = true; |
| }); |
| |
| // Cause `batchesFuture` to complete. |
| async.flushMicrotasks(); |
| }); |
| |
| // Expectations are at the end of a fake async future, check it actually |
| // completed. |
| expect(expectationsRan, true); |
| }); |
| }); |
| }); |
| } |