├── .gitmodules ├── test ├── experiment.cc ├── before_run.cc ├── ignore.cc ├── compare.cc ├── run_if.cc ├── context.cc ├── cleanup.cc ├── multiple_candidates.cc └── publish.cc ├── conanfile.py ├── benchmark └── benchmark.cc ├── CMakeLists.txt ├── LICENSE ├── README.md └── include └── scientist.hh /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/googletest"] 2 | path = test/googletest 3 | url = https://github.com/google/googletest 4 | [submodule "benchmark/benchmark"] 5 | path = benchmark/benchmark 6 | url = https://github.com/google/benchmark 7 | -------------------------------------------------------------------------------- /test/experiment.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | 6 | TEST(Experiment, ReturnsControlValue) 7 | { 8 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 9 | { 10 | e.Use([]() { return 42;}); 11 | e.Try([]() { return 0;}); 12 | }); 13 | 14 | ASSERT_EQ(42, res); 15 | } 16 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile, CMake 2 | 3 | class ScientistConan(ConanFile): 4 | name = "scientist" 5 | version = "1.0.0" 6 | settings = "os", "compiler", "arch", "build_type" 7 | exports_sources = "include/*", "CMakeLists.txt", "benchmark/*", "test/*" 8 | no_copy_source = True 9 | 10 | def build(self): 11 | cmake = CMake(self) 12 | cmake.definitions['BENCHMARK_ENABLE_TESTING'] = 'OFF' 13 | cmake.configure() 14 | cmake.build() 15 | cmake.test() 16 | 17 | def package(self): 18 | self.copy("*.h") 19 | 20 | def package_id(self): 21 | self.info.header_only() 22 | -------------------------------------------------------------------------------- /benchmark/benchmark.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | int function() 6 | { 7 | return 1; 8 | } 9 | 10 | static void WithScientist(benchmark::State& state) 11 | { 12 | while (state.KeepRunning()) 13 | { 14 | int res = Scientist::Science("benchmark", [](ExperimentInterface& e) 15 | { 16 | e.Use(function); 17 | e.Try(function); 18 | }); 19 | } 20 | } 21 | 22 | static void WithoutScientist(benchmark::State& state) 23 | { 24 | while(state.KeepRunning()) 25 | int res = function(); 26 | } 27 | 28 | BENCHMARK(WithScientist); 29 | BENCHMARK(WithoutScientist); 30 | 31 | BENCHMARK_MAIN(); 32 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(Scientist) 3 | 4 | include_directories("include/") 5 | 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 7 | 8 | # Tests 9 | set(BUILD_GTEST ON) 10 | add_subdirectory(test/googletest) 11 | enable_testing() 12 | include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) 13 | 14 | set(TEST_SOURCES test/ignore.cc test/publish.cc test/experiment.cc test/compare.cc test/run_if.cc test/cleanup.cc test/context.cc test/before_run.cc test/multiple_candidates.cc) 15 | 16 | add_executable(tests ${TEST_SOURCES}) 17 | 18 | target_link_libraries(tests gtest gtest_main) 19 | 20 | add_test(all_tests tests) 21 | 22 | # Benchmarks 23 | add_subdirectory(benchmark/benchmark) 24 | include_directories(benchmark/benchmark/include) 25 | add_executable(benchmarks benchmark/benchmark.cc) 26 | target_link_libraries(benchmarks benchmark) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tomi Äijö 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/before_run.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | 6 | TEST(BeforeRun, RunsSetup) 7 | { 8 | bool setupCalled = false; 9 | int res = Scientist::Science("", [&](ExperimentInterface& e) 10 | { 11 | e.Use([]() { return 42;}); 12 | e.Try([]() { return 0;}); 13 | e.BeforeRun([&]() { setupCalled = true; }); 14 | }); 15 | 16 | ASSERT_TRUE(setupCalled); 17 | } 18 | 19 | TEST(BeforeRun, RunSetupsInOrder) 20 | { 21 | bool firstSetupCalled = false; 22 | bool secondSetupCalled = false; 23 | 24 | int res = Scientist::Science("", [&](ExperimentInterface& e) 25 | { 26 | e.Use([]() { return 42;}); 27 | e.Try([]() { return 0;}); 28 | e.BeforeRun([&]() { ASSERT_FALSE(secondSetupCalled); firstSetupCalled = true; }); 29 | e.BeforeRun([&]() { ASSERT_TRUE(firstSetupCalled); secondSetupCalled = true; }); 30 | }); 31 | 32 | ASSERT_TRUE(firstSetupCalled); 33 | ASSERT_TRUE(secondSetupCalled); 34 | } 35 | 36 | TEST(BeforeRun, DoesNotRunSetupIfExperimentIsDisabled) 37 | { 38 | bool setupCalled = false; 39 | int res = Scientist::Science("", [&](ExperimentInterface& e) 40 | { 41 | e.Use([]() { return 42; }); 42 | e.Try([]() { return 0; }); 43 | e.RunIf([]() { return false; }); 44 | e.BeforeRun([&]() { setupCalled = true; }); 45 | }); 46 | 47 | ASSERT_FALSE(setupCalled); 48 | } 49 | -------------------------------------------------------------------------------- /test/ignore.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | TEST(Ignore, IgnoresIfReturnsFalse) 6 | { 7 | bool published = false; 8 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 9 | { 10 | e.Use([]() { return 42;}); 11 | e.Try([]() { return 1;}); 12 | e.Ignore([]() { return true; }); 13 | e.Publish([&](const Observation& o) 14 | { 15 | published = true; 16 | ASSERT_TRUE(o.Success()); 17 | }); 18 | }); 19 | 20 | ASSERT_TRUE(published); 21 | ASSERT_EQ(42, res); 22 | } 23 | 24 | TEST(Ignore, DoesNotIgnore) 25 | { 26 | bool published = false; 27 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 28 | { 29 | e.Use([]() { return 42;}); 30 | e.Try([]() { return 1;}); 31 | e.Ignore([]() { return false; }); 32 | e.Publish([&](const Observation& o) 33 | { 34 | published = true; 35 | ASSERT_FALSE(o.Success()); 36 | }); 37 | }); 38 | 39 | ASSERT_TRUE(published); 40 | ASSERT_EQ(42, res); 41 | } 42 | 43 | TEST(Ignore, IgnoresIfPredicateThrows) 44 | { 45 | bool published = false; 46 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 47 | { 48 | e.Use([]() { return 42;}); 49 | e.Try([]() { return 1;}); 50 | e.Ignore([]() { throw ""; return false; }); 51 | e.Publish([&](const Observation& o) 52 | { 53 | published = true; 54 | ASSERT_TRUE(o.Success()); 55 | }); 56 | }); 57 | 58 | ASSERT_TRUE(published); 59 | ASSERT_EQ(42, res); 60 | } 61 | -------------------------------------------------------------------------------- /test/compare.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | 6 | TEST(Compare, CustomComparison) 7 | { 8 | bool published = false; 9 | Scientist::Science("test", [&](ExperimentInterface& e) 10 | { 11 | e.Use([]() { return 42;}); 12 | e.Try([]() { return 1;}); 13 | e.Publish([&](const Observation& o) 14 | { 15 | published = true; 16 | ASSERT_TRUE(o.Success()); 17 | }); 18 | e.Compare([](const int&, const int&) { return true; }); 19 | }); 20 | 21 | ASSERT_TRUE(published); 22 | } 23 | 24 | struct Data 25 | { 26 | int Field; 27 | }; 28 | 29 | TEST(Compare, CustomComparisonForClass) 30 | { 31 | bool published = false; 32 | Scientist::Science("test", [&](ExperimentInterface& e) 33 | { 34 | e.Use([]() { return Data { 1 };}); 35 | e.Try([]() { return Data { 1 };}); 36 | e.Publish([&](const Observation& o) 37 | { 38 | published = true; 39 | ASSERT_TRUE(o.Success()); 40 | }); 41 | e.Compare([](const Data& a, const Data& b) { return a.Field == b.Field; }); 42 | }); 43 | 44 | ASSERT_TRUE(published); 45 | } 46 | 47 | TEST(Compare, CustomComparisonRequiredButMissing) 48 | { 49 | // Observation.Success must be false if comparison is missing 50 | bool published = false; 51 | Scientist::Science("test", [&](ExperimentInterface& e) 52 | { 53 | e.Use([]() { return Data { 1 };}); 54 | e.Try([]() { return Data { 1 };}); 55 | e.Publish([&](const Observation& o) 56 | { 57 | published = true; 58 | ASSERT_FALSE(o.Success()); 59 | }); 60 | }); 61 | 62 | ASSERT_TRUE(published); 63 | } 64 | -------------------------------------------------------------------------------- /test/run_if.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | TEST(RunIf, DoesNotRunCandidate) 6 | { 7 | bool candidateRun = false; 8 | bool published = false; 9 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 10 | { 11 | e.Use([]() { return 42;}); 12 | e.Try([&]() { candidateRun = true; return 1;}); 13 | e.RunIf([]() { return false; }); 14 | e.Publish([&](const Observation& o) 15 | { 16 | published = true; 17 | }); 18 | }); 19 | 20 | ASSERT_FALSE(published); 21 | ASSERT_FALSE(candidateRun); 22 | ASSERT_EQ(42, res); 23 | } 24 | 25 | TEST(RunIf, DoesNotRunCandidateIfAnyFalse) 26 | { 27 | bool candidateRun = false; 28 | bool published = false; 29 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 30 | { 31 | e.Use([]() { return 42;}); 32 | e.Try([&]() { candidateRun = true; return 1;}); 33 | e.RunIf([]() { return true; }); 34 | e.RunIf([]() { return false; }); 35 | e.Publish([&](const Observation& o) 36 | { 37 | published = true; 38 | }); 39 | }); 40 | 41 | ASSERT_FALSE(published); 42 | ASSERT_FALSE(candidateRun); 43 | ASSERT_EQ(42, res); 44 | } 45 | 46 | TEST(RunIf, DoesNotRunCandidateIfPredicateThrows) 47 | { 48 | bool candidateRun = false; 49 | bool published = false; 50 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 51 | { 52 | e.Use([]() { return 42;}); 53 | e.Try([&]() { candidateRun = true; return 1;}); 54 | e.RunIf([]() { return true; }); 55 | e.RunIf([]() { throw ""; return true; }); 56 | e.Publish([&](const Observation& o) 57 | { 58 | published = true; 59 | }); 60 | }); 61 | 62 | ASSERT_FALSE(published); 63 | ASSERT_FALSE(candidateRun); 64 | ASSERT_EQ(42, res); 65 | } 66 | -------------------------------------------------------------------------------- /test/context.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | 6 | TEST(Context, FindsValueForKey) 7 | { 8 | bool published = false; 9 | Scientist::Science("test", [&](ExperimentInterface& e) 10 | { 11 | e.Use([]() { return 42;}); 12 | e.Try([]() { return 0;}); 13 | e.Context("key", "value"); 14 | e.Publish([&](const Observation& o) 15 | { 16 | published = true; 17 | std::pair c = o.Context("key"); 18 | 19 | ASSERT_TRUE(c.first); 20 | ASSERT_EQ("value", c.second); 21 | }); 22 | }); 23 | 24 | ASSERT_TRUE(published); 25 | } 26 | 27 | TEST(Context, NonExistingKey) 28 | { 29 | bool published = false; 30 | Scientist::Science("test", [&](ExperimentInterface& e) 31 | { 32 | e.Use([]() { return 42;}); 33 | e.Try([]() { return 0;}); 34 | e.Publish([&](const Observation& o) 35 | { 36 | published = true; 37 | std::pair c = o.Context("nonexisting"); 38 | 39 | ASSERT_FALSE(c.first); 40 | }); 41 | }); 42 | 43 | ASSERT_TRUE(published); 44 | } 45 | 46 | TEST(Context, ReturnsKeys) 47 | { 48 | bool published = false; 49 | Scientist::Science("test", [&](ExperimentInterface& e) 50 | { 51 | e.Use([]() { return 42;}); 52 | e.Try([]() { return 0;}); 53 | e.Context("key1", "value"); 54 | e.Context("key2", "value"); 55 | e.Publish([&](const Observation& o) 56 | { 57 | published = true; 58 | 59 | std::vector keys = o.ContextKeys(); 60 | std::sort(keys.begin(), keys.end()); 61 | 62 | ASSERT_EQ(2, keys.size()); 63 | ASSERT_EQ("key1", keys.front()); 64 | ASSERT_EQ("key2", keys.back()); 65 | }); 66 | }); 67 | 68 | ASSERT_TRUE(published); 69 | } 70 | -------------------------------------------------------------------------------- /test/cleanup.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | TEST(Cleanup, Cleanup) 6 | { 7 | bool published = false; 8 | Scientist::Science("test", [&](ExperimentInterface& e) 9 | { 10 | e.Use([]() { return 42;}); 11 | e.Try([]() { return 1;}); 12 | e.Publish([&](const Observation& o) 13 | { 14 | published = true; 15 | ASSERT_FALSE(o.Success()); 16 | ASSERT_EQ(o.ControlResult(), 84); 17 | ASSERT_EQ(o.CandidateResult(), 2); 18 | }); 19 | e.Cleanup([](const int& value ) { return 2 * value; }); 20 | }); 21 | 22 | ASSERT_TRUE(published); 23 | } 24 | 25 | struct Data 26 | { 27 | int Field; 28 | }; 29 | 30 | TEST(Cleanup, CleanupClass) 31 | { 32 | bool published = false; 33 | Scientist::Science("test", [&](ExperimentInterface& e) 34 | { 35 | e.Use([]() { return Data { 42 };}); 36 | e.Try([]() { return Data { 1 };}); 37 | e.Publish([&](const Observation& o) 38 | { 39 | published = true; 40 | ASSERT_FALSE(o.Success()); 41 | ASSERT_EQ(o.ControlResult(), 42); 42 | ASSERT_EQ(o.CandidateResult(), 1); 43 | 44 | }); 45 | e.Cleanup([](const Data& d ) { return d.Field; }); 46 | e.Compare([](const Data& a, const Data& b) { return a.Field == b.Field;}); 47 | }); 48 | 49 | ASSERT_TRUE(published); 50 | } 51 | 52 | TEST(Cleanup, CleanupRequiredButMissingDoesNotCrash) 53 | { 54 | bool published = false; 55 | Scientist::Science("test", [&](ExperimentInterface& e) 56 | { 57 | e.Use([]() { return Data { 42 };}); 58 | e.Try([]() { return Data { 1 };}); 59 | e.Publish([&](const Observation& o) 60 | { 61 | published = true; 62 | ASSERT_FALSE(o.Success()); 63 | ASSERT_EQ(o.ControlResult(), 0 /* default value for int */); 64 | ASSERT_EQ(o.CandidateResult(), 0 /* default value for int */); 65 | }); 66 | e.Compare([](const Data& a, const Data& b) { return a.Field == b.Field;}); 67 | }); 68 | 69 | ASSERT_TRUE(published); 70 | } 71 | -------------------------------------------------------------------------------- /test/multiple_candidates.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | TEST(MultipleCandidates, RunsAllCandidates) 6 | { 7 | std::size_t ran = 0x0; 8 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 9 | { 10 | e.Use([]() { return 42; }); 11 | e.Try([&]() { ran |= 0x1; return 42; }); 12 | e.Try([&]() { ran |= 0x2; return 42; }); 13 | e.Try([&]() { ran |= 0x4; return 42; }); 14 | e.Publish([&](const Observation& o) 15 | { 16 | ASSERT_TRUE(o.Success()); 17 | }); 18 | }); 19 | 20 | ASSERT_EQ(0x7, ran); 21 | ASSERT_EQ(42, res); 22 | } 23 | 24 | TEST(MultipleCandidates, ReportsSuccessWhenAllCandidatesReturnCorrectAnswer) 25 | { 26 | std::size_t number = 0; 27 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 28 | { 29 | e.Use([]() { return 42; }); 30 | e.Try([]() { return 42; }); 31 | e.Try([]() { return 42; }); 32 | e.Try([]() { return 42; }); 33 | e.Publish([&](const Observation& o) 34 | { 35 | number = o.NumberOfCandidates(); 36 | ASSERT_TRUE(o.Success()); 37 | }); 38 | }); 39 | 40 | ASSERT_EQ(3, number); 41 | ASSERT_EQ(42, res); 42 | } 43 | 44 | TEST(MultipleCandidates, ReportsFailureIfOneCandidateReturnsWrongAnswer) 45 | { 46 | std::size_t number = 0; 47 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 48 | { 49 | e.Use([]() { return 42; }); 50 | e.Try([]() { return 42; }); 51 | e.Try([]() { return 42; }); 52 | e.Try([]() { return 41; }); 53 | e.Publish([&](const Observation& o) 54 | { 55 | number = o.NumberOfCandidates(); 56 | 57 | ASSERT_FALSE(o.Success()); 58 | }); 59 | }); 60 | 61 | ASSERT_EQ(3, number); 62 | ASSERT_EQ(42, res); 63 | } 64 | 65 | TEST(MultipleCandidates, CandidateAnswersInOrderOfCandidateAddition) 66 | { 67 | std::size_t number = 0; 68 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 69 | { 70 | e.Use([]() { return 42; }); 71 | e.Try([]() { return 1; }); 72 | e.Try([]() { return 2; }); 73 | e.Try([]() { return 3; }); 74 | e.Publish([&](const Observation& o) 75 | { 76 | number = o.NumberOfCandidates(); 77 | 78 | ASSERT_EQ(1, o.CandidateResult(0)); 79 | ASSERT_EQ(2, o.CandidateResult(1)); 80 | ASSERT_EQ(3, o.CandidateResult(2)); 81 | 82 | ASSERT_FALSE(o.Success()); 83 | }); 84 | }); 85 | 86 | ASSERT_EQ(3, number); 87 | ASSERT_EQ(42, res); 88 | } 89 | 90 | TEST(MultipleCandidates, ReportsFailureIfOneCandidateThrows) 91 | { 92 | std::size_t number = 0; 93 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 94 | { 95 | e.Use([]() { return 42; }); 96 | e.Try([]() { return 42; }); 97 | e.Try([]() { throw std::exception(); return 42; }); 98 | e.Try([]() { return 42; }); 99 | e.Publish([&](const Observation& o) 100 | { 101 | number = o.NumberOfCandidates(); 102 | ASSERT_FALSE(o.Success()); 103 | }); 104 | }); 105 | 106 | ASSERT_EQ(3, number); 107 | ASSERT_EQ(42, res); 108 | } 109 | -------------------------------------------------------------------------------- /test/publish.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "scientist.hh" 4 | 5 | TEST(Publish, PublishesObservationFields) 6 | { 7 | bool published = false; 8 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 9 | { 10 | e.Use([]() { return 42;}); 11 | e.Try([]() { return 42;}); 12 | e.Publish([&](const Observation& o) 13 | { 14 | published = true; 15 | ASSERT_EQ("test", o.Name()); 16 | ASSERT_TRUE(o.Success()); 17 | ASSERT_FALSE(o.CandidateException()); 18 | ASSERT_FALSE(o.ControlException()); 19 | ASSERT_GT(o.CandidateDuration().count(), 0); 20 | ASSERT_GT(o.ControlDuration().count(), 0); 21 | ASSERT_EQ(o.CandidateResult(), 42); 22 | ASSERT_EQ(o.ControlResult(), 42); 23 | }); 24 | }); 25 | 26 | ASSERT_TRUE(published); 27 | ASSERT_EQ(42, res); 28 | } 29 | 30 | TEST(Publish, PublishesResultIfExperimentUnsuccessful) 31 | { 32 | bool published = false; 33 | int res = Scientist::Science("test", [&](ExperimentInterface& e) 34 | { 35 | e.Use([]() { return 42;}); 36 | e.Try([]() { return 0;}); 37 | e.Publish([&](const Observation& o) 38 | { 39 | published = true; 40 | ASSERT_FALSE(o.Success()); 41 | ASSERT_EQ(o.CandidateResult(), 0); 42 | ASSERT_EQ(o.ControlResult(), 42); 43 | }); 44 | }); 45 | 46 | ASSERT_TRUE(published); 47 | ASSERT_EQ(42, res); 48 | } 49 | 50 | TEST(Publish, PublishesObservationIfCandidateThrows) 51 | { 52 | bool published = false; 53 | 54 | int res = Scientist::Science("", [&](ExperimentInterface& e) 55 | { 56 | e.Use([]() { return 42;}); 57 | e.Try([]() { throw ""; return 0; }); 58 | e.Publish([&](const Observation& o) 59 | { 60 | published = true; 61 | ASSERT_FALSE(o.Success()); 62 | ASSERT_TRUE(o.CandidateException()); 63 | ASSERT_FALSE(o.ControlException()); 64 | ASSERT_GT(o.CandidateDuration().count(), 0); 65 | ASSERT_GT(o.ControlDuration().count(), 0); 66 | }); 67 | }); 68 | 69 | ASSERT_TRUE(published); 70 | ASSERT_EQ(42, res); 71 | } 72 | 73 | TEST(Publish, RethrowsControlException) 74 | { 75 | ASSERT_THROW(Scientist::Science("", [](ExperimentInterface& e) 76 | { 77 | e.Use([]() { throw std::string(); return 42;}); 78 | e.Try([]() { return 0; }); 79 | }), std::string); 80 | } 81 | 82 | TEST(Publish, SuccessIfBothThrow) 83 | { 84 | bool published = false; 85 | ASSERT_ANY_THROW(Scientist::Science("", [&](ExperimentInterface& e) 86 | { 87 | e.Use([]() { throw ""; return 0; }); 88 | e.Try([]() { throw ""; return 42; }); 89 | e.Publish([&](const Observation& o) 90 | { 91 | published = true; 92 | ASSERT_TRUE(o.Success()); 93 | }); 94 | })); 95 | 96 | ASSERT_TRUE(published); 97 | } 98 | 99 | TEST(Publish, MultiplePublishers) 100 | { 101 | bool aPublished = false; 102 | bool bPublished = false; 103 | Scientist::Science("test", [&](ExperimentInterface& e) 104 | { 105 | e.Use([]() { return 42;}); 106 | e.Try([]() { return 42;}); 107 | e.Publish([&](const Observation& o) 108 | { 109 | aPublished = true; 110 | }); 111 | e.Publish([&](const Observation& o) 112 | { 113 | bPublished = true; 114 | }); 115 | }); 116 | 117 | ASSERT_TRUE(aPublished); 118 | ASSERT_TRUE(bPublished); 119 | } 120 | 121 | TEST(Publish, AsyncPublishes) 122 | { 123 | bool published = false; 124 | Scientist::Science("test", [&](ExperimentInterface& e) 125 | { 126 | e.Use([]() { return 42;}); 127 | e.Try([]() { return 42;}); 128 | e.PublishAsync([&](const Observation& o) 129 | { 130 | published = true; 131 | }); 132 | }); 133 | 134 | while (!published); 135 | ASSERT_TRUE(published); 136 | } 137 | 138 | TEST(Publish, AsyncPublishDoesNotBlock) 139 | { 140 | bool block = true; 141 | int value = Scientist::Science("test", [&](ExperimentInterface& e) 142 | { 143 | e.Use([]() { return 42;}); 144 | e.Try([]() { return 42;}); 145 | e.PublishAsync([&](const Observation& o) 146 | { 147 | while(block); 148 | }); 149 | }); 150 | 151 | ASSERT_EQ(42, value); 152 | block = false; 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scientist 2 | 3 | A C++ library for carefully refactoring critical paths. This is a port of Github's Ruby [Scientist library](http://githubengineering.com/scientist/). 4 | 5 | # Usage 6 | 7 | Currently it follows closely the original API (and the [.NET version](https://github.com/Haacked/Scientist.net)). 8 | 9 | Here is how a simple experiment is defined in C++: 10 | 11 | ```cpp 12 | #include 13 | 14 | int res = Scientist::Science("do-stuff", [](ExperimentInterface& e) 15 | { 16 | e.Use([]() { return do_stuff_legacy();}); 17 | e.Try([]() { return do_stuff();}); 18 | }); 19 | 20 | ``` 21 | 22 | Scientist has most of the features of the Ruby library: 23 | 24 | - It decides whether or not to run the `Try` function 25 | - Randomizes the order of `Try`and `Use` functions 26 | - Measures the durations of both functions 27 | - Compares the results 28 | - Swallows exceptions from `Try` function 29 | - Publishes all observations 30 | 31 | # Installation 32 | 33 | Currently there is only a [single header](include/scientist.hh) that is required. Copy `scientist.hh` somewhere and point the compiler at the location. 34 | 35 | # Experiments 36 | 37 | Experiments are described using the following interface: 38 | 39 | ```cpp 40 | template 41 | class ExperimentInterface 42 | { 43 | public: 44 | virtual void BeforeRun(Setup setup) = 0; 45 | virtual void Use(Operation control) = 0; 46 | virtual void Try(Operation candidate) = 0; 47 | virtual void Ignore(Predicate ignore) = 0; 48 | virtual void RunIf(Predicate runIf) = 0; 49 | virtual void Publish(Publisher publisher) = 0; 50 | virtual void PublishAsync(Publisher publisher) = 0; 51 | virtual void Compare(Compare compare) = 0; 52 | virtual void Cleanup(Transform cleanup) = 0; 53 | virtual void Context(std::string key, std::string value) = 0; 54 | }; 55 | 56 | using Operation = std::function; 57 | using Compare = std::function; 58 | using Predicate = std::function; 59 | using Publisher = std::function&)>; 60 | using Transform = std::function; 61 | using Setup = std::function; 62 | ``` 63 | 64 | Template parameter `T` denotes the result type of the operations, and `U` the possible cleaned result type (See [Cleanup](#control-the-stored-results)) 65 | 66 | # Observations 67 | 68 | For each experiment run, Scientist returns an observation in the following form: 69 | 70 | ```cpp 71 | template 72 | class Observation 73 | { 74 | const std::string& Name() const; 75 | bool Success() const; 76 | 77 | std::chrono::nanoseconds ControlDuration() const; 78 | std::exception_ptr ControlException() const; 79 | T ControlResult() const; 80 | 81 | std::chrono::nanoseconds CandidateDuration() const; 82 | std::exception_ptr CandidateException() const; 83 | T CandidateResult() const; 84 | 85 | std::list ContextKeys() const; 86 | std::pair Context(std::string key) const; 87 | }; 88 | 89 | ``` 90 | 91 | An experiment is successful if: 92 | 93 | - Control and candidate return identical results (according to the equal operator or given custom comparator) 94 | - They both throw an exception 95 | - `Try` is ignored (See [Ignore](#ignore-known-issues)) 96 | 97 | To gather observations, register `Publish` function: 98 | 99 | ```cpp 100 | int res = Scientist::Science("", [](ExperimentInterface& e) 101 | { 102 | ... 103 | e.Publish([](const Observation& o) 104 | { 105 | ... 106 | } 107 | }); 108 | 109 | ``` 110 | 111 | There can be a number of registered publishers. 112 | All `Publish` functions are executed before the control result is returned. 113 | There exists a asynchronous version, `PublishAsync`, for long running operations. 114 | 115 | See [publish tests](test/publish.cc) for more examples. 116 | 117 | # Comparison 118 | 119 | You can specify a custom comparison function: 120 | 121 | ```cpp 122 | int res = Scientist::Science("", [](ExperimentInterface& e) 123 | { 124 | ... 125 | e.Compare([](const int&, const int&) { ... }); 126 | 127 | }); 128 | 129 | ``` 130 | 131 | Comparison function is required if the result type does not have an equal operator. 132 | Otherwise, all experiments fail silently. 133 | 134 | See [comparison tests](test/compare.cc) for more examples. 135 | 136 | # Control the stored results 137 | 138 | The `Observation` contains results from both operations, which might not be ideal in all cases. 139 | You can register a `Cleanup` function, which maps the returned data into desired form. 140 | This cleaned value is available in observations. 141 | 142 | ```cpp 143 | struct Data 144 | { 145 | int Field; 146 | }; 147 | 148 | Scientist::Science("", [](ExperimentInterface& e) 149 | { 150 | e.Use([]() { return Data { 0 };}); 151 | e.Try([]() { return Data { 1 };}); 152 | e.Publish([&](const Observation& o) 153 | { 154 | ... 155 | 156 | }); 157 | e.Cleanup([](const Data& d ) { return d.Field; }); 158 | e.Compare([]( const Data& a, const Data& b) { return a.Field == b.Field;}); 159 | }); 160 | ``` 161 | The second template parameter denotes the type of the cleaned value (`int` in this case). 162 | If the cleanup is required (i.e. `T` != `U`) but is not given, `Observation` has the 163 | default constructed value `U()`. 164 | 165 | See [cleanup tests](test/cleanup.cc) for more examples. 166 | 167 | # Ignore known issues 168 | 169 | You can ignore some test results with `Ignore` function. 170 | Experiment is ignored if any `Ignore` function returns `true` or throws an exception. 171 | Exceptions are swallowed. 172 | 173 | ```cpp 174 | int res = Scientist::Science("", [&](ExperimentInterface& e) 175 | { 176 | e.Use([]() { return 1;}); 177 | e.Try([]() { return 1;}); 178 | e.Ignore([]() { return true; }); 179 | }); 180 | ``` 181 | 182 | See [Ignore tests](test/ignore.cc) for more examples. 183 | 184 | 185 | # Disable experiments 186 | 187 | Experiments can be disabled with `RunIf` function. 188 | If at least one of them (or there is none) return `true`, the experiment is run. 189 | If an exception is caught from any `RunIf` function, the experiment is not run. 190 | 191 | ```cpp 192 | int res = Scientist::Science("", [&](ExperimentInterface& e) 193 | { 194 | e.Use([]() { return 42;}); 195 | e.Try([]() { return 1;}); 196 | e.RunIf([]() { return true; }); 197 | }); 198 | ``` 199 | 200 | See [RunIf tests](test/run_if.cc) for more examples. 201 | 202 | # Context 203 | 204 | You can add contextual information to observations as string key-value pairs. 205 | Writing same key multiple times overwrites the previous value. 206 | This information can be queried from `Observation` as shown below: 207 | 208 | ```cpp 209 | Scientist::Science("", [](ExperimentInterface& e) 210 | { 211 | e.Use([]() { return 42;}); 212 | e.Try([]() { return 0;}); 213 | e.Context("key1", "value"); 214 | e.Context("key2", "value"); 215 | e.Publish([](const Observation& o) 216 | { 217 | for (std::string key: o.ContextKeys()) 218 | { 219 | std::pair value = o.Context(key); 220 | 221 | if (value.first) 222 | std::cout << key << " : " << value.second << std::endl; 223 | } 224 | }); 225 | }); 226 | ``` 227 | 228 | The first value (`bool`) of the returned pair from `Observation::Context` is `true` if the requested key was found. 229 | 230 | # Expensive Setup 231 | 232 | If an experiment requires expensive setup that should only occur once for enabled experiments, register a setup with 233 | `BeforeRun` function: 234 | 235 | ```cpp 236 | Scientist::Science("", [&](ExperimentInterface& e) 237 | { 238 | ... 239 | e.BeforeRun([&]() { ... }); 240 | }); 241 | ``` 242 | 243 | You can register any number of setup functions, and they are run in the order of registration. 244 | These functions are only run if the experiment is enabled (See [RunIf](#disable-experiments)). 245 | 246 | See [BeforeRun tests](test/before_run.cc) for more examples. 247 | 248 | 249 | # Exceptions 250 | 251 | Exceptions from both `Try` and `Use` functions are caught and stored in the `Observation`. 252 | Exceptions from `Use` function are rethrown after all `Publish` functions have been called. 253 | 254 | Exceptions can be rethrown with `std::rethrow_exception` and handled accordingly. 255 | 256 | # Tests 257 | 258 | Tests are written with Google Test. 259 | To run tests, install CMake and run the following commands: 260 | 261 | ```bash 262 | cd path/to/scientist 263 | git submodule update --init 264 | mkdir build 265 | cd build 266 | cmake .. 267 | make tests 268 | ./tests 269 | ``` 270 | 271 | # TODO 272 | 273 | - [ ] Come up with a better name 274 | - [ ] Clean up the SFINAE magic if possible 275 | - [ ] Finalize the API 276 | - [ ] Allow more than one `Try` function 277 | - [ ] Define an interface for publishers and register them separately (See [IResultPublisher](https://github.com/Haacked/Scientist.net/blob/master/src/Scientist/IResultPublisher.cs) in Scientist.NET) 278 | - [x] Add context to experiments (See [Ruby version](https://github.com/github/scientist/blob/master/README.md#adding-context)) 279 | -------------------------------------------------------------------------------- /include/scientist.hh: -------------------------------------------------------------------------------- 1 | #ifndef SCIENTIST_HH 2 | #define SCIENTIST_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | template 15 | using Operation = std::function; 16 | 17 | template 18 | using Compare = std::function; 19 | 20 | using Predicate = std::function; 21 | 22 | template 23 | class Observation; 24 | template 25 | using Publisher = std::function&)>; 26 | 27 | template 28 | using Transform = std::function; 29 | 30 | using Setup = std::function; 31 | 32 | template 33 | class Observation 34 | { 35 | public: 36 | Observation(std::string name, bool success, std::unordered_map context, 37 | std::tuple control, 38 | std::vector> candidates) : 39 | name_(std::move(name)), success_(success), context_(context), 40 | control_(control), 41 | candidates_(candidates) 42 | { 43 | } 44 | 45 | const std::string& Name() const { return name_; } 46 | bool Success() const { return success_; } 47 | 48 | std::chrono::nanoseconds ControlDuration() const { return std::get<1>(control_); } 49 | std::exception_ptr ControlException() const { return std::get<2>(control_); } 50 | T ControlResult() const { return std::get<0>(control_); } 51 | 52 | std::size_t NumberOfCandidates() const 53 | { 54 | return candidates_.size(); 55 | } 56 | 57 | std::vector CandidateDurations() const 58 | { 59 | std::vector result; 60 | 61 | result.reserve(candidates_.size()); 62 | 63 | for (const auto &candidate : candidates_) 64 | { 65 | result.push_back(std::get<1>(candidate)); 66 | } 67 | 68 | return result; 69 | } 70 | 71 | std::chrono::nanoseconds CandidateDuration(const std::size_t index = 0) const 72 | { 73 | std::chrono::nanoseconds result; 74 | 75 | if (index < candidates_.size()) 76 | { 77 | result = std::get<1>(candidates_.at(index)); 78 | } 79 | 80 | return result; 81 | } 82 | 83 | std::vector CandidateExceptions() const 84 | { 85 | std::vector result; 86 | 87 | result.reserve(candidates_.size()); 88 | 89 | for (const auto &candidate : candidates_) 90 | { 91 | result.push_back(std::get<2>(candidate)); 92 | } 93 | 94 | return result; 95 | } 96 | 97 | std::exception_ptr CandidateException(const std::size_t index = 0) const 98 | { 99 | std::exception_ptr result; 100 | 101 | if (index < candidates_.size()) 102 | { 103 | result = std::get<2>(candidates_.at(index)); 104 | } 105 | 106 | return result; 107 | } 108 | 109 | std::vector CandidateResults() const 110 | { 111 | std::vector result; 112 | 113 | result.reserve(candidates_.size()); 114 | 115 | for (const auto &candidate : candidates_) 116 | { 117 | result.push_back(std::get<0>(candidate)); 118 | } 119 | 120 | return result; 121 | } 122 | 123 | T CandidateResult(const std::size_t index = 0) const 124 | { 125 | T result; 126 | 127 | if (index < candidates_.size()) 128 | { 129 | result = std::get<0>(candidates_.at(index)); 130 | } 131 | 132 | return result; 133 | } 134 | 135 | std::vector ContextKeys() const 136 | { 137 | std::vector keys; 138 | 139 | for (const auto& p : context_) 140 | { 141 | keys.push_back(p.first); 142 | } 143 | 144 | return keys; 145 | } 146 | 147 | std::pair Context(std::string key) const 148 | { 149 | std::unordered_map::const_iterator ret = context_.find(key); 150 | 151 | std::pair result(false, std::string()); 152 | 153 | if (ret != context_.cend()) 154 | { 155 | result = std::make_pair(true, ret->second); 156 | } 157 | 158 | return result; 159 | } 160 | 161 | private: 162 | std::string name_; 163 | bool success_; 164 | std::unordered_map context_; 165 | 166 | std::tuple control_; 167 | 168 | std::vector> candidates_; 169 | }; 170 | 171 | template 172 | struct has_operator_equal_impl 173 | { 174 | template 175 | static auto test(U*) -> decltype(std::declval() == std::declval()); 176 | template 177 | static auto test(...) -> std::false_type; 178 | 179 | using type = typename std::is_same(0))>::type; 180 | }; 181 | 182 | template 183 | struct has_operator_equal : has_operator_equal_impl::type {}; 184 | 185 | template 186 | class Experiment 187 | { 188 | public: 189 | using Measurement = std::tuple; 190 | 191 | Experiment(std::string name, std::unordered_map context, 192 | std::list<::Setup> setups, 193 | Operation control, std::vector> candidates, 194 | std::list ignorePredicates, 195 | std::list runIfPredicates, std::list> publishers, 196 | std::list> asyncPublishers, Transform cleanup, 197 | Compare compare) : 198 | name_(name), context_(context), setups_(setups), control_(control), candidates_(candidates), 199 | ignorePredicates_(ignorePredicates), runIfPredicates_(runIfPredicates), 200 | publishers_(publishers), asyncPublishers_(asyncPublishers), 201 | compare_(compare), cleanup_(cleanup) 202 | { 203 | } 204 | 205 | T Run() const 206 | { 207 | if (!RunCandidate()) 208 | return control_(); 209 | 210 | Setup(); 211 | 212 | std::tuple> result = MeasureBoth(); 213 | Observation observation = std::get<1>(result); 214 | T controlResult = std::get<0>(result); 215 | 216 | Publish(observation); 217 | 218 | if (observation.ControlException()) 219 | { 220 | std::rethrow_exception(observation.ControlException()); 221 | } 222 | 223 | return controlResult; 224 | } 225 | 226 | private: 227 | 228 | void Setup() const 229 | { 230 | for (::Setup s: setups_) 231 | { 232 | s(); 233 | } 234 | } 235 | 236 | std::tuple> MeasureBoth() const 237 | { 238 | std::int32_t index = -1; 239 | 240 | std::vector indices; 241 | 242 | indices.resize(candidates_.size() + 1); 243 | 244 | std::generate_n(indices.begin(), candidates_.size() + 1, [&index]() { return ++index; }); 245 | 246 | std::random_device rd; 247 | std::mt19937 mt(rd()); 248 | std::shuffle(indices.begin(), indices.end(), mt); 249 | 250 | Measurement control; 251 | std::vector candidates; 252 | 253 | candidates.resize(candidates_.size()); 254 | 255 | for (const auto i : indices) 256 | { 257 | if (i == index) 258 | { 259 | control = Measure(control_); 260 | } 261 | else 262 | { 263 | candidates[i] = Measure(candidates_[i]); 264 | } 265 | } 266 | 267 | return std::make_tuple(std::get<0>(control), CreateObservation(control, candidates)); 268 | } 269 | 270 | Observation CreateObservation(Measurement control, std::vector candidates) const 271 | { 272 | T controlResult = std::get<0>(control); 273 | bool controlThrew = static_cast(std::get<2>(control)); 274 | 275 | bool success = std::all_of(candidates.cbegin(), candidates.cend(), [=](Measurement measurement) 276 | { 277 | T result = std::get<0>(measurement); 278 | bool threw = static_cast(std::get<2>(measurement)); 279 | 280 | return ((compare_ && compare_(controlResult, result) && (controlThrew == threw)) || Ignored()); 281 | }); 282 | 283 | Observation result(name_, success, context_, Cleanup(control), Cleanup(candidates)); 284 | 285 | return result; 286 | } 287 | 288 | // TODO: investigate if this actually necessary 289 | template 290 | typename std::enable_if::value, std::tuple>::type 291 | Cleanup(const std::tuple &value) const 292 | { 293 | if (!cleanup_) 294 | { 295 | return value; 296 | } 297 | 298 | std::tuple result(cleanup_(std::get<0>(value)), std::get<1>(value), std::get<2>(value)); 299 | 300 | return result; 301 | } 302 | 303 | template 304 | typename std::enable_if::value == false, std::tuple>::type 305 | Cleanup(const std::tuple &value) const 306 | { 307 | if (!cleanup_) 308 | { 309 | return std::tuple(U(), std::get<1>(value), std::get<2>(value)); 310 | } 311 | 312 | std::tuple result(cleanup_(std::get<0>(value)), std::get<1>(value), std::get<2>(value)); 313 | 314 | return result; 315 | } 316 | 317 | // TODO: investigate if this actually necessary 318 | template 319 | typename std::enable_if::value, std::vector>>::type 320 | Cleanup(const std::vector> &value) const 321 | { 322 | std::vector> result; 323 | 324 | result.resize(value.size()); 325 | 326 | if (!cleanup_) 327 | { 328 | result = value; 329 | } 330 | else 331 | { 332 | std::transform(value.begin(), value.end(), result.begin(), [this](std::tuple item) 333 | { 334 | std::tuple transformed(cleanup_(std::get<0>(item)), std::get<1>(item), std::get<2>(item)); 335 | 336 | return transformed; 337 | }); 338 | } 339 | 340 | return result; 341 | } 342 | 343 | template 344 | typename std::enable_if::value == false, std::vector>>::type 345 | Cleanup(const std::vector> &value) const 346 | { 347 | std::vector> result; 348 | 349 | result.resize(value.size()); 350 | 351 | std::function(std::tuple)> transformer; 352 | 353 | if (!cleanup_) 354 | { 355 | transformer = [](std::tuple item) 356 | { 357 | std::tuple transformed(U(), std::get<1>(item), std::get<2>(item)); 358 | 359 | return transformed; 360 | }; 361 | } 362 | else 363 | { 364 | transformer = [this](std::tuple item) 365 | { 366 | std::tuple transformed(cleanup_(std::get<0>(item)), std::get<1>(item), std::get<2>(item)); 367 | 368 | return transformed; 369 | }; 370 | } 371 | 372 | std::transform(value.begin(), value.end(), result.begin(), transformer); 373 | 374 | return result; 375 | } 376 | 377 | Measurement Measure(Operation f) const 378 | { 379 | T result; 380 | std::exception_ptr exception; 381 | auto start = std::chrono::steady_clock::now(); 382 | 383 | try 384 | { 385 | result = f(); 386 | } 387 | catch(...) 388 | { 389 | exception = std::current_exception(); 390 | } 391 | 392 | auto end = std::chrono::steady_clock::now(); 393 | return std::make_tuple(result, std::chrono::nanoseconds(end - start), exception); 394 | } 395 | 396 | bool Ignored() const 397 | { 398 | bool result = true; 399 | 400 | try 401 | { 402 | result = std::any_of(ignorePredicates_.begin(), ignorePredicates_.end(), [](Predicate p) { return p(); }); 403 | } 404 | catch(...) 405 | { 406 | } 407 | 408 | return result; 409 | } 410 | 411 | bool RunCandidate() const 412 | { 413 | bool result = false; 414 | 415 | try 416 | { 417 | result = std::all_of(runIfPredicates_.begin(), runIfPredicates_.end(), [](Predicate p) { return p(); }); 418 | } 419 | catch(...) 420 | { 421 | } 422 | 423 | return result; 424 | 425 | } 426 | 427 | void Publish(const Observation& observation) const 428 | { 429 | for (Publisher p: publishers_) 430 | p(observation); 431 | for (Publisher p: asyncPublishers_) 432 | std::thread(std::bind(p, observation)).detach(); 433 | } 434 | 435 | std::string name_; 436 | std::unordered_map context_; 437 | std::list<::Setup> setups_; 438 | Operation control_; 439 | std::vector> candidates_; 440 | std::list ignorePredicates_; 441 | std::list runIfPredicates_; 442 | std::list> publishers_; 443 | std::list> asyncPublishers_; 444 | Compare compare_; 445 | Transform cleanup_; 446 | }; 447 | 448 | template 449 | class ExperimentInterface 450 | { 451 | public: 452 | virtual ~ExperimentInterface() {} 453 | virtual void BeforeRun(Setup setup) = 0; 454 | virtual void Use(Operation control) = 0; 455 | virtual void Try(Operation candidate) = 0; 456 | virtual void Ignore(Predicate ignore) = 0; 457 | virtual void RunIf(Predicate runIf) = 0; 458 | virtual void Publish(Publisher publisher) = 0; 459 | virtual void PublishAsync(Publisher publisher) = 0; 460 | virtual void Compare(Compare compare) = 0; 461 | virtual void Cleanup(Transform cleanup) = 0; 462 | virtual void Context(std::string key, std::string value) = 0; 463 | }; 464 | 465 | template 466 | class ExperimentBuilder : public ExperimentInterface 467 | { 468 | public: 469 | ExperimentBuilder(std::string name) : name_(std::move(name)) {} 470 | virtual ~ExperimentBuilder() {} 471 | 472 | virtual void BeforeRun(Setup setup) override 473 | { 474 | setups_.push_back(setup); 475 | } 476 | 477 | virtual void Use(Operation control) override 478 | { 479 | control_ = control; 480 | } 481 | 482 | virtual void Try(Operation candidate) override 483 | { 484 | candidates_.push_back(candidate); 485 | } 486 | 487 | virtual void Ignore(Predicate ignore) override 488 | { 489 | ignorePredicates_.push_back(ignore); 490 | } 491 | 492 | virtual void RunIf(Predicate runIf) override 493 | { 494 | runIfPredicates_.push_back(runIf); 495 | } 496 | 497 | virtual void Publish(Publisher publisher) override 498 | { 499 | publishers_.push_back(publisher); 500 | } 501 | 502 | virtual void PublishAsync(Publisher publisher) override 503 | { 504 | asyncPublishers_.push_back(publisher); 505 | } 506 | 507 | virtual void Compare(::Compare compare) override 508 | { 509 | compare_ = compare; 510 | } 511 | 512 | virtual void Cleanup(Transform cleanup) override 513 | { 514 | cleanup_ = cleanup; 515 | } 516 | 517 | virtual void Context(std::string key, std::string value) override 518 | { 519 | context_[key] = value; 520 | } 521 | 522 | template 523 | typename std::enable_if::value, Experiment>::type 524 | Build() const 525 | { 526 | return Experiment(name_, context_, setups_, control_, candidates_, ignorePredicates_, runIfPredicates_, 527 | publishers_, asyncPublishers_, cleanup_, compare_ ? compare_ : std::equal_to()); 528 | } 529 | 530 | template 531 | typename std::enable_if::value, Experiment>::type 532 | Build() const 533 | { 534 | return Experiment(name_, context_, setups_, control_, candidates_, ignorePredicates_, runIfPredicates_, 535 | publishers_, asyncPublishers_, cleanup_, compare_); 536 | } 537 | private: 538 | std::string name_; 539 | std::list setups_; 540 | Operation control_; 541 | std::vector> candidates_; 542 | std::list ignorePredicates_; 543 | std::list runIfPredicates_; 544 | std::list> publishers_; 545 | std::list> asyncPublishers_; 546 | Transform cleanup_; 547 | ::Compare compare_; 548 | std::unordered_map context_; 549 | }; 550 | 551 | template 552 | class Scientist 553 | { 554 | public: 555 | 556 | static T Science(std::string name, std::function&)> experimentDefinition) 557 | { 558 | ExperimentBuilder builder(name); 559 | experimentDefinition(builder); 560 | Experiment experiment = builder.Build(); 561 | return experiment.Run(); 562 | } 563 | }; 564 | 565 | #endif //SCIENTIST_HH 566 | --------------------------------------------------------------------------------