diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa4a10..7d11090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ +## 0.4.0 + +* Added + * `stop` method in `SimContext`. + * Error handling. + ## 0.3.0 * Access the list of created resources. * Added * `runState` getter to determine whether the simulation has not started, is running, or has completed. * `stop` method to manually stop the simulation before it completes. - * Observer to receive data throughout the simulation. + * Listener to receive data throughout the simulation. ## 0.2.0 diff --git a/example/example.dart b/example/example.dart index f6cb715..79d5c73 100644 --- a/example/example.dart +++ b/example/example.dart @@ -1,7 +1,9 @@ +import 'dart:async'; + import 'package:simdart/simdart.dart'; void main() async { - final SimDart sim = SimDart(observer: ConsoleEventObserver()); + final SimDart sim = SimDart(listener: ConsoleEventListener()); sim.process(event: _eventA, name: 'A'); diff --git a/lib/simdart.dart b/lib/simdart.dart index df4118c..13f47e5 100644 --- a/lib/simdart.dart +++ b/lib/simdart.dart @@ -1,7 +1,7 @@ export 'src/event.dart'; export 'src/interval.dart'; export 'src/observable.dart'; -export 'src/sim_observer.dart'; +export 'src/sim_listener.dart'; export 'src/event_phase.dart'; export 'src/resources.dart' hide ResourcesFactory, ResourceStore; export 'src/simdart.dart' hide SimDartHelper; diff --git a/lib/src/internal/completer_action.dart b/lib/src/internal/completer_action.dart index 0446da3..2656717 100644 --- a/lib/src/internal/completer_action.dart +++ b/lib/src/internal/completer_action.dart @@ -12,7 +12,4 @@ class CompleterAction extends TimeAction { void execute() { complete.call(); } - - @override - void dispose() {} } diff --git a/lib/src/internal/completer_interrupt.dart b/lib/src/internal/completer_interrupt.dart new file mode 100644 index 0000000..d1dea51 --- /dev/null +++ b/lib/src/internal/completer_interrupt.dart @@ -0,0 +1,4 @@ +import 'package:meta/meta.dart'; + +@internal +class CompleterInterrupt {} diff --git a/lib/src/internal/debug_listener.dart b/lib/src/internal/debug_listener.dart new file mode 100644 index 0000000..21845bf --- /dev/null +++ b/lib/src/internal/debug_listener.dart @@ -0,0 +1,8 @@ +import 'package:meta/meta.dart'; + +@internal +abstract class DebugListener { + void onAddCompleter(); + + void onRemoveCompleter(); +} diff --git a/lib/src/internal/event_action.dart b/lib/src/internal/event_action.dart index b284f63..f754556 100644 --- a/lib/src/internal/event_action.dart +++ b/lib/src/internal/event_action.dart @@ -4,6 +4,8 @@ import 'package:meta/meta.dart'; import 'package:simdart/src/event.dart'; import 'package:simdart/src/event_phase.dart'; import 'package:simdart/src/internal/completer_action.dart'; +import 'package:simdart/src/internal/completer_interrupt.dart'; +import 'package:simdart/src/internal/stop_action.dart'; import 'package:simdart/src/internal/time_action.dart'; import 'package:simdart/src/interval.dart'; import 'package:simdart/src/resources.dart'; @@ -52,46 +54,49 @@ class EventAction extends TimeAction implements SimContext { throw StateError('This event is yielding'); } - sim.observer?.onEvent( + sim.listener?.onEvent( name: eventName, time: sim.now, phase: EventPhase.called, executionHash: hashCode); - _runEvent().then((_) { - if (_eventCompleter != null) { + event(this).then((_) { + if (_eventCompleter != null && sim.runState == RunState.running) { + SimDartHelper.removeCompleter( + sim: sim, completer: _eventCompleter!.completer); SimDartHelper.error( sim: sim, - msg: - "Next event is being scheduled, but the current one is still paused waiting for continuation. Did you forget to use 'await'?"); + error: StateError( + "Next event is being scheduled, but the current one is still paused waiting for continuation. Did you forget to use 'await'?")); return; } - sim.observer?.onEvent( + sim.listener?.onEvent( name: eventName, time: sim.now, phase: EventPhase.finished, executionHash: hashCode); - SimDartHelper.scheduleNextAction(sim: sim); - }).catchError((e) { - // Sim already marked to finish. Let the last event finalize. + }).catchError((error) { + if (error is! CompleterInterrupt) { + SimDartHelper.error(sim: sim, error: error); + SimDartHelper.scheduleNextAction(sim: sim); + } }); } - Future _runEvent() async { - await event(this); - } - @override Future wait(int delay) async { if (_eventCompleter != null) { + SimDartHelper.removeCompleter( + sim: sim, completer: _eventCompleter!.completer); SimDartHelper.error( sim: sim, - msg: "The event is already waiting. Did you forget to use 'await'?"); + error: StateError( + "The event is already waiting. Did you forget to use 'await'?")); return; } - sim.observer?.onEvent( + sim.listener?.onEvent( name: eventName, time: sim.now, phase: EventPhase.yielded, @@ -106,8 +111,8 @@ class EventAction extends TimeAction implements SimContext { start: sim.now + delay, complete: _eventCompleter!.complete, order: order)); - SimDartHelper.scheduleNextAction(sim: sim); + SimDartHelper.scheduleNextAction(sim: sim); await _eventCompleter!.future; } @@ -133,6 +138,12 @@ class EventAction extends TimeAction implements SimContext { name: name); } + @override + void stop() { + SimDartHelper.addAction( + sim: sim, action: StopAction(start: sim.now, sim: sim)); + } + @override SimCounter counter(String name) { return sim.counter(name); @@ -142,29 +153,29 @@ class EventAction extends TimeAction implements SimContext { SimNum num(String name) { return sim.num(name); } - - @override - void dispose() { - _eventCompleter?.complete(); - } } class EventCompleter { - EventCompleter({required this.event}); + EventCompleter({required this.event}) { + SimDartHelper.addCompleter(sim: event.sim, completer: completer); + } - final Completer _completer = Completer(); + final Completer completer = Completer(); final EventAction event; - Future get future => _completer.future; + Future get future { + return completer.future; + } void complete() { - event.sim.observer?.onEvent( + event.sim.listener?.onEvent( name: event.eventName, time: event.sim.now, phase: EventPhase.resumed, executionHash: hashCode); - _completer.complete(); + completer.complete(); event._eventCompleter = null; + SimDartHelper.removeCompleter(sim: event.sim, completer: completer); } } diff --git a/lib/src/internal/repeat_event_action.dart b/lib/src/internal/repeat_event_action.dart index d0ef6c2..91a3ee7 100644 --- a/lib/src/internal/repeat_event_action.dart +++ b/lib/src/internal/repeat_event_action.dart @@ -45,7 +45,4 @@ class RepeatEventAction extends TimeAction { } SimDartHelper.scheduleNextAction(sim: sim); } - - @override - void dispose() {} } diff --git a/lib/src/internal/stop_action.dart b/lib/src/internal/stop_action.dart new file mode 100644 index 0000000..b14162c --- /dev/null +++ b/lib/src/internal/stop_action.dart @@ -0,0 +1,16 @@ +import 'package:meta/meta.dart'; +import 'package:simdart/src/internal/time_action.dart'; +import 'package:simdart/src/simdart.dart'; + +@internal +class StopAction extends TimeAction { + StopAction({required super.start, super.order = -1, required this.sim}); + + final SimDart sim; + + @override + void execute() { + SimDartHelper.stop(sim: sim); + SimDartHelper.scheduleNextAction(sim: sim); + } +} diff --git a/lib/src/internal/time_action.dart b/lib/src/internal/time_action.dart index 3d00727..e8583a8 100644 --- a/lib/src/internal/time_action.dart +++ b/lib/src/internal/time_action.dart @@ -14,6 +14,4 @@ abstract class TimeAction { final int order; void execute(); - - void dispose(); } diff --git a/lib/src/resources.dart b/lib/src/resources.dart index a774eb2..ce01e65 100644 --- a/lib/src/resources.dart +++ b/lib/src/resources.dart @@ -153,17 +153,20 @@ class ResourcesContext extends Resources { /// - Returns: A [Future] that completes when the resource is acquired. Future acquire(String name) async { if (_event.eventCompleter != null) { + SimDartHelper.removeCompleter( + sim: _sim, completer: _event.eventCompleter!.completer); + //TODO method or throw? SimDartHelper.error( sim: _sim, - msg: - "This event should be waiting for the resource to be released. Did you forget to use 'await'?"); + error: StateError( + "This event should be waiting. Did you forget to use 'await'?")); return; } _ResourceImpl? resource = _store._map[name]; if (resource != null) { bool acquired = resource.acquire(_event); if (!acquired) { - _sim.observer?.onEvent( + _sim.listener?.onEvent( name: _event.eventName, time: _sim.now, phase: EventPhase.yielded, @@ -185,14 +188,15 @@ class ResourcesContext extends Resources { if (resource != null) { if (resource.release(_sim, _event)) { if (resource._waiting.isNotEmpty) { - EventAction other = resource._waiting.removeAt(0); + EventAction waitingEvent = resource._waiting.removeAt(0); // Schedule a complete to resume this event in the future. SimDartHelper.addAction( sim: _sim, action: CompleterAction( start: _sim.now, - complete: other.eventCompleter!.complete, - order: other.order)); + complete: waitingEvent.eventCompleter!.complete, + order: waitingEvent.order)); + //TODO need? SimDartHelper.scheduleNextAction(sim: _sim); } } diff --git a/lib/src/sim_context.dart b/lib/src/sim_context.dart index bc16b99..13c8cf8 100644 --- a/lib/src/sim_context.dart +++ b/lib/src/sim_context.dart @@ -13,4 +13,6 @@ abstract interface class SimContext implements SimDartInterface { String get eventName; ResourcesContext get resources; + + void stop(); } diff --git a/lib/src/sim_observer.dart b/lib/src/sim_listener.dart similarity index 80% rename from lib/src/sim_observer.dart rename to lib/src/sim_listener.dart index ebde3b9..fc7ddea 100644 --- a/lib/src/sim_observer.dart +++ b/lib/src/sim_listener.dart @@ -1,7 +1,7 @@ import 'package:simdart/src/event_phase.dart'; -/// A base class for observing simulation. -abstract class SimObserver { +/// A base class for listening simulation. +abstract class SimListener { /// Called when there is a change in resource usage. /// /// [name] - The name of the resource whose usage is being reported. @@ -21,11 +21,15 @@ abstract class SimObserver { required int executionHash}); void onStart() {} + + void onError(String error); } -mixin SimObserverMixin implements SimObserver { +mixin SimListenerMixin implements SimListener { @override void onStart() {} + @override + void onError(String error) {} //@override //void onResourceUsage({required String name, required double usage}) {} @override @@ -36,8 +40,8 @@ mixin SimObserverMixin implements SimObserver { required int executionHash}) {} } -class ConsoleEventObserver with SimObserverMixin { - ConsoleEventObserver(); +class ConsoleEventListener with SimListenerMixin { + ConsoleEventListener(); @override void onEvent( @@ -47,4 +51,9 @@ class ConsoleEventObserver with SimObserverMixin { required int executionHash}) { print('[time:$time][event:$name][phase:${phase.name}]'); } + + @override + void onError(String error) { + print('Error: $error'); + } } diff --git a/lib/src/simdart.dart b/lib/src/simdart.dart index a5d96a0..d93d9c7 100644 --- a/lib/src/simdart.dart +++ b/lib/src/simdart.dart @@ -4,15 +4,18 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:simdart/src/event.dart'; +import 'package:simdart/src/internal/completer_interrupt.dart'; +import 'package:simdart/src/internal/debug_listener.dart'; import 'package:simdart/src/internal/event_action.dart'; import 'package:simdart/src/internal/repeat_event_action.dart'; import 'package:simdart/src/internal/simdart_interface.dart'; +import 'package:simdart/src/internal/stop_action.dart'; import 'package:simdart/src/internal/time_action.dart'; import 'package:simdart/src/interval.dart'; import 'package:simdart/src/resources.dart'; import 'package:simdart/src/sim_counter.dart'; import 'package:simdart/src/sim_num.dart'; -import 'package:simdart/src/sim_observer.dart'; +import 'package:simdart/src/sim_listener.dart'; import 'package:simdart/src/sim_result.dart'; import 'package:simdart/src/start_time_handling.dart'; @@ -37,7 +40,7 @@ class SimDart implements SimDartInterface { SimDart( {this.startTimeHandling = StartTimeHandling.throwErrorIfPast, int now = 0, - this.observer, + this.listener, this.executionPriority = 0, int? seed}) : random = Random(seed), @@ -46,7 +49,9 @@ class SimDart implements SimDartInterface { RunState _runState = RunState.notStarted; RunState get runState => _runState; - final SimObserver? observer; + DebugListener? _debugListener; + + final SimListener? listener; final Map _numProperties = {}; final Map _counterProperties = {}; @@ -94,9 +99,11 @@ class SimDart implements SimDartInterface { int get now => _now; late int _now; + final List> _completerList = []; + final Completer _terminator = Completer(); - bool _error = false; + Object? _error; /// Runs the simulation, processing events in chronological order. /// @@ -108,7 +115,7 @@ class SimDart implements SimDartInterface { } _runState = RunState.running; - observer?.onStart(); + listener?.onStart(); if (until != null && _now > until) { throw ArgumentError('`now` must be less than or equal to `until`.'); } @@ -118,19 +125,34 @@ class SimDart implements SimDartInterface { if (_actions.isEmpty) { _startTime = 0; + _runState = RunState.finished; } else { _startTime = null; _scheduleNextAction(); await _terminator.future; + if (_error != null) { + _runState = RunState.error; + throw _error!; + } else { + _runState = RunState.finished; + } _duration = _now - (_startTime ?? 0); } - _runState = RunState.finished; return _buildResult(); } void stop() { - if (!_terminator.isCompleted) { - _terminator.complete(); + _addAction(StopAction(start: now, sim: this)); + } + + void _disposeCompleterList() { + while (_completerList.isNotEmpty) { + Completer completer = _completerList.removeAt(0); + _debugListener?.onRemoveCompleter(); + if (!completer.isCompleted) { + // prevents subsequent methods from being executed after complete inside the async method. + completer.completeError(CompleterInterrupt()); + } } } @@ -207,36 +229,31 @@ class SimDart implements SimDartInterface { } void _scheduleNextAction() { - if (_error) { - return; - } if (!_nextActionScheduled) { _nextActionScheduled = true; if (executionPriority == 0 || _executionCount < executionPriority) { _executionCount++; - Future.microtask(_consumeNextAction); + Future.microtask(_nextAction); } else { _executionCount = 0; - Future.delayed(Duration.zero, _consumeNextAction); + Future.delayed(Duration.zero, _nextAction); } } } void _addAction(TimeAction action) { - if (_error) { - return; + if (_runState == RunState.running || _runState == RunState.notStarted) { + _actions.add(action); } - _actions.add(action); } - Future _consumeNextAction() async { - if (_error || _terminator.isCompleted) { - return; - } - + Future _nextAction() async { _nextActionScheduled = false; - if (_actions.isEmpty) { - _terminator.complete(); + if (_actions.isEmpty || runState != RunState.running) { + _disposeCompleterList(); + if (!_terminator.isCompleted) { + _terminator.complete(); + } return; } @@ -260,7 +277,7 @@ class SimDart implements SimDartInterface { } } -enum RunState { notStarted, running, finished } +enum RunState { notStarted, running, finished, stopped, error } typedef StopCondition = bool Function(SimDart sim); @@ -274,19 +291,38 @@ class SimDartHelper { sim._addAction(action); } + static void scheduleNextAction({required SimDart sim}) { + sim._scheduleNextAction(); + } + static ResourceStore resourceStore({required SimDart sim}) => sim._resourceStore; - static void scheduleNextAction({required SimDart sim}) { - sim._scheduleNextAction(); + static void addCompleter( + {required SimDart sim, required Completer completer}) { + sim._completerList.add(completer); + sim._debugListener?.onAddCompleter(); + } + + static void removeCompleter( + {required SimDart sim, required Completer completer}) { + sim._completerList.remove(completer); + sim._debugListener?.onRemoveCompleter(); } - static void error({required SimDart sim, required String msg}) { - sim._error = true; - while (sim._actions.isNotEmpty) { - TimeAction action = sim._actions.removeFirst(); - action.dispose(); + static void error({required SimDart sim, required Object error}) { + if (sim._error == null) { + sim._error = error; + sim._runState = RunState.error; } - sim._terminator.completeError(StateError(msg)); + } + + static void setDebugListener( + {required SimDart sim, required DebugListener? listener}) { + sim._debugListener = listener; + } + + static void stop({required SimDart sim}) { + sim._runState = RunState.stopped; } } diff --git a/pubspec.yaml b/pubspec.yaml index 8247363..7e2d621 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: simdart description: A discrete event simulation package for Dart, designed for modeling and analyzing processes and systems. -version: 0.3.0 +version: 0.4.0 repository: https://github.com/SimDart/simdart topics: diff --git a/test/error_test.dart b/test/error_test.dart new file mode 100644 index 0000000..58163a1 --- /dev/null +++ b/test/error_test.dart @@ -0,0 +1,95 @@ +import 'package:simdart/src/simdart.dart'; +import 'package:test/test.dart'; + +import 'test_helper.dart'; + +void main() { + late SimDart sim; + TestHelper helper = TestHelper(); + + setUp(() { + sim = SimDart(listener: helper); + SimDartHelper.setDebugListener(sim: sim, listener: helper); + }); + + group('Error', () { + test('Simple', () async { + sim.process( + event: (context) async { + throw 'ERROR'; + }, + name: 'a'); + sim.process(event: (context) async {}, name: 'b'); + + await expectLater(sim.run(), throwsA(equals('ERROR'))); + + helper.testEvents(['[0][a][called]']); + expect(helper.completerCount, 0); + }); + + test('Wait', () async { + sim.process( + event: (context) async { + context.counter('counter').inc(); + await context.wait(10); + context.counter('counter').inc(); + }, + name: 'a'); + sim.process( + delay: 1, + event: (context) async { + throw 'ERROR'; + }, + name: 'b'); + sim.process(delay: 2, event: (context) async {}, name: 'c'); + + await expectLater(sim.run(), throwsA(equals('ERROR'))); + + helper + .testEvents(['[0][a][called]', '[0][a][yielded]', '[1][b][called]']); + expect(sim.counter('counter').value, 1); + expect(helper.completerCount, 0); + }); + + test('Resource', () async { + sim.resources.limited(name: 'resource'); + sim.process( + event: (context) async { + await context.resources.acquire('resource'); + context.counter('counter1').inc(); + await context.wait(10); + context.counter('counter1').inc(); + context.resources.release('resource'); + }, + name: 'a'); + sim.process( + delay: 1, + event: (context) async { + context.counter('counter2').inc(); + await context.resources.acquire('resource'); + context.counter('counter2').inc(); + }, + name: 'b'); + sim.process( + delay: 2, + event: (context) async { + throw 'ERROR'; + }, + name: 'c'); + sim.process(delay: 3, event: (context) async {}, name: 'd'); + + await expectLater(sim.run(), throwsA(equals('ERROR'))); + + helper.testEvents([ + '[0][a][called]', + '[0][a][yielded]', + '[1][b][called]', + '[1][b][yielded]', + '[2][c][called]' + ]); + expect(sim.counter('counter1').value, 1); + expect(sim.counter('counter2').value, 1); + expect(helper.completerCount, 0); + }); + }); +} diff --git a/test/process_test.dart b/test/process_test.dart index 9a56a5f..f8d69b0 100644 --- a/test/process_test.dart +++ b/test/process_test.dart @@ -5,40 +5,35 @@ import 'test_helper.dart'; Future emptyEvent(SimContext context) async {} -Future loopEvent(SimContext context) async { - context.process(event: loopEvent); -} - void main() { late SimDart sim; TestHelper helper = TestHelper(); setUp(() { - helper.afterOnFinishedEvent = null; // priority to allow test timeout - sim = SimDart(executionPriority: 5, observer: helper); + sim = SimDart(executionPriority: 5, listener: helper); }); group('Process', () { test('start 1', () async { sim.process(event: emptyEvent, name: 'a'); await sim.run(); - helper.test(['[0][a][called]', '[0][a][finished]']); + helper.testEvents(['[0][a][called]', '[0][a][finished]']); }); test('start 2', () async { sim.process(event: emptyEvent, start: 10, name: 'a'); await sim.run(); - helper.test(['[10][a][called]', '[10][a][finished]']); + helper.testEvents(['[10][a][called]', '[10][a][finished]']); }); test('delay 1', () async { sim.process(event: emptyEvent, delay: 0, name: 'a'); await sim.run(); - helper.test(['[0][a][called]', '[0][a][finished]']); + helper.testEvents(['[0][a][called]', '[0][a][finished]']); }); test('delay 2', () async { sim.process(event: emptyEvent, delay: 10, name: 'a'); await sim.run(); - helper.test(['[10][a][called]', '[10][a][finished]']); + helper.testEvents(['[10][a][called]', '[10][a][finished]']); }); test('delay 3', () async { sim.process( @@ -48,7 +43,7 @@ void main() { start: 5, name: 'a'); await sim.run(); - helper.test([ + helper.testEvents([ '[5][a][called]', '[5][a][finished]', '[15][b][called]', @@ -69,7 +64,7 @@ void main() { start: 2, name: 'c'); await sim.run(); - helper.test([ + helper.testEvents([ '[0][a][called]', '[0][a][finished]', '[2][c][called]', @@ -80,11 +75,5 @@ void main() { '[10][b][finished]' ]); }); - test('stop', timeout: Timeout(Duration(seconds: 2)), () async { - helper.afterOnFinishedEvent = () => sim.stop(); - sim.process(event: loopEvent, name: 'a'); - await sim.run(); - helper.test(['[0][a][called]', '[0][a][finished]']); - }); }); } diff --git a/test/repeat_process_test.dart b/test/repeat_process_test.dart index f12021e..dc6884b 100644 --- a/test/repeat_process_test.dart +++ b/test/repeat_process_test.dart @@ -8,7 +8,7 @@ void main() { TestHelper helper = TestHelper(); setUp(() { - sim = SimDart(observer: helper); + sim = SimDart(listener: helper); }); group('Repeat process', () { @@ -19,7 +19,7 @@ void main() { interval: Interval.fixed(fixedInterval: 1, untilTime: 2)); await sim.run(); - helper.test([ + helper.testEvents([ '[0][A0][called]', '[0][A0][finished]', '[1][A1][called]', @@ -38,7 +38,7 @@ void main() { interval: Interval.fixed(fixedInterval: 1, untilTime: 2)); await sim.run(); - helper.test([ + helper.testEvents([ '[0][A0][called]', '[0][A0][yielded]', '[1][A0][resumed]', @@ -73,7 +73,7 @@ void main() { await sim.run(); - helper.test([ + helper.testEvents([ '[0][A][called]', '[0][A][yielded]', '[0][B][called]', @@ -99,7 +99,7 @@ void main() { await sim.run(); - helper.test([ + helper.testEvents([ '[0][A0][called]', '[0][A0][yielded]', '[1][A1][called]', @@ -134,7 +134,7 @@ void main() { await sim.run(); - helper.test([ + helper.testEvents([ '[0][A0][called]', '[0][A0][yielded]', '[2][A0][resumed]', diff --git a/test/resource_test.dart b/test/resource_test.dart index 4fe6b9e..a2c19cc 100644 --- a/test/resource_test.dart +++ b/test/resource_test.dart @@ -8,7 +8,7 @@ void main() { TestHelper helper = TestHelper(); setUp(() { - sim = SimDart(observer: helper); + sim = SimDart(listener: helper); }); group('Resource', () { @@ -39,7 +39,7 @@ void main() { await sim.run(); - helper.test([ + helper.testEvents([ '[0][A][called]', '[0][A][yielded]', '[0][B][called]', @@ -73,7 +73,7 @@ void main() { await sim.run(); - helper.test([ + helper.testEvents([ '[0][A][called]', '[0][A][yielded]', '[0][B][called]', @@ -110,7 +110,7 @@ void main() { await sim.run(); - helper.test([ + helper.testEvents([ '[0][A][called]', '[0][A][yielded]', '[1][B][called]', @@ -129,27 +129,25 @@ void main() { '[20][C][finished]' ]); }); + test('without await', () async { - expect( - () async { - sim.resources.limited(name: 'r', capacity: 1); - sim.process( - event: (context) async { - context.resources.acquire('r'); // acquired - context.resources.acquire('r'); // should await - context.resources.acquire('r'); // error - }, - name: 'a'); - sim.process(event: (context) async {}); - await sim.run(); - }, - throwsA( - predicate((e) => - e is StateError && - e.message.contains( - "This event should be waiting for the resource to be released. Did you forget to use 'await'?")), - ), - ); + expect(() async { + sim.resources.limited(name: 'r', capacity: 1); + sim.process( + event: (context) async { + context.resources.acquire('r'); // acquired + context.resources.acquire('r'); // should await + context.resources.acquire('r'); // error + }, + name: 'a'); + sim.process(event: (context) async {}); + await sim.run(); + }, + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "This event should be waiting. Did you forget to use 'await'?")))); }); }); } diff --git a/test/stop_test.dart b/test/stop_test.dart new file mode 100644 index 0000000..d9a2b86 --- /dev/null +++ b/test/stop_test.dart @@ -0,0 +1,97 @@ +import 'package:simdart/src/simdart.dart'; +import 'package:test/test.dart'; + +import 'test_helper.dart'; + +void main() { + late SimDart sim; + TestHelper helper = TestHelper(); + + setUp(() { + sim = SimDart(listener: helper); + SimDartHelper.setDebugListener(sim: sim, listener: helper); + }); + + group('Stop', () { + test('Simple', () async { + sim.process( + event: (context) async { + context.stop(); + }, + name: 'a'); + sim.process(event: (context) async {}, name: 'b'); + + await sim.run(); + helper.testEvents(['[0][a][called]', '[0][a][finished]']); + expect(helper.completerCount, 0); + }); + + test('Wait', () async { + sim.process( + event: (context) async { + context.counter('counter').inc(); + await context.wait(10); + context.counter('counter').inc(); + }, + name: 'a'); + sim.process( + delay: 1, + event: (context) async { + context.stop(); + }, + name: 'b'); + sim.process(delay: 2, event: (context) async {}, name: 'c'); + await sim.run(); + + helper.testEvents([ + '[0][a][called]', + '[0][a][yielded]', + '[1][b][called]', + '[1][b][finished]' + ]); + expect(sim.counter('counter').value, 1); + expect(helper.completerCount, 0); + }); + + test('Resource', () async { + sim.resources.limited(name: 'resource'); + sim.process( + event: (context) async { + await context.resources.acquire('resource'); + context.counter('counter1').inc(); + await context.wait(10); + context.counter('counter1').inc(); + context.resources.release('resource'); + }, + name: 'a'); + sim.process( + delay: 1, + event: (context) async { + context.counter('counter2').inc(); + await context.resources.acquire('resource'); + context.counter('counter2').inc(); + }, + name: 'b'); + sim.process( + delay: 2, + event: (context) async { + context.stop(); + }, + name: 'c'); + sim.process(delay: 3, event: (context) async {}, name: 'd'); + await sim.run(); + + helper.testEvents([ + '[0][a][called]', + '[0][a][yielded]', + '[1][b][called]', + '[1][b][yielded]', + '[2][c][called]', + '[2][c][finished]' + ]); + expect(sim.counter('counter1').value, 1); + expect(sim.counter('counter2').value, 1); + expect(helper.completerCount, 0); + }); + }); +} diff --git a/test/test_helper.dart b/test/test_helper.dart index 9e2e410..280c5b4 100644 --- a/test/test_helper.dart +++ b/test/test_helper.dart @@ -1,16 +1,17 @@ import 'package:simdart/src/event_phase.dart'; -import 'package:simdart/src/sim_observer.dart'; +import 'package:simdart/src/internal/debug_listener.dart'; +import 'package:simdart/src/sim_listener.dart'; import 'package:test/expect.dart'; -class TestHelper with SimObserverMixin { +class TestHelper with SimListenerMixin implements DebugListener { final List _events = []; int get length => _events.length; + int _completerCount = 0; + int get completerCount => _completerCount; - Function? afterOnFinishedEvent; - - void test(List events) { - expect(events, _events); + void testEvents(List events) { + expect(_events, events); } @override @@ -20,13 +21,21 @@ class TestHelper with SimObserverMixin { required EventPhase phase, required int executionHash}) { _events.add('[$time][$name][${phase.name}]'); - if (phase == EventPhase.finished) { - afterOnFinishedEvent?.call(); - } } @override void onStart() { + _completerCount = 0; _events.clear(); } + + @override + void onAddCompleter() { + _completerCount++; + } + + @override + void onRemoveCompleter() { + _completerCount--; + } } diff --git a/test/time_loop_test.dart b/test/time_loop_test.dart index 81977ca..3fa8dd1 100644 --- a/test/time_loop_test.dart +++ b/test/time_loop_test.dart @@ -19,9 +19,6 @@ class TestAction extends TimeAction { names.add(name); SimDartHelper.scheduleNextAction(sim: sim); } - - @override - void dispose() {} } void main() { diff --git a/test/wait_test.dart b/test/wait_test.dart index eef06dd..890120a 100644 --- a/test/wait_test.dart +++ b/test/wait_test.dart @@ -10,7 +10,7 @@ void main() { TestHelper helper = TestHelper(); setUp(() { - sim = SimDart(observer: helper); + sim = SimDart(listener: helper); }); group('Wait', () { @@ -38,7 +38,7 @@ void main() { await sim.run(); - helper.test([ + helper.testEvents([ '[0][a][called]', '[0][a][yielded]', '[10][a][resumed]', @@ -54,7 +54,7 @@ void main() { sim.process(event: emptyEvent, start: 5, name: 'b'); await sim.run(); - helper.test([ + helper.testEvents([ '[0][a][called]', '[0][a][yielded]', '[5][b][called]', @@ -74,7 +74,7 @@ void main() { sim.process(event: emptyEvent, delay: 5, name: 'b'); await sim.run(); - helper.test([ + helper.testEvents([ '[0][a][called]', '[0][a][yielded]', '[5][b][called]', @@ -87,44 +87,48 @@ void main() { }); test('wait without await', () async { - expect( - () async { - sim.process( - event: (context) async { - context.wait(10); - }, - name: 'a'); - sim.process(event: emptyEvent, start: 5, name: 'b'); + sim.process( + event: (context) async { + context.wait(10); + }, + name: 'a'); + sim.process(event: emptyEvent, start: 5, name: 'b'); + sim.process(event: emptyEvent, start: 10, name: 'c'); - await sim.run(); - }, - throwsA( - predicate((e) => - e is StateError && - e.message.contains( - "Next event is being scheduled, but the current one is still paused waiting for continuation. Did you forget to use 'await'?")), - ), - ); + await expectLater( + sim.run(), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "Next event is being scheduled, but the current one is still paused waiting for continuation. Did you forget to use 'await'?")))); + helper.testEvents([ + '[0][a][called]', + '[0][a][yielded]', + '[5][b][called]', + '[5][b][finished]' + ]); + expect(helper.completerCount, 0); }); test('multiple wait without await', () async { - expect( - () async { - sim.process( - event: (context) async { - context.wait(10); - context.wait(10); - }, - name: 'a'); - await sim.run(); - }, - throwsA( - predicate((e) => - e is StateError && - e.message.contains( - "The event is already waiting. Did you forget to use 'await'?")), - ), - ); + sim.process( + event: (context) async { + context.wait(10); + context.wait(10); + }, + name: 'a'); + sim.process(event: emptyEvent, start: 5, name: 'b'); + await expectLater( + sim.run(), + throwsA(isA().having( + (e) => e.message, + 'message', + equals( + "The event is already waiting. Did you forget to use 'await'?")))); + helper.testEvents( + ['[0][a][called]', '[0][a][yielded]', '[0][a][finished]']); + expect(helper.completerCount, 0); }); }); }