소스 검색

fix: make it so Tape no longer befouls unit testing

Sam Jaffe 2 년 전
부모
커밋
7574cdc0a6
9개의 변경된 파일159개의 추가작업 그리고 90개의 파일을 삭제
  1. 6 5
      include/random/device.h
  2. 4 0
      include/random/forwards.h
  3. 7 10
      include/random/mock/mock_device.h
  4. 14 4
      include/random/tape.h
  5. 13 5
      src/distribution.cxx
  6. 5 15
      src/random.cxx
  7. 30 7
      src/tape.cxx
  8. 2 6
      test/distribution_test.cxx
  9. 78 38
      test/tape_test.cxx

+ 6 - 5
include/random/device.h

@@ -10,7 +10,9 @@
 #include <random/distribution.h>
 #include <random/forwards.h>
 
-#define APPLY_DEVICE(T)                                                        \
+#define IMPLEMENT_DEVICE(X) X(size_t) X(double) X(int32_t) X(uint32_t)
+
+#define TRIVIAL_RANDOM_DEVICE(T)                                               \
   virtual T apply(Distribution<T> const & dist) { return dist(*this); }
 
 namespace engine::random {
@@ -25,9 +27,8 @@ public:
 
 private:
   friend class Random;
-  APPLY_DEVICE(size_t)
-  APPLY_DEVICE(double)
-  APPLY_DEVICE(int32_t)
-  APPLY_DEVICE(uint32_t)
+  IMPLEMENT_DEVICE(TRIVIAL_RANDOM_DEVICE)
 };
 }
+
+#undef TRIVIAL_RANDOM_DEVICE

+ 4 - 0
include/random/forwards.h

@@ -8,6 +8,8 @@
 
 #pragma once
 
+#include <string>
+
 class scope_exit;
 
 /**
@@ -21,4 +23,6 @@ template <typename> class Distribution;
 class Mock;
 class Random;
 class Tape;
+
+template <typename T> std::string to_string(Distribution<T> const & dist);
 }

+ 7 - 10
include/random/mock/mock_device.h

@@ -19,21 +19,18 @@
 #define CONCAT(A, B) CONCAT2(A, B)
 #endif
 
-#define MOCK_APPLY_DEVICE(T)                                                   \
+#define MOCK_DEVICE(T)                                                         \
+  MOCK_METHOD1(CONCAT(apply_, T), T(std::string const &));                     \
   T apply(Distribution<T> const & dist) final {                                \
-    std::stringstream ss;                                                      \
-    ss << dist;                                                                \
-    return CONCAT(apply_, T)(ss.str());                                        \
-  }                                                                            \
-  MOCK_METHOD1(CONCAT(apply_, T), T(std::string const &))
+    return CONCAT(apply_, T)(to_string(dist));                                 \
+  }
 
 namespace engine::random {
 class MockDevice : public Device {
 public:
   result_type operator()() final { return apply_uint32_t("?"); }
-  MOCK_APPLY_DEVICE(size_t);
-  MOCK_APPLY_DEVICE(double);
-  MOCK_APPLY_DEVICE(int32_t);
-  MOCK_APPLY_DEVICE(uint32_t);
+  IMPLEMENT_DEVICE(MOCK_DEVICE)
 };
 }
+
+#undef MOCK_DEVICE

+ 14 - 4
include/random/tape.h

@@ -13,22 +13,32 @@
 
 #include <random/device.h>
 
+#define DECLARE_TAPE(T)                                                        \
+  T apply(Distribution<T> const & dist) final;                                 \
+  T apply(Distribution<T> const & dist, T value);
+
 namespace engine::random {
 class Tape : public Device {
 public:
   // Allow for the serialization and de-serialization of this Tape
-  using serial_type = std::vector<result_type>;
+  using serial_type = std::vector<std::pair<std::string, uint64_t>>;
 
 private:
   size_t index_{0}; // transient
-  std::vector<result_type> entries_;
+  serial_type entries_;
 
 public:
   Tape() = default;
   Tape(serial_type serial) : entries_(std::move(serial)) {}
   explicit operator serial_type() const { return entries_; }
 
-  result_type operator()() override;
-  result_type operator()(result_type result);
+  IMPLEMENT_DEVICE(DECLARE_TAPE)
+
+private:
+  result_type operator()() final { throw; }
+  template <typename T> T poll(std::string const & dist);
+  template <typename T> T operator()(Distribution<T> const & dist);
 };
 }
+
+#undef DECLARE_TAPE

+ 13 - 5
src/distribution.cxx

@@ -10,10 +10,21 @@
 
 #include <iostream>
 #include <random>
+#include <sstream>
 
 #include "random/device.h"
 
+#define INSTANTIATE_DISTRIBUTION_TEMPLATES(T)                                  \
+  template std::string to_string(Distribution<T> const &);                     \
+  template class Uniform<T>;
+
 namespace engine::random {
+template <typename T> std::string to_string(Distribution<T> const & dist) {
+  std::stringstream ss;
+  ss << dist;
+  return ss.str();
+}
+
 template <typename Rand>
 Uniform<Rand>::Uniform(Rand min)
     : Uniform(min, std::numeric_limits<Rand>::max()) {}
@@ -32,11 +43,8 @@ auto Uniform<Rand>::operator()(Device & device) const -> result_type {
 
 template <typename Rand> void Uniform<Rand>::describe(std::ostream & os) const {
   constexpr char close = std::is_floating_point_v<Rand> ? ')' : ']';
-  os << "Uniform[" << min_ << ',' << max_ << close;
+  os << typeid(Rand).name() << "Uniform[" << min_ << ',' << max_ << close;
 }
 
-template class Uniform<size_t>;
-template class Uniform<double>;
-template class Uniform<int32_t>;
-template class Uniform<uint32_t>;
+IMPLEMENT_DEVICE(INSTANTIATE_DISTRIBUTION_TEMPLATES)
 }

+ 5 - 15
src/random.cxx

@@ -10,6 +10,9 @@
 #include "random/distribution.h"
 #include "random/tape.h"
 
+#define IMPLEMENT_RANDOM(T)                                                    \
+  template T Random::operator()(Distribution<T> const &) const;
+
 namespace engine::random {
 class DefaultDevice : public Device {
 public:
@@ -19,16 +22,6 @@ private:
   std::mt19937 rng{std::random_device{}()};
 };
 
-class Link : public Device {
-public:
-  Link(Tape & tape, Device & to) : tape_(tape), device_(to) {}
-  result_type operator()() { return tape_(device_()); }
-
-private:
-  Tape & tape_;
-  Device & device_;
-};
-
 Random::Random() : impl_(std::make_shared<DefaultDevice>()) {}
 
 Random::Random(Random const & other, std::shared_ptr<Tape> with_tape)
@@ -39,7 +32,7 @@ Random::Random(Random const & other, Tape & with_tape)
 
 template <typename Rand>
 Rand Random::operator()(Distribution<Rand> const & dist) const {
-  return tape_ ? dist(Link(*tape_, *impl_)) : impl_->apply(dist);
+  return tape_ ? tape_->apply(dist, impl_->apply(dist)) : impl_->apply(dist);
 }
 
 scope_exit Random::record(std::shared_ptr<Tape> tape) {
@@ -48,8 +41,5 @@ scope_exit Random::record(std::shared_ptr<Tape> tape) {
   return [this]() { tape_.reset(); };
 }
 
-template size_t Random::operator()(Distribution<size_t> const &) const;
-template double Random::operator()(Distribution<double> const &) const;
-template int32_t Random::operator()(Distribution<int32_t> const &) const;
-template uint32_t Random::operator()(Distribution<uint32_t> const &) const;
+IMPLEMENT_DEVICE(IMPLEMENT_RANDOM)
 }

+ 30 - 7
src/tape.cxx

@@ -11,15 +11,38 @@
 #include <expect/expect.hpp>
 #include <scope_guard/scope_guard.hpp>
 
-namespace engine::random {
-auto Tape::operator()(result_type result) -> result_type {
-  entries_.push_back(result);
-  return result;
+#define IMPLEMENT_TAPE(T)                                                      \
+  T Tape::apply(Distribution<T> const & dist) { return (*this)(dist); }        \
+  T Tape::apply(Distribution<T> const & dist, T value) {                       \
+    entries_.emplace_back(to_string(dist), ByteSerial<T>()(value));            \
+    return value;                                                              \
+  }
+
+namespace {
+template <typename T> struct ByteSerial {
+  auto operator()(T in) const { return static_cast<uint64_t>(in); }
+  auto operator()(uint64_t in) const { return static_cast<T>(in); }
+};
+
+template <> struct ByteSerial<double> {
+  auto operator()(double in) const { return reinterpret_cast<uint64_t &>(in); }
+  auto operator()(uint64_t in) const { return reinterpret_cast<double &>(in); }
+};
 }
 
-auto Tape::operator()() -> result_type {
+namespace engine::random {
+
+template <typename T> T Tape::poll(std::string const & dist) {
   expects(index_ < entries_.size(), std::out_of_range, "End of Tape");
-  scope(exit) { ++index_; };
-  return entries_[index_];
+  auto const & [id, value] = entries_[index_];
+  expects(id == dist, "Expected " + id + " got " + dist);
+  ++index_;
+  return ByteSerial<T>()(value);
+}
+
+template <typename T> T Tape::operator()(Distribution<T> const & dist) {
+  return poll<T>(to_string(dist));
 }
+
+IMPLEMENT_DEVICE(IMPLEMENT_TAPE)
 }

+ 2 - 6
test/distribution_test.cxx

@@ -50,9 +50,7 @@ TEST(UniformIntTest, UniformOnDefaultRandom) {
 }
 
 TEST(UniformIntTest, IsClosed) {
-  std::stringstream ss;
-  ss << Uniform(0, 10);
-  EXPECT_THAT(ss.str(), "Uniform[0,10]");
+  EXPECT_THAT(to_string(Uniform(0, 10)), "iUniform[0,10]");
 }
 
 TEST(UniformRealTest, UniformOnDefaultRandom) {
@@ -68,7 +66,5 @@ TEST(UniformRealTest, UniformOnDefaultRandom) {
 }
 
 TEST(UniformRealTest, IsOpen) {
-  std::stringstream ss;
-  ss << Uniform(0.0, 10.0);
-  EXPECT_THAT(ss.str(), "Uniform[0,10)");
+  EXPECT_THAT(to_string(Uniform(0., 10.)), "dUniform[0,10)");
 }

+ 78 - 38
test/tape_test.cxx

@@ -12,6 +12,7 @@
 
 #include <scope_guard/scope_guard.hpp>
 
+#include "random/distribution.h"
 #include "random/random.h"
 
 #include "xcode_gtest_helper.h"
@@ -19,69 +20,109 @@
 using testing::ElementsAre;
 using testing::FieldsAre;
 
-class StubDistribution : public engine::random::Distribution<int> {
+template <typename T> class Uniform : public engine::random::Uniform<T> {
 public:
-  StubDistribution(int value) : value_(value) {}
-  result_type operator()(engine::random::Device &) const { return value_; }
+  Uniform(T min, T max) : engine::random::Uniform<T>(min, max) {}
 
-private:
-  int value_;
+  T operator()(engine::random::Device &) const final { throw; }
 };
 
-TEST(TapeTest, CanRecord) {
+MATCHER_P(BytesAre, value, "") {
+  *result_listener << "whose bytes are " << std::hex << arg;
+  return reinterpret_cast<double const &>(arg) == value;
+}
+
+TEST(TapeTest, CanRecordIntData) {
   engine::random::Tape tape;
 
-  tape(50u);
-  EXPECT_EQ(tape(), 50);
+  tape.apply(Uniform(0u, 100u), 50u);
+  EXPECT_EQ(tape.apply(Uniform(0u, 100u)), 50);
+}
+
+TEST(TapeTest, CanRecordDoubleData) {
+  engine::random::Tape tape;
+
+  tape.apply(Uniform(0.0, 100.0), 50.0);
+  EXPECT_EQ(tape.apply(Uniform(0.0, 100.0)), 50.0);
+
+  tape.apply(Uniform(0.0, 100.0), 50.0);
+  EXPECT_EQ(tape.apply(Uniform(0.0, 100.0)), 50.0);
 }
 
 TEST(TapeTest, ThrowsOnOverFetch) {
   engine::random::Tape tape;
-  tape(50);
-  EXPECT_EQ(tape(), 50);
-  EXPECT_THROW(tape(), std::out_of_range);
+  tape.apply(Uniform(0, 100), 50);
+  EXPECT_EQ(tape.apply(Uniform(0, 100)), 50);
+  EXPECT_THROW(tape.apply(Uniform(0, 100)), std::out_of_range);
+}
+
+TEST(TapeTest, BadRequestIsNoOp) {
+  engine::random::Tape tape;
+  tape.apply(Uniform(0, 100), 50);
+  EXPECT_ANY_THROW(tape.apply(Uniform(0, 1)));
+  EXPECT_EQ(tape.apply(Uniform(0, 100)), 50);
+}
+
+TEST(TapeTest, ThrowsOnTypeMismatch) {
+  engine::random::Tape tape;
+  tape.apply(Uniform(0u, 100u), 50u);
+  EXPECT_THROW(tape.apply(Uniform(0.0, 100.0)), std::logic_error);
+  EXPECT_THROW(tape.apply(Uniform(0, 100)), std::logic_error);
+  EXPECT_NO_THROW(tape.apply(Uniform(0u, 100u)));
+}
+
+TEST(TapeTest, ThrowsOnBoundsMismatch) {
+  engine::random::Tape tape;
+  tape.apply(Uniform(0.0, 100.0), 50.0);
+  EXPECT_THROW(tape.apply(Uniform(0.0, 1.0)), std::logic_error);
 }
 
 TEST(TapeTest, IsOrdered) {
   engine::random::Tape tape;
-  tape(1);
-  tape(2);
+  tape.apply(Uniform(0.0, 100.0), 50.0);
+  tape.apply(Uniform(10.0, 90.0), 50.0);
+
+  EXPECT_THROW(tape.apply(Uniform(10.0, 90.0)), std::logic_error);
 
-  EXPECT_EQ(tape(), 1);
-  EXPECT_EQ(tape(), 2);
+  EXPECT_EQ(tape.apply(Uniform(0.0, 100.0)), 50.0);
+  EXPECT_EQ(tape.apply(Uniform(10.0, 90.0)), 50.0);
 }
 
 TEST(TapeTest, IsSerializable) {
   engine::random::Tape tape;
-  tape(1);
-  tape(2);
-  tape(3);
+  tape.apply(Uniform(0, 100), 50);
+  tape.apply(Uniform(0u, 100u), 50u);
+  tape.apply(Uniform(0.0, 100.0), 50.0);
 
   auto serial = engine::random::Tape::serial_type(tape);
-  EXPECT_THAT(serial, ElementsAre(1, 2, 3));
+  EXPECT_THAT(serial, ElementsAre(FieldsAre("iUniform[0,100]", 50),
+                                  FieldsAre("jUniform[0,100]", 50),
+                                  FieldsAre("dUniform[0,100)", BytesAre(50))));
 
   tape = engine::random::Tape(serial);
-  EXPECT_EQ(tape(), 1);
-  EXPECT_EQ(tape(), 2);
-  EXPECT_EQ(tape(), 3);
+  EXPECT_EQ(tape.apply(Uniform(0, 100)), 50);
+  EXPECT_EQ(tape.apply(Uniform(0u, 100u)), 50u);
+  EXPECT_EQ(tape.apply(Uniform(0.0, 100.0)), 50.0);
 }
 
 TEST(TapeTest, IndexNotIncludedInSerialization) {
   engine::random::Tape tape;
-  tape(1);
-  tape(2);
-  tape(3);
+  tape.apply(Uniform(0, 100), 50);
+  tape.apply(Uniform(0u, 100u), 50u);
+  tape.apply(Uniform(0.0, 100.0), 50.0);
 
-  EXPECT_EQ(tape(), 1);
-  EXPECT_EQ(tape(), 2);
+  EXPECT_EQ(tape.apply(Uniform(0, 100)), 50);
+  EXPECT_EQ(tape.apply(Uniform(0u, 100u)), 50u);
 
   auto serial = engine::random::Tape::serial_type(tape);
-  EXPECT_THAT(serial, ElementsAre(1, 2, 3));
+  EXPECT_THAT(serial, ElementsAre(FieldsAre("iUniform[0,100]", 50),
+                                  FieldsAre("jUniform[0,100]", 50),
+                                  FieldsAre("dUniform[0,100)", BytesAre(50))));
 
   tape = engine::random::Tape(serial);
-  EXPECT_EQ(tape(), 1);
-  EXPECT_EQ(tape(), 2);
-  EXPECT_EQ(tape(), 3);
+  EXPECT_EQ(tape.apply(Uniform(0, 100)), 50);
+  EXPECT_EQ(tape.apply(Uniform(0u, 100u)), 50u);
+  EXPECT_EQ(tape.apply(Uniform(0.0, 100.0)), 50.0);
 }
 
 TEST(TapeTest, CanAttachToRandom) {
@@ -90,9 +131,8 @@ TEST(TapeTest, CanAttachToRandom) {
 
   auto scope = random.record(tape);
 
-  auto result = random(StubDistribution(5));
-  EXPECT_EQ(result, 5);
-  EXPECT_EQ((*tape)(), 5);
+  auto result = random(engine::random::Uniform(0, 100));
+  EXPECT_EQ(tape->apply(engine::random::Uniform(0, 100)), result);
 }
 
 TEST(TapeTest, StopsRecordingOnScopeExit) {
@@ -101,10 +141,10 @@ TEST(TapeTest, StopsRecordingOnScopeExit) {
 
   {
     auto scope = random.record(tape);
-    random(StubDistribution(2));
+    random(engine::random::Uniform(0, 100));
   }
-  random(StubDistribution(1));
+  random(engine::random::Uniform(0, 100));
 
-  EXPECT_NO_THROW((*tape)());
-  EXPECT_THROW((*tape)(), std::out_of_range);
+  EXPECT_NO_THROW(tape->apply(engine::random::Uniform(0, 100)));
+  EXPECT_THROW(tape->apply(engine::random::Uniform(0, 100)), std::out_of_range);
 }