瀏覽代碼

test: add coverage for Tape, recording

Sam Jaffe 2 年之前
父節點
當前提交
9324854019
共有 7 個文件被更改,包括 227 次插入26 次删除
  1. 3 0
      .gitmodules
  2. 1 0
      external/expect
  3. 1 1
      include/random/random.h
  4. 16 2
      include/random/tape.h
  5. 60 0
      shared_random_generator.xcodeproj/project.pbxproj
  6. 9 23
      src/tape.cxx
  7. 137 0
      test/tape_test.cxx

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "external/expect"]
+	path = external/expect
+	url = ssh://git@gogs.sjaffe.name:3000/sjjaffe/cpp-expect.git

+ 1 - 0
external/expect

@@ -0,0 +1 @@
+Subproject commit ff7cd322bd883b085d383253ceefb27933e6d6cf

+ 1 - 1
include/random/random.h

@@ -32,7 +32,7 @@ public:
     assert(tape && !tape_);
     tape_ = tape;
     auto cleanup = [this](void *) { tape_.reset(); };
-    return std::unique_ptr<void, decltype(cleanup)>(nullptr, cleanup);
+    return std::unique_ptr<void, decltype(cleanup)>(this, cleanup);
   }
 };
 }

+ 16 - 2
include/random/tape.h

@@ -24,8 +24,22 @@ public:
 
 private:
   enum class Type : char;
-  template <typename T> struct EntryT;
-  struct Entry;
+  template <typename T> struct EntryT {
+    EntryT(T min, T max) : min(min), max(max), result() {}
+    EntryT(T min, T max, T result) : min(min), max(max), result(result) {}
+
+    T min;
+    T max;
+    T result;
+  };
+  
+  struct Entry {
+    template <typename T>
+    Entry(Type type, EntryT<T> params) : type(type), params(params) {}
+
+    Type type;
+    std::variant<EntryT<double>, EntryT<unsigned int>> params;
+  };
 
 private:
   size_t index_{0}; // transient

+ 60 - 0
shared_random_generator.xcodeproj/project.pbxproj

@@ -10,6 +10,7 @@
 		CD89E51824E6F3FD008167A8 /* libshared_random_generator.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CDED6A4221B2F5A700AB91D0 /* libshared_random_generator.dylib */; };
 		CD89E51F24E6F40B008167A8 /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD89E50824E6F3EA008167A8 /* GoogleMock.framework */; };
 		CD89E52124E6F424008167A8 /* random_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD89E52024E6F424008167A8 /* random_test.cxx */; };
+		CDD2138229C76EF500A4582C /* tape_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDD2138129C76EF500A4582C /* tape_test.cxx */; };
 		CDE943B829C75E170086A8CA /* tape.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDE943B629C75E170086A8CA /* tape.cxx */; };
 		CDE943B929C75E170086A8CA /* tape.h in Headers */ = {isa = PBXBuildFile; fileRef = CDE943B729C75E170086A8CA /* tape.h */; };
 		CDE943BC29C7643E0086A8CA /* thread_safe.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDE943BB29C7643E0086A8CA /* thread_safe.cxx */; };
@@ -53,6 +54,27 @@
 			remoteGlobalIDString = CDED6A4121B2F5A700AB91D0;
 			remoteInfo = shared_random_generator;
 		};
+		CDD2138929C7723700A4582C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CDD2138329C7723700A4582C /* expect.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDD476BD29C5423B00BDB829;
+			remoteInfo = expect;
+		};
+		CDD2138B29C7723700A4582C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CDD2138329C7723700A4582C /* expect.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDEC1E8A235248390091D9F2;
+			remoteInfo = "expect-test";
+		};
+		CDD2138D29C7724200A4582C /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CDD2138329C7723700A4582C /* expect.xcodeproj */;
+			proxyType = 1;
+			remoteGlobalIDString = CDD476BC29C5423B00BDB829;
+			remoteInfo = expect;
+		};
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
@@ -61,6 +83,8 @@
 		CD89E51724E6F3FD008167A8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		CD89E52024E6F424008167A8 /* random_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = random_test.cxx; sourceTree = "<group>"; };
 		CDD2137C29C76E5600A4582C /* xcode_gtest_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xcode_gtest_helper.h; sourceTree = "<group>"; };
+		CDD2138129C76EF500A4582C /* tape_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tape_test.cxx; sourceTree = "<group>"; };
+		CDD2138329C7723700A4582C /* expect.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = expect.xcodeproj; path = external/expect/expect.xcodeproj; sourceTree = "<group>"; };
 		CDE943B329C75C610086A8CA /* device.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = device.h; sourceTree = "<group>"; };
 		CDE943B429C75C610086A8CA /* random.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = random.h; sourceTree = "<group>"; };
 		CDE943B529C75CD70086A8CA /* forwards.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = forwards.h; sourceTree = "<group>"; };
@@ -119,6 +143,15 @@
 			name = Frameworks;
 			sourceTree = "<group>";
 		};
+		CDD2138429C7723700A4582C /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CDD2138A29C7723700A4582C /* libexpect.a */,
+				CDD2138C29C7723700A4582C /* expect-test.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
 		CDE943AD29C75C250086A8CA /* include */ = {
 			isa = PBXGroup;
 			children = (
@@ -143,6 +176,7 @@
 			isa = PBXGroup;
 			children = (
 				CD89E50024E6F3E9008167A8 /* GoogleMock.xcodeproj */,
+				CDD2138329C7723700A4582C /* expect.xcodeproj */,
 				CDED6A5D21B2F76700AB91D0 /* random */,
 				CDE943AD29C75C250086A8CA /* include */,
 				CDED6A4921B2F5DF00AB91D0 /* src */,
@@ -177,6 +211,7 @@
 			children = (
 				CDD2137C29C76E5600A4582C /* xcode_gtest_helper.h */,
 				CD89E52024E6F424008167A8 /* random_test.cxx */,
+				CDD2138129C76EF500A4582C /* tape_test.cxx */,
 			);
 			path = test;
 			sourceTree = "<group>";
@@ -225,6 +260,7 @@
 			buildRules = (
 			);
 			dependencies = (
+				CDD2138E29C7724200A4582C /* PBXTargetDependency */,
 			);
 			name = shared_random_generator;
 			productName = shared_random_generator;
@@ -260,6 +296,10 @@
 			productRefGroup = CDED6A4321B2F5A700AB91D0 /* Products */;
 			projectDirPath = "";
 			projectReferences = (
+				{
+					ProductGroup = CDD2138429C7723700A4582C /* Products */;
+					ProjectRef = CDD2138329C7723700A4582C /* expect.xcodeproj */;
+				},
 				{
 					ProductGroup = CD89E50124E6F3E9008167A8 /* Products */;
 					ProjectRef = CD89E50024E6F3E9008167A8 /* GoogleMock.xcodeproj */;
@@ -302,6 +342,20 @@
 			remoteRef = CD89E50D24E6F3EA008167A8 /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
+		CDD2138A29C7723700A4582C /* libexpect.a */ = {
+			isa = PBXReferenceProxy;
+			fileType = archive.ar;
+			path = libexpect.a;
+			remoteRef = CDD2138929C7723700A4582C /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CDD2138C29C7723700A4582C /* expect-test.xctest */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.cfbundle;
+			path = "expect-test.xctest";
+			remoteRef = CDD2138B29C7723700A4582C /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
 /* End PBXReferenceProxy section */
 
 /* Begin PBXResourcesBuildPhase section */
@@ -320,6 +374,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD89E52124E6F424008167A8 /* random_test.cxx in Sources */,
+				CDD2138229C76EF500A4582C /* tape_test.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -341,6 +396,11 @@
 			target = CDED6A4121B2F5A700AB91D0 /* shared_random_generator */;
 			targetProxy = CD89E51924E6F3FD008167A8 /* PBXContainerItemProxy */;
 		};
+		CDD2138E29C7724200A4582C /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			name = expect;
+			targetProxy = CDD2138D29C7724200A4582C /* PBXContainerItemProxy */;
+		};
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */

+ 9 - 23
src/tape.cxx

@@ -8,28 +8,13 @@
 
 #include "random/tape.h"
 
+#include <expect/expect.hpp>
+
 #define IS_A(T) tname == typeid(T).name()
 
 namespace engine::random {
 enum class Tape::Type : char { Inclusive, Exclusive };
 
-template <typename T> struct Tape::EntryT {
-  EntryT(T min, T max) : min(min), max(max), result() {}
-  EntryT(T min, T max, T result) : min(min), max(max), result(result) {}
-
-  T min;
-  T max;
-  T result;
-};
-
-struct Tape::Entry {
-  template <typename T>
-  Entry(Type type, EntryT<T> params) : type(type), params(params) {}
-
-  Type type;
-  std::variant<EntryT<double>, EntryT<unsigned int>> params;
-};
-
 Tape::Tape(serial_type serial) {
   for (auto const & [exclusive, tname, min, max, result] : serial) {
     Type const type = exclusive ? Type::Exclusive : Type::Inclusive;
@@ -78,12 +63,13 @@ void Tape::inclusive(double min, double max, double result) {
 }
 
 template <typename T> T Tape::fetch(Type type, T min, T max) {
+  expects(index_ < entries_.size(), std::out_of_range, "End of Tape");
   auto const * entry = std::get_if<EntryT<T>>(&entries_[index_].params);
-  if (entries_[index_].type == type && entry && entry->min == min &&
-      entry->max == max) {
-    ++index_;
-    return entry->result;
-  }
-  throw std::logic_error("Attempting replay with different actions");
+  expects(entries_[index_].type == type && entry, std::domain_error,
+          "Mismatched signature to Device on Tape");
+  expects(entry->min == min && entry->max == max,
+          "Mismatched bounds to Device on Tape");
+  ++index_;
+  return entry->result;
 }
 }

+ 137 - 0
test/tape_test.cxx

@@ -0,0 +1,137 @@
+//
+//  tape_test.cpp
+//  shared_random_generator-test
+//
+//  Created by Sam Jaffe on 3/19/23.
+//  Copyright © 2023 Sam Jaffe. All rights reserved.
+//
+
+#include "random/tape.h"
+
+#include "random/random.h"
+
+#include "xcode_gtest_helper.h"
+
+using testing::ElementsAre;
+using testing::FieldsAre;
+
+TEST(TapeTest, CanRecordIntData) {
+  engine::random::Tape tape;
+  
+  tape.exclusive(100u, 50);
+  EXPECT_EQ(tape.exclusive(100), 50);
+}
+
+TEST(TapeTest, CanRecordDoubleData) {
+  engine::random::Tape tape;
+  
+  tape.exclusive(0.0, 100.0, 50.0);
+  EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
+
+  tape.inclusive(0.0, 100.0, 50.0);
+  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
+}
+
+TEST(TapeTest, ThrowsOnOverFetch) {
+  engine::random::Tape tape;
+  tape.exclusive(100u, 50);
+  EXPECT_EQ(tape.exclusive(100), 50);
+  EXPECT_THROW(tape.exclusive(100), std::out_of_range);
+}
+
+TEST(TapeTest, BadRequestIsNoOp) {
+  engine::random::Tape tape;
+  tape.exclusive(100u, 50);
+  EXPECT_ANY_THROW(tape.exclusive(1));
+  EXPECT_EQ(tape.exclusive(100), 50);
+}
+
+TEST(TapeTest, ThrowsOnTypeMismatch) {
+  engine::random::Tape tape;
+  tape.exclusive(100u, 50);
+  EXPECT_THROW(tape.exclusive(0.0, 100.0), std::domain_error);
+}
+
+TEST(TapeTest, ThrowsOnInclusiveExclusiveMismatch) {
+  engine::random::Tape tape;
+  tape.exclusive(0.0, 100.0, 50.0);
+  EXPECT_THROW(tape.inclusive(0.0, 100.0), std::domain_error);
+}
+
+TEST(TapeTest, ThrowsOnBoundsMismatch) {
+  engine::random::Tape tape;
+  tape.exclusive(0.0, 100.0, 50.0);
+  EXPECT_THROW(tape.exclusive(0.0, 1.0), std::logic_error);
+}
+
+TEST(TapeTest, IsOrdered) {
+  engine::random::Tape tape;
+  tape.exclusive(0.0, 100.0, 50.0);
+  tape.inclusive(0.0, 100.0, 50.0);
+  
+  EXPECT_THROW(tape.inclusive(0.0, 100.0), std::domain_error);
+
+  EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
+  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
+}
+
+TEST(TapeTest, IsSerializable) {
+  engine::random::Tape tape;
+  tape.exclusive(100u, 50);
+  tape.exclusive(0.0, 100.0, 50.0);
+  tape.inclusive(0.0, 100.0, 50.0);
+  
+  auto serial = engine::random::Tape::serial_type(tape);
+  EXPECT_THAT(serial, ElementsAre(FieldsAre(true, "j", 0, 100, 50),
+                                  FieldsAre(true, "d", 0, 100, 50),
+                                  FieldsAre(false, "d", 0, 100, 50)));
+  
+  tape = engine::random::Tape(serial);
+  EXPECT_EQ(tape.exclusive(100), 50);
+  EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
+  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
+}
+
+TEST(TapeTest, IndexNotIncludedInSerialization) {
+  engine::random::Tape tape;
+  tape.exclusive(100u, 50);
+  tape.exclusive(0.0, 100.0, 50.0);
+  tape.inclusive(0.0, 100.0, 50.0);
+  
+  EXPECT_EQ(tape.exclusive(100), 50);
+  EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
+  
+  auto serial = engine::random::Tape::serial_type(tape);
+  EXPECT_THAT(serial, ElementsAre(FieldsAre(true, "j", 0, 100, 50),
+                                  FieldsAre(true, "d", 0, 100, 50),
+                                  FieldsAre(false, "d", 0, 100, 50)));
+  
+  tape = engine::random::Tape(serial);
+  EXPECT_EQ(tape.exclusive(100), 50);
+  EXPECT_EQ(tape.exclusive(0.0, 100.0), 50.0);
+  EXPECT_EQ(tape.inclusive(0.0, 100.0), 50.0);
+}
+
+TEST(TapeTest, CanAttachToRandom) {
+  engine::random::Random random;
+  auto tape = std::make_shared<engine::random::Tape>();
+
+  auto scope = random.record(tape);
+  
+  auto result = random.exclusive(100);
+  EXPECT_EQ(tape->exclusive(100), result);
+}
+
+TEST(TapeTest, StopsRecordingOnScopeExit) {
+  engine::random::Random random;
+  auto tape = std::make_shared<engine::random::Tape>();
+
+  {
+    auto scope = random.record(tape);
+    random.exclusive(100);
+  }
+  random.exclusive(100);
+  
+  EXPECT_NO_THROW(tape->exclusive(100));
+  EXPECT_THROW(tape->exclusive(100), std::out_of_range);
+}