[mask rom] Add mask_rom_test.h for mask ROM unit tests.
This change introduces `mask_rom_test.h`, which provides classes for
mocking dependencies of and unit testing mask ROM modules.
Signed-off-by: Alphan Ulusoy <alphan@google.com>
diff --git a/sw/device/lib/testing/mask_rom_test.h b/sw/device/lib/testing/mask_rom_test.h
new file mode 100644
index 0000000..d7dab97
--- /dev/null
+++ b/sw/device/lib/testing/mask_rom_test.h
@@ -0,0 +1,95 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_MASK_ROM_TEST_H_
+#define OPENTITAN_SW_DEVICE_LIB_TESTING_MASK_ROM_TEST_H_
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace mask_rom_test {
+
+/**
+ * Mixin for global mocks.
+ *
+ * `GlobalMock<T>` is a derived class of `T` that can have at most one instance
+ * at a time (checked at runtime) and makes this instance globally accessible
+ * via the static `GlobalMock<T> &Instance()` method.
+ *
+ * Mock classes used in tests should be of this type so that mock functions can
+ * access their instances during tests. Since we prefer using strict mocks,
+ * mock classes should also be of type `testing::StrictMock`. Mock classes that
+ * satisfy both requirements can be defined using a type alias as follows:
+ *
+ * namespace mask_rom_test {
+ * namespace internal {
+ * class MockFoo {
+ * ...
+ * };
+ * } // namespace internal
+ * // Type alias for making `internal::MockFoo` a global and strict mock.
+ * using MockFoo = GlobalMock<testing::StrictMock<internal::MockFoo>>;
+ * ...
+ * } // namespace mask_rom_test
+ */
+template <typename T>
+class GlobalMock : public T {
+ public:
+ template <typename... Args>
+ GlobalMock(Args &&... args) : T(std::forward(args)...) {
+ if (instance_ != nullptr) {
+ throw std::runtime_error("Mock is already instantiated.");
+ }
+ instance_ = this;
+ }
+
+ static_assert(std::has_virtual_destructor<T>::value,
+ "Mock class must have a virtual destructor.");
+ virtual ~GlobalMock() { instance_ = nullptr; }
+
+ static GlobalMock<T> &Instance() {
+ if (instance_ == nullptr) {
+ throw std::runtime_error("Mock is not instantiated yet.");
+ }
+ return *instance_;
+ }
+
+ GlobalMock(const GlobalMock &) = delete;
+ GlobalMock &operator=(const GlobalMock &) = delete;
+ GlobalMock(GlobalMock &&) = delete;
+ GlobalMock &operator=(GlobalMock &&) = delete;
+
+ private:
+ static GlobalMock<T> *instance_;
+};
+template <typename T>
+GlobalMock<T> *GlobalMock<T>::instance_ = nullptr;
+
+/**
+ * Test fixture for mask ROM tests.
+ *
+ * Test suites should derive their test fixtures from this class. This class
+ * enforces that mock methods are called in the order `EXPECT_CALL` statements
+ * are written. In cases where this behavior is not desired, test fixtures can
+ * derive from `Unordered<MaskRomTest>` instead to opt-out.
+ */
+class MaskRomTest : public testing::Test {
+ protected:
+ std::unique_ptr<testing::InSequence> seq_ =
+ std::make_unique<testing::InSequence>();
+};
+
+/**
+ * Mixin for unordered calls.
+ *
+ * @see MaskRomTest.
+ */
+template <typename T>
+class Unordered : public T {
+ protected:
+ Unordered() : T() { T::seq_.reset(); }
+};
+
+} // namespace mask_rom_test
+#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_MASK_ROM_TEST_H_