Repository: intel/rohd (dart)

Issue:
Update "timestamp in the past" error to suggest a Simulator reset
### Motivation

There's a couple situations where a timestamp may be added in the past in a simulation:
- Someone is making a mistake and calling `Simulator.registerAction` at a time that is in the past.
- Someone forgot to add a call to `Simulator.reset` between tests.

Usually people do not often need to use the `registerAction` API, and if they do, they would know about how they are picking the proper `timestamp`.  However, when people forget to call `reset()`, the existing error message is confusing and unhelpful.

### Desired solution

Update the exception thrown when a `registerAction` is called with a timestamp in the past to suggest that maybe they meant to include a `Simulator.reset()` call.

Update the documentation/user guide as well to make it clear how to do this.

### Alternatives considered

_No response_

### Additional details

_No response_

Pull request description:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

This improves the exceptions and documentation for when you forget a call to `Simulator.reset()`.

## Related Issue(s)

Fix #490

## Testing

Added a new test

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible? If yes, how so?

No

## Documentation

> Does the change require any updates to documentation? If so, where? Are they included?

Yes, this is basically doc changes only

Patch:
diff --git a/lib/src/exceptions/exceptions.dart b/lib/src/exceptions/exceptions.dart
index e98d22d1c..e0945dd4b 100644
--- a/lib/src/exceptions/exceptions.dart
+++ b/lib/src/exceptions/exceptions.dart
@@ -10,4 +10,5 @@ export './name/name_exceptions.dart';
 export './sim_compare/sim_compare_exceptions.dart';
 export 'illegal_configuration_exception.dart';
 export 'rohd_exception.dart';
+export 'simulator_exception.dart';
 export 'unsupported_type_exception.dart';
diff --git a/lib/src/exceptions/simulator_exception.dart b/lib/src/exceptions/simulator_exception.dart
new file mode 100644
index 000000000..46f633fb8
--- /dev/null
+++ b/lib/src/exceptions/simulator_exception.dart
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 Intel Corporation
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// simulator_exception.dart
+// Definition for exception when an error occurs in the simulator.
+//
+// 2024 June 11
+// Author: Max Korbel <max.korbel@intel.com>
+
+import 'package:rohd/rohd.dart';
+
+/// An [Exception] thrown when an error occurs in the simulator.
+class SimulatorException extends RohdException {
+  /// Constructs a new [Exception] for when an error occurs in the simulator
+  /// with [message] explaining why.
+  SimulatorException(super.message);
+}
diff --git a/lib/src/simulator.dart b/lib/src/simulator.dart
index 82bdd791f..64476c903 100644
--- a/lib/src/simulator.dart
+++ b/lib/src/simulator.dart
@@ -191,8 +191,12 @@ abstract class Simulator {
   /// The [action], if it returns a [Future], will be `await`ed.
   static void registerAction(int timestamp, dynamic Function() action) {
     if (timestamp < _currentTimestamp) {
-      throw Exception('Cannot add timestamp "$timestamp" in the past.'
-          '  Current time is ${Simulator.time}');
+      throw SimulatorException('Cannot add timestamp "$timestamp" in the past.'
+          ' Current time is ${Simulator.time}.'
+          ' Did you mean to include a call to Simulator.reset()?'
+          ' If this is hit in a series of unit tests, see the user guide'
+          ' for tips:'
+          ' https://intel.github.io/rohd-website/docs/unit-test/');
     }
     if (!_pendingTimestamps.containsKey(timestamp)) {
       _pendingTimestamps[timestamp] = ListQueue();


Test patch:
diff --git a/doc/user_guide/_docs/A18-unit-testing.md b/doc/user_guide/_docs/A18-unit-testing.md
index 92c7c417f..22d97d9c9 100644
--- a/doc/user_guide/_docs/A18-unit-testing.md
+++ b/doc/user_guide/_docs/A18-unit-testing.md
@@ -2,12 +2,22 @@
 title: "Unit Testing"
 permalink: /docs/unit-test/
 excerpt: "Unit Testing"
-last_modified_at: 2022-12-06
+last_modified_at: 2024-06-11
 toc: true
 ---
 
 Dart has a great unit testing package available on pub.dev: <https://pub.dev/packages/test>
 
-The ROHD package has a great set of examples of how to write unit tests for ROHD `Module`s in the test/ directory.
+The ROHD package has a great set of examples of how to write unit tests for ROHD `Module`s in the `test/` directory.
 
-Note that when unit testing with ROHD, it is important to reset the `Simulator` with `Simulator.reset()`.
+Note that when unit testing with ROHD, it is important to reset the `Simulator` with `Simulator.reset()` between tests.  For example, you could include something like the following so that the `Simulator` is always reset at the end of each of your tests:
+
+```dart
+void main() {
+  tearDown(() async {
+    await Simulator.reset();
+  });
+
+  test('my first test', () async {
+    ...
+```
diff --git a/test/simulator_test.dart b/test/simulator_test.dart
index 0cb932364..f6ef041c4 100644
--- a/test/simulator_test.dart
+++ b/test/simulator_test.dart
@@ -137,6 +137,16 @@ void main() {
     expect(injectedActionExecuted, isTrue);
   });
 
+  test('simulator exception when registering action in the past', () async {
+    Simulator.registerAction(100, () {
+      Simulator.registerAction(50, () {});
+    });
+
+    expect(() async {
+      await Simulator.run();
+    }, throwsA(isA<SimulatorException>()));
+  });
+
   group('Rohme compatibility tests', () {
     test('simulator supports delta cycles', () async {
       // ignore: omit_local_variable_types