├── Chapter01 └── MereTDD │ ├── Test.h │ └── tests │ ├── Creation.cpp │ └── main.cpp ├── Chapter02 └── MereTDD │ ├── Test.h │ └── tests │ ├── Creation.cpp │ └── main.cpp ├── Chapter03 └── MereTDD │ ├── Test.h │ └── tests │ ├── Creation.cpp │ └── main.cpp ├── Chapter04 └── MereTDD │ ├── Test.h │ └── tests │ ├── Confirm.cpp │ ├── Creation.cpp │ └── main.cpp ├── Chapter05 └── MereTDD │ ├── Test.h │ └── tests │ ├── Confirm.cpp │ ├── Creation.cpp │ └── main.cpp ├── Chapter06 └── MereTDD │ ├── Test.h │ └── tests │ ├── Confirm.cpp │ ├── Creation.cpp │ └── main.cpp ├── Chapter07 └── MereTDD │ ├── Test.h │ └── tests │ ├── Confirm.cpp │ ├── Creation.cpp │ ├── Setup.cpp │ └── main.cpp ├── Chapter09 ├── MereMemo │ ├── Log.h │ └── tests │ │ ├── Construction.cpp │ │ ├── Util.cpp │ │ ├── Util.h │ │ └── main.cpp └── MereTDD │ └── Test.h ├── Chapter10 ├── MereMemo │ ├── Log.h │ └── tests │ │ ├── Construction.cpp │ │ ├── LogTags.h │ │ ├── Tags.cpp │ │ ├── Util.cpp │ │ ├── Util.h │ │ └── main.cpp └── MereTDD │ └── Test.h ├── Chapter11 ├── MereMemo │ ├── Log.h │ └── tests │ │ ├── Construction.cpp │ │ ├── LogTags.h │ │ ├── Tags.cpp │ │ ├── Util.cpp │ │ ├── Util.h │ │ └── main.cpp └── MereTDD │ └── Test.h ├── Chapter12 └── MereTDD │ ├── Test.h │ └── tests │ ├── Confirm.cpp │ ├── Creation.cpp │ ├── Hamcrest.cpp │ ├── Setup.cpp │ └── main.cpp ├── Chapter13 └── MereTDD │ ├── Test.h │ └── tests │ ├── Confirm.cpp │ ├── Creation.cpp │ ├── Hamcrest.cpp │ ├── Setup.cpp │ └── main.cpp ├── Chapter14 ├── MereMemo │ └── Log.h ├── MereTDD │ └── Test.h └── SimpleService │ ├── LogTags.h │ ├── Service.cpp │ ├── Service.h │ └── tests │ ├── Message.cpp │ ├── SetupTeardown.cpp │ ├── SetupTeardown.h │ └── main.cpp ├── Chapter15 ├── MereMemo │ ├── Log.h │ └── tests │ │ ├── Construction.cpp │ │ ├── LogTags.h │ │ ├── Tags.cpp │ │ ├── Thread.cpp │ │ ├── Util.cpp │ │ ├── Util.h │ │ └── main.cpp ├── MereTDD │ ├── Test.h │ └── tests │ │ ├── Confirm.cpp │ │ ├── Creation.cpp │ │ ├── Hamcrest.cpp │ │ ├── Setup.cpp │ │ ├── Thread.cpp │ │ └── main.cpp └── SimpleService │ ├── LogTags.h │ ├── Service.cpp │ ├── Service.h │ └── tests │ ├── Message.cpp │ ├── SetupTeardown.cpp │ ├── SetupTeardown.h │ └── main.cpp ├── LICENSE └── README.md /Chapter01/MereTDD/Test.h: -------------------------------------------------------------------------------- 1 | #ifndef MERETDD_TEST_H 2 | #define MERETDD_TEST_H 3 | 4 | #include 5 | #include 6 | 7 | namespace MereTDD 8 | { 9 | 10 | class TestInterface 11 | { 12 | public: 13 | virtual ~TestInterface () = default; 14 | 15 | virtual void run () = 0; 16 | }; 17 | 18 | inline std::vector & getTests () 19 | { 20 | static std::vector tests; 21 | 22 | return tests; 23 | } 24 | 25 | inline void runTests () 26 | { 27 | for (auto * test: getTests()) 28 | { 29 | test->run(); 30 | } 31 | } 32 | 33 | } // namespace MereTDD 34 | 35 | #define TEST \ 36 | class Test : public MereTDD::TestInterface \ 37 | { \ 38 | public: \ 39 | Test (std::string_view name) \ 40 | : mName(name), mResult(true) \ 41 | { \ 42 | MereTDD::getTests().push_back(this); \ 43 | } \ 44 | void run () override; \ 45 | private: \ 46 | std::string mName; \ 47 | bool mResult; \ 48 | }; \ 49 | Test test("testCanBeCreated"); \ 50 | void Test::run () 51 | 52 | #endif // MERETDD_TEST_H 53 | -------------------------------------------------------------------------------- /Chapter01/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | TEST 6 | { 7 | std::cout << mName << std::endl; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter01/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | int main () 4 | { 5 | MereTDD::runTests(); 6 | 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /Chapter02/MereTDD/Test.h: -------------------------------------------------------------------------------- 1 | #ifndef MERETDD_TEST_H 2 | #define MERETDD_TEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace MereTDD 9 | { 10 | 11 | class TestBase 12 | { 13 | public: 14 | TestBase (std::string_view name) 15 | : mName(name), mPassed(true) 16 | { } 17 | 18 | virtual ~TestBase () = default; 19 | 20 | virtual void run () = 0; 21 | 22 | std::string_view name () const 23 | { 24 | return mName; 25 | } 26 | 27 | bool passed () const 28 | { 29 | return mPassed; 30 | } 31 | 32 | std::string_view reason () const 33 | { 34 | return mReason; 35 | } 36 | 37 | void setFailed (std::string_view reason) 38 | { 39 | mPassed = false; 40 | mReason = reason; 41 | } 42 | 43 | private: 44 | std::string mName; 45 | bool mPassed; 46 | std::string mReason; 47 | }; 48 | 49 | inline std::vector & getTests () 50 | { 51 | static std::vector tests; 52 | 53 | return tests; 54 | } 55 | 56 | inline int runTests (std::ostream & output) 57 | { 58 | output << "Running " 59 | << getTests().size() 60 | << " tests\n"; 61 | 62 | int numPassed = 0; 63 | int numFailed = 0; 64 | for (auto * test: getTests()) 65 | { 66 | output << "---------------\n" 67 | << test->name() 68 | << std::endl; 69 | 70 | try 71 | { 72 | test->run(); 73 | } 74 | catch (...) 75 | { 76 | test->setFailed("Unexpected exception thrown."); 77 | } 78 | 79 | if (test->passed()) 80 | { 81 | ++numPassed; 82 | output << "Passed" 83 | << std::endl; 84 | } 85 | else 86 | { 87 | ++numFailed; 88 | output << "Failed\n" 89 | << test->reason() 90 | << std::endl; 91 | } 92 | } 93 | 94 | output << "---------------\n"; 95 | if (numFailed == 0) 96 | { 97 | output << "All tests passed." 98 | << std::endl; 99 | } 100 | else 101 | { 102 | output << "Tests passed: " << numPassed 103 | << "\nTests failed: " << numFailed 104 | << std::endl; 105 | } 106 | 107 | return numFailed; 108 | } 109 | 110 | } // namespace MereTDD 111 | 112 | #define MERETDD_CLASS_FINAL( line ) Test ## line 113 | #define MERETDD_CLASS_RELAY( line ) MERETDD_CLASS_FINAL( line ) 114 | #define MERETDD_CLASS MERETDD_CLASS_RELAY( __LINE__ ) 115 | 116 | #define MERETDD_INSTANCE_FINAL( line ) test ## line 117 | #define MERETDD_INSTANCE_RELAY( line ) MERETDD_INSTANCE_FINAL( line ) 118 | #define MERETDD_INSTANCE MERETDD_INSTANCE_RELAY( __LINE__ ) 119 | 120 | #define TEST( testName ) \ 121 | class MERETDD_CLASS : public MereTDD::TestBase \ 122 | { \ 123 | public: \ 124 | MERETDD_CLASS (std::string_view name) \ 125 | : TestBase(name) \ 126 | { \ 127 | MereTDD::getTests().push_back(this); \ 128 | } \ 129 | void run () override; \ 130 | }; \ 131 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 132 | void MERETDD_CLASS::run () 133 | 134 | #endif // MERETDD_TEST_H 135 | -------------------------------------------------------------------------------- /Chapter02/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | TEST("Test with throw can be created") 8 | { 9 | throw 1; 10 | } 11 | -------------------------------------------------------------------------------- /Chapter02/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter03/MereTDD/Test.h: -------------------------------------------------------------------------------- 1 | #ifndef MERETDD_TEST_H 2 | #define MERETDD_TEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace MereTDD 9 | { 10 | 11 | class MissingException 12 | { 13 | public: 14 | MissingException (std::string_view exType) 15 | : mExType(exType) 16 | { } 17 | 18 | std::string_view exType () const 19 | { 20 | return mExType; 21 | } 22 | 23 | private: 24 | std::string mExType; 25 | }; 26 | 27 | class TestBase 28 | { 29 | public: 30 | TestBase (std::string_view name) 31 | : mName(name), mPassed(true) 32 | { } 33 | 34 | virtual ~TestBase () = default; 35 | 36 | virtual void runEx () 37 | { 38 | run(); 39 | } 40 | 41 | virtual void run () = 0; 42 | 43 | std::string_view name () const 44 | { 45 | return mName; 46 | } 47 | 48 | bool passed () const 49 | { 50 | return mPassed; 51 | } 52 | 53 | std::string_view reason () const 54 | { 55 | return mReason; 56 | } 57 | 58 | std::string_view expectedReason () const 59 | { 60 | return mExpectedReason; 61 | } 62 | 63 | void setFailed (std::string_view reason) 64 | { 65 | mPassed = false; 66 | mReason = reason; 67 | } 68 | 69 | void setExpectedFailureReason (std::string_view reason) 70 | { 71 | mExpectedReason = reason; 72 | } 73 | 74 | private: 75 | std::string mName; 76 | bool mPassed; 77 | std::string mReason; 78 | std::string mExpectedReason; 79 | }; 80 | 81 | inline std::vector & getTests () 82 | { 83 | static std::vector tests; 84 | 85 | return tests; 86 | } 87 | 88 | inline int runTests (std::ostream & output) 89 | { 90 | output << "Running " 91 | << getTests().size() 92 | << " tests\n"; 93 | 94 | int numPassed = 0; 95 | int numMissedFailed = 0; 96 | int numFailed = 0; 97 | for (auto * test: getTests()) 98 | { 99 | output << "---------------\n" 100 | << test->name() 101 | << std::endl; 102 | 103 | try 104 | { 105 | test->runEx(); 106 | } 107 | catch (MissingException const & ex) 108 | { 109 | std::string message = "Expected exception type "; 110 | message += ex.exType(); 111 | message += " was not thrown."; 112 | test->setFailed(message); 113 | } 114 | catch (...) 115 | { 116 | test->setFailed("Unexpected exception thrown."); 117 | } 118 | 119 | if (test->passed()) 120 | { 121 | if (not test->expectedReason().empty()) 122 | { 123 | // This test passed but it was supposed 124 | // to have failed. 125 | ++numMissedFailed; 126 | output << "Missed expected failure\n" 127 | << "Test passed but was expected to fail." 128 | << std::endl; 129 | } 130 | else 131 | { 132 | ++numPassed; 133 | output << "Passed" 134 | << std::endl; 135 | } 136 | } 137 | else if (not test->expectedReason().empty() && 138 | test->expectedReason() == test->reason()) 139 | { 140 | ++numPassed; 141 | output << "Expected failure\n" 142 | << test->reason() 143 | << std::endl; 144 | } 145 | else 146 | { 147 | ++numFailed; 148 | output << "Failed\n" 149 | << test->reason() 150 | << std::endl; 151 | } 152 | } 153 | 154 | output << "---------------\n"; 155 | output << "Tests passed: " << numPassed 156 | << "\nTests failed: " << numFailed; 157 | if (numMissedFailed != 0) 158 | { 159 | output << "\nTests failures missed: " << numMissedFailed; 160 | } 161 | output << std::endl; 162 | 163 | return numFailed; 164 | } 165 | 166 | } // namespace MereTDD 167 | 168 | #define MERETDD_CLASS_FINAL( line ) Test ## line 169 | #define MERETDD_CLASS_RELAY( line ) MERETDD_CLASS_FINAL( line ) 170 | #define MERETDD_CLASS MERETDD_CLASS_RELAY( __LINE__ ) 171 | 172 | #define MERETDD_INSTANCE_FINAL( line ) test ## line 173 | #define MERETDD_INSTANCE_RELAY( line ) MERETDD_INSTANCE_FINAL( line ) 174 | #define MERETDD_INSTANCE MERETDD_INSTANCE_RELAY( __LINE__ ) 175 | 176 | #define TEST( testName ) \ 177 | class MERETDD_CLASS : public MereTDD::TestBase \ 178 | { \ 179 | public: \ 180 | MERETDD_CLASS (std::string_view name) \ 181 | : TestBase(name) \ 182 | { \ 183 | MereTDD::getTests().push_back(this); \ 184 | } \ 185 | void run () override; \ 186 | }; \ 187 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 188 | void MERETDD_CLASS::run () 189 | 190 | #define TEST_EX( testName, exceptionType ) \ 191 | class MERETDD_CLASS : public MereTDD::TestBase \ 192 | { \ 193 | public: \ 194 | MERETDD_CLASS (std::string_view name) \ 195 | : TestBase(name) \ 196 | { \ 197 | MereTDD::getTests().push_back(this); \ 198 | } \ 199 | void runEx () override \ 200 | { \ 201 | try \ 202 | { \ 203 | run(); \ 204 | } \ 205 | catch (exceptionType const &) \ 206 | { \ 207 | return; \ 208 | } \ 209 | throw MereTDD::MissingException(#exceptionType); \ 210 | } \ 211 | void run () override; \ 212 | }; \ 213 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 214 | void MERETDD_CLASS::run () 215 | 216 | #endif // MERETDD_TEST_H 217 | -------------------------------------------------------------------------------- /Chapter03/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter03/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter04/MereTDD/Test.h: -------------------------------------------------------------------------------- 1 | #ifndef MERETDD_TEST_H 2 | #define MERETDD_TEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace MereTDD 9 | { 10 | 11 | class ConfirmException 12 | { 13 | public: 14 | ConfirmException () = default; 15 | virtual ~ConfirmException () = default; 16 | 17 | std::string_view reason () const 18 | { 19 | return mReason; 20 | } 21 | 22 | protected: 23 | std::string mReason; 24 | }; 25 | 26 | class BoolConfirmException : public ConfirmException 27 | { 28 | public: 29 | BoolConfirmException (bool expected, int line) 30 | { 31 | mReason = "Confirm failed on line "; 32 | mReason += std::to_string(line) + "\n"; 33 | mReason += " Expected: "; 34 | mReason += expected ? "true" : "false"; 35 | } 36 | }; 37 | 38 | class MissingException 39 | { 40 | public: 41 | MissingException (std::string_view exType) 42 | : mExType(exType) 43 | { } 44 | 45 | std::string_view exType () const 46 | { 47 | return mExType; 48 | } 49 | 50 | private: 51 | std::string mExType; 52 | }; 53 | 54 | class TestBase 55 | { 56 | public: 57 | TestBase (std::string_view name) 58 | : mName(name), mPassed(true) 59 | { } 60 | 61 | virtual ~TestBase () = default; 62 | 63 | virtual void runEx () 64 | { 65 | run(); 66 | } 67 | 68 | virtual void run () = 0; 69 | 70 | std::string_view name () const 71 | { 72 | return mName; 73 | } 74 | 75 | bool passed () const 76 | { 77 | return mPassed; 78 | } 79 | 80 | std::string_view reason () const 81 | { 82 | return mReason; 83 | } 84 | 85 | std::string_view expectedReason () const 86 | { 87 | return mExpectedReason; 88 | } 89 | 90 | void setFailed (std::string_view reason) 91 | { 92 | mPassed = false; 93 | mReason = reason; 94 | } 95 | 96 | void setExpectedFailureReason (std::string_view reason) 97 | { 98 | mExpectedReason = reason; 99 | } 100 | 101 | private: 102 | std::string mName; 103 | bool mPassed; 104 | std::string mReason; 105 | std::string mExpectedReason; 106 | }; 107 | 108 | inline std::vector & getTests () 109 | { 110 | static std::vector tests; 111 | 112 | return tests; 113 | } 114 | 115 | inline int runTests (std::ostream & output) 116 | { 117 | output << "Running " 118 | << getTests().size() 119 | << " tests\n"; 120 | 121 | int numPassed = 0; 122 | int numMissedFailed = 0; 123 | int numFailed = 0; 124 | for (auto * test: getTests()) 125 | { 126 | output << "---------------\n" 127 | << test->name() 128 | << std::endl; 129 | 130 | try 131 | { 132 | test->runEx(); 133 | } 134 | catch (ConfirmException const & ex) 135 | { 136 | test->setFailed(ex.reason()); 137 | } 138 | catch (MissingException const & ex) 139 | { 140 | std::string message = "Expected exception type "; 141 | message += ex.exType(); 142 | message += " was not thrown."; 143 | test->setFailed(message); 144 | } 145 | catch (...) 146 | { 147 | test->setFailed("Unexpected exception thrown."); 148 | } 149 | 150 | if (test->passed()) 151 | { 152 | if (not test->expectedReason().empty()) 153 | { 154 | // This test passed but it was supposed 155 | // to have failed. 156 | ++numMissedFailed; 157 | output << "Missed expected failure\n" 158 | << "Test passed but was expected to fail." 159 | << std::endl; 160 | } 161 | else 162 | { 163 | ++numPassed; 164 | output << "Passed" 165 | << std::endl; 166 | } 167 | } 168 | else if (not test->expectedReason().empty() && 169 | test->expectedReason() == test->reason()) 170 | { 171 | ++numPassed; 172 | output << "Expected failure\n" 173 | << test->reason() 174 | << std::endl; 175 | } 176 | else 177 | { 178 | ++numFailed; 179 | output << "Failed\n" 180 | << test->reason() 181 | << std::endl; 182 | } 183 | } 184 | 185 | output << "---------------\n"; 186 | output << "Tests passed: " << numPassed 187 | << "\nTests failed: " << numFailed; 188 | if (numMissedFailed != 0) 189 | { 190 | output << "\nTests failures missed: " << numMissedFailed; 191 | } 192 | output << std::endl; 193 | 194 | return numFailed; 195 | } 196 | 197 | } // namespace MereTDD 198 | 199 | #define MERETDD_CLASS_FINAL( line ) Test ## line 200 | #define MERETDD_CLASS_RELAY( line ) MERETDD_CLASS_FINAL( line ) 201 | #define MERETDD_CLASS MERETDD_CLASS_RELAY( __LINE__ ) 202 | 203 | #define MERETDD_INSTANCE_FINAL( line ) test ## line 204 | #define MERETDD_INSTANCE_RELAY( line ) MERETDD_INSTANCE_FINAL( line ) 205 | #define MERETDD_INSTANCE MERETDD_INSTANCE_RELAY( __LINE__ ) 206 | 207 | #define TEST( testName ) \ 208 | namespace { \ 209 | class MERETDD_CLASS : public MereTDD::TestBase \ 210 | { \ 211 | public: \ 212 | MERETDD_CLASS (std::string_view name) \ 213 | : TestBase(name) \ 214 | { \ 215 | MereTDD::getTests().push_back(this); \ 216 | } \ 217 | void run () override; \ 218 | }; \ 219 | } /* end of unnamed namespace */ \ 220 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 221 | void MERETDD_CLASS::run () 222 | 223 | #define TEST_EX( testName, exceptionType ) \ 224 | namespace { \ 225 | class MERETDD_CLASS : public MereTDD::TestBase \ 226 | { \ 227 | public: \ 228 | MERETDD_CLASS (std::string_view name) \ 229 | : TestBase(name) \ 230 | { \ 231 | MereTDD::getTests().push_back(this); \ 232 | } \ 233 | void runEx () override \ 234 | { \ 235 | try \ 236 | { \ 237 | run(); \ 238 | } \ 239 | catch (exceptionType const &) \ 240 | { \ 241 | return; \ 242 | } \ 243 | throw MereTDD::MissingException(#exceptionType); \ 244 | } \ 245 | void run () override; \ 246 | }; \ 247 | } /* end of unnamed namespace */ \ 248 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 249 | void MERETDD_CLASS::run () 250 | 251 | #define CONFIRM_FALSE( actual ) \ 252 | if (actual) \ 253 | { \ 254 | throw MereTDD::BoolConfirmException(false, __LINE__); \ 255 | } 256 | 257 | #define CONFIRM_TRUE( actual ) \ 258 | if (not actual) \ 259 | { \ 260 | throw MereTDD::BoolConfirmException(true, __LINE__); \ 261 | } 262 | 263 | #endif // MERETDD_TEST_H 264 | -------------------------------------------------------------------------------- /Chapter04/MereTDD/tests/Confirm.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | bool isPassingGrade (int value) 4 | { 5 | if (value < 60) 6 | { 7 | return false; 8 | } 9 | return true; 10 | } 11 | 12 | TEST("Test will pass without any confirms") 13 | { 14 | } 15 | 16 | TEST("Test passing grades") 17 | { 18 | bool result = isPassingGrade(0); 19 | CONFIRM_FALSE(result); 20 | 21 | result = isPassingGrade(100); 22 | CONFIRM_TRUE(result); 23 | } 24 | -------------------------------------------------------------------------------- /Chapter04/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter04/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter05/MereTDD/Test.h: -------------------------------------------------------------------------------- 1 | #ifndef MERETDD_TEST_H 2 | #define MERETDD_TEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace MereTDD 9 | { 10 | 11 | class ConfirmException 12 | { 13 | public: 14 | ConfirmException (int line) 15 | : mLine(line) 16 | { } 17 | 18 | virtual ~ConfirmException () = default; 19 | 20 | std::string_view reason () const 21 | { 22 | return mReason; 23 | } 24 | 25 | int line () const 26 | { 27 | return mLine; 28 | } 29 | 30 | protected: 31 | std::string mReason; 32 | int mLine; 33 | }; 34 | 35 | class BoolConfirmException : public ConfirmException 36 | { 37 | public: 38 | BoolConfirmException (bool expected, int line) 39 | : ConfirmException(line) 40 | { 41 | mReason += " Expected: "; 42 | mReason += expected ? "true" : "false"; 43 | } 44 | }; 45 | 46 | class ActualConfirmException : public ConfirmException 47 | { 48 | public: 49 | ActualConfirmException ( 50 | std::string_view expected, 51 | std::string_view actual, 52 | int line) 53 | : ConfirmException(line), 54 | mExpected(expected), 55 | mActual(actual) 56 | { 57 | formatReason(); 58 | } 59 | 60 | private: 61 | void formatReason () 62 | { 63 | mReason += " Expected: " + mExpected + "\n"; 64 | mReason += " Actual : " + mActual; 65 | } 66 | 67 | std::string mExpected; 68 | std::string mActual; 69 | }; 70 | 71 | class MissingException 72 | { 73 | public: 74 | MissingException (std::string_view exType) 75 | : mExType(exType) 76 | { } 77 | 78 | std::string_view exType () const 79 | { 80 | return mExType; 81 | } 82 | 83 | private: 84 | std::string mExType; 85 | }; 86 | 87 | inline void confirm ( 88 | bool expected, 89 | bool actual, 90 | int line) 91 | { 92 | if (actual != expected) 93 | { 94 | throw BoolConfirmException(expected, line); 95 | } 96 | } 97 | 98 | inline void confirm ( 99 | std::string_view expected, 100 | std::string_view actual, 101 | int line) 102 | { 103 | if (actual != expected) 104 | { 105 | throw ActualConfirmException( 106 | expected, 107 | actual, 108 | line); 109 | } 110 | } 111 | 112 | inline void confirm ( 113 | std::string const & expected, 114 | std::string const & actual, 115 | int line) 116 | { 117 | confirm( 118 | std::string_view(expected), 119 | std::string_view(actual), 120 | line); 121 | } 122 | 123 | inline void confirm ( 124 | float expected, 125 | float actual, 126 | int line) 127 | { 128 | if (actual < (expected - 0.0001f) || 129 | actual > (expected + 0.0001f)) 130 | { 131 | throw ActualConfirmException( 132 | std::to_string(expected), 133 | std::to_string(actual), 134 | line); 135 | } 136 | } 137 | 138 | inline void confirm ( 139 | double expected, 140 | double actual, 141 | int line) 142 | { 143 | if (actual < (expected - 0.000001) || 144 | actual > (expected + 0.000001)) 145 | { 146 | throw ActualConfirmException( 147 | std::to_string(expected), 148 | std::to_string(actual), 149 | line); 150 | } 151 | } 152 | 153 | inline void confirm ( 154 | long double expected, 155 | long double actual, 156 | int line) 157 | { 158 | if (actual < (expected - 0.000001) || 159 | actual > (expected + 0.000001)) 160 | { 161 | throw ActualConfirmException( 162 | std::to_string(expected), 163 | std::to_string(actual), 164 | line); 165 | } 166 | } 167 | 168 | template 169 | void confirm ( 170 | T const & expected, 171 | T const & actual, 172 | int line) 173 | { 174 | if (actual != expected) 175 | { 176 | throw ActualConfirmException( 177 | std::to_string(expected), 178 | std::to_string(actual), 179 | line); 180 | } 181 | } 182 | 183 | class TestBase 184 | { 185 | public: 186 | TestBase (std::string_view name) 187 | : mName(name), mPassed(true), mConfirmLocation(-1) 188 | { } 189 | 190 | virtual ~TestBase () = default; 191 | 192 | virtual void runEx () 193 | { 194 | run(); 195 | } 196 | 197 | virtual void run () = 0; 198 | 199 | std::string_view name () const 200 | { 201 | return mName; 202 | } 203 | 204 | bool passed () const 205 | { 206 | return mPassed; 207 | } 208 | 209 | std::string_view reason () const 210 | { 211 | return mReason; 212 | } 213 | 214 | int confirmLocation () const 215 | { 216 | return mConfirmLocation; 217 | } 218 | 219 | std::string_view expectedReason () const 220 | { 221 | return mExpectedReason; 222 | } 223 | 224 | void setFailed (std::string_view reason, int confirmLocation = -1) 225 | { 226 | mPassed = false; 227 | mReason = reason; 228 | mConfirmLocation = confirmLocation; 229 | } 230 | 231 | void setExpectedFailureReason (std::string_view reason) 232 | { 233 | mExpectedReason = reason; 234 | } 235 | 236 | private: 237 | std::string mName; 238 | bool mPassed; 239 | std::string mReason; 240 | std::string mExpectedReason; 241 | int mConfirmLocation; 242 | }; 243 | 244 | inline std::vector & getTests () 245 | { 246 | static std::vector tests; 247 | 248 | return tests; 249 | } 250 | 251 | inline int runTests (std::ostream & output) 252 | { 253 | output << "Running " 254 | << getTests().size() 255 | << " tests\n"; 256 | 257 | int numPassed = 0; 258 | int numMissedFailed = 0; 259 | int numFailed = 0; 260 | for (auto * test: getTests()) 261 | { 262 | output << "---------------\n" 263 | << test->name() 264 | << std::endl; 265 | 266 | try 267 | { 268 | test->runEx(); 269 | } 270 | catch (ConfirmException const & ex) 271 | { 272 | test->setFailed(ex.reason(), ex.line()); 273 | } 274 | catch (MissingException const & ex) 275 | { 276 | std::string message = "Expected exception type "; 277 | message += ex.exType(); 278 | message += " was not thrown."; 279 | test->setFailed(message); 280 | } 281 | catch (...) 282 | { 283 | test->setFailed("Unexpected exception thrown."); 284 | } 285 | 286 | if (test->passed()) 287 | { 288 | if (not test->expectedReason().empty()) 289 | { 290 | // This test passed but it was supposed 291 | // to have failed. 292 | ++numMissedFailed; 293 | output << "Missed expected failure\n" 294 | << "Test passed but was expected to fail." 295 | << std::endl; 296 | } 297 | else 298 | { 299 | ++numPassed; 300 | output << "Passed" 301 | << std::endl; 302 | } 303 | } 304 | else if (not test->expectedReason().empty() && 305 | test->expectedReason() == test->reason()) 306 | { 307 | ++numPassed; 308 | output << "Expected failure\n" 309 | << test->reason() 310 | << std::endl; 311 | } 312 | else 313 | { 314 | ++numFailed; 315 | if (test->confirmLocation() != -1) 316 | { 317 | output << "Failed confirm on line " 318 | << test->confirmLocation() << "\n"; 319 | } 320 | else 321 | { 322 | output << "Failed\n"; 323 | } 324 | output << test->reason() 325 | << std::endl; 326 | } 327 | } 328 | 329 | output << "---------------\n"; 330 | output << "Tests passed: " << numPassed 331 | << "\nTests failed: " << numFailed; 332 | if (numMissedFailed != 0) 333 | { 334 | output << "\nTests failures missed: " << numMissedFailed; 335 | } 336 | output << std::endl; 337 | 338 | return numFailed; 339 | } 340 | 341 | } // namespace MereTDD 342 | 343 | #define MERETDD_CLASS_FINAL( line ) Test ## line 344 | #define MERETDD_CLASS_RELAY( line ) MERETDD_CLASS_FINAL( line ) 345 | #define MERETDD_CLASS MERETDD_CLASS_RELAY( __LINE__ ) 346 | 347 | #define MERETDD_INSTANCE_FINAL( line ) test ## line 348 | #define MERETDD_INSTANCE_RELAY( line ) MERETDD_INSTANCE_FINAL( line ) 349 | #define MERETDD_INSTANCE MERETDD_INSTANCE_RELAY( __LINE__ ) 350 | 351 | #define TEST( testName ) \ 352 | namespace { \ 353 | class MERETDD_CLASS : public MereTDD::TestBase \ 354 | { \ 355 | public: \ 356 | MERETDD_CLASS (std::string_view name) \ 357 | : TestBase(name) \ 358 | { \ 359 | MereTDD::getTests().push_back(this); \ 360 | } \ 361 | void run () override; \ 362 | }; \ 363 | } /* end of unnamed namespace */ \ 364 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 365 | void MERETDD_CLASS::run () 366 | 367 | #define TEST_EX( testName, exceptionType ) \ 368 | namespace { \ 369 | class MERETDD_CLASS : public MereTDD::TestBase \ 370 | { \ 371 | public: \ 372 | MERETDD_CLASS (std::string_view name) \ 373 | : TestBase(name) \ 374 | { \ 375 | MereTDD::getTests().push_back(this); \ 376 | } \ 377 | void runEx () override \ 378 | { \ 379 | try \ 380 | { \ 381 | run(); \ 382 | } \ 383 | catch (exceptionType const &) \ 384 | { \ 385 | return; \ 386 | } \ 387 | throw MereTDD::MissingException(#exceptionType); \ 388 | } \ 389 | void run () override; \ 390 | }; \ 391 | } /* end of unnamed namespace */ \ 392 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 393 | void MERETDD_CLASS::run () 394 | 395 | #define CONFIRM_FALSE( actual ) \ 396 | MereTDD::confirm(false, actual, __LINE__) 397 | #define CONFIRM_TRUE( actual ) \ 398 | MereTDD::confirm(true, actual, __LINE__) 399 | #define CONFIRM( expected, actual ) \ 400 | MereTDD::confirm(expected, actual, __LINE__) 401 | 402 | #endif // MERETDD_TEST_H 403 | -------------------------------------------------------------------------------- /Chapter05/MereTDD/tests/Confirm.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | bool isNegative (int value) 4 | { 5 | return value < 0; 6 | } 7 | 8 | int multiplyBy2 (int value) 9 | { 10 | return value * 2; 11 | } 12 | 13 | long multiplyBy2 (long value) 14 | { 15 | return value * 2L; 16 | } 17 | 18 | long long multiplyBy2 (long long value) 19 | { 20 | return value * 2LL; 21 | } 22 | 23 | TEST("Test will pass without any confirms") 24 | { 25 | } 26 | 27 | TEST("Test bool confirms") 28 | { 29 | bool result = isNegative(0); 30 | CONFIRM_FALSE(result); 31 | 32 | result = isNegative(-1); 33 | CONFIRM_TRUE(result); 34 | } 35 | 36 | TEST("Test int confirms") 37 | { 38 | int result = multiplyBy2(0); 39 | CONFIRM(0, result); 40 | 41 | result = multiplyBy2(1); 42 | CONFIRM(2, result); 43 | 44 | result = multiplyBy2(-1); 45 | CONFIRM(-2, result); 46 | } 47 | 48 | TEST("Test long confirms") 49 | { 50 | long result = multiplyBy2(0L); 51 | CONFIRM(0L, result); 52 | 53 | result = multiplyBy2(1L); 54 | CONFIRM(2L, result); 55 | 56 | result = multiplyBy2(-1L); 57 | CONFIRM(-2L, result); 58 | } 59 | 60 | TEST("Test long long confirms") 61 | { 62 | long long result = multiplyBy2(0LL); 63 | CONFIRM(0LL, result); 64 | 65 | result = multiplyBy2(10'000'000'000LL); 66 | CONFIRM(20'000'000'000LL, result); 67 | 68 | result = multiplyBy2(-10'000'000'000LL); 69 | CONFIRM(-20'000'000'000LL, result); 70 | } 71 | 72 | TEST("Test string confirms") 73 | { 74 | std::string result = "abc"; 75 | std::string expected = "abc"; 76 | CONFIRM(expected, result); 77 | } 78 | 79 | TEST("Test string and string literal confirms") 80 | { 81 | std::string result = "abc"; 82 | CONFIRM("abc", result); 83 | } 84 | 85 | TEST("Test float confirms") 86 | { 87 | float f1 = 0.1f; 88 | float f2 = 0.2f; 89 | float sum = f1 + f2; 90 | float expected = 0.3f; 91 | CONFIRM(expected, sum); 92 | } 93 | 94 | TEST("Test double confirms") 95 | { 96 | double d1 = 0.1; 97 | double d2 = 0.2; 98 | double sum = d1 + d2; 99 | double expected = 0.3; 100 | CONFIRM(expected, sum); 101 | } 102 | 103 | TEST("Test long double confirms") 104 | { 105 | long double ld1 = 0.1; 106 | long double ld2 = 0.2; 107 | long double sum = ld1 + ld2; 108 | long double expected = 0.3; 109 | CONFIRM(expected, sum); 110 | } 111 | 112 | TEST("Test bool confirm failure") 113 | { 114 | std::string reason = " Expected: true"; 115 | setExpectedFailureReason(reason); 116 | 117 | bool result = isNegative(0); 118 | CONFIRM_TRUE(result); 119 | } 120 | 121 | TEST("Test int confirm failure") 122 | { 123 | std::string reason = " Expected: 0\n"; 124 | reason += " Actual : 2"; 125 | setExpectedFailureReason(reason); 126 | 127 | int result = multiplyBy2(1); 128 | CONFIRM(0, result); 129 | } 130 | 131 | TEST("Test long confirm failure") 132 | { 133 | std::string reason = " Expected: 0\n"; 134 | reason += " Actual : 2"; 135 | setExpectedFailureReason(reason); 136 | 137 | long result = multiplyBy2(1L); 138 | CONFIRM(0L, result); 139 | } 140 | 141 | TEST("Test long long confirm failure") 142 | { 143 | std::string reason = " Expected: 10000000000\n"; 144 | reason += " Actual : 20000000000"; 145 | setExpectedFailureReason(reason); 146 | 147 | long long result = multiplyBy2(10'000'000'000LL); 148 | CONFIRM(10'000'000'000LL, result); 149 | } 150 | 151 | TEST("Test string confirm failure") 152 | { 153 | std::string reason = " Expected: def\n"; 154 | reason += " Actual : abc"; 155 | setExpectedFailureReason(reason); 156 | 157 | std::string result = "abc"; 158 | std::string expected = "def"; 159 | CONFIRM(expected, result); 160 | } 161 | -------------------------------------------------------------------------------- /Chapter05/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter05/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter06/MereTDD/Test.h: -------------------------------------------------------------------------------- 1 | #ifndef MERETDD_TEST_H 2 | #define MERETDD_TEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace MereTDD 9 | { 10 | 11 | class ConfirmException 12 | { 13 | public: 14 | ConfirmException (int line) 15 | : mLine(line) 16 | { } 17 | 18 | virtual ~ConfirmException () = default; 19 | 20 | std::string_view reason () const 21 | { 22 | return mReason; 23 | } 24 | 25 | int line () const 26 | { 27 | return mLine; 28 | } 29 | 30 | protected: 31 | std::string mReason; 32 | int mLine; 33 | }; 34 | 35 | class BoolConfirmException : public ConfirmException 36 | { 37 | public: 38 | BoolConfirmException (bool expected, int line) 39 | : ConfirmException(line) 40 | { 41 | mReason += " Expected: "; 42 | mReason += expected ? "true" : "false"; 43 | } 44 | }; 45 | 46 | class ActualConfirmException : public ConfirmException 47 | { 48 | public: 49 | ActualConfirmException ( 50 | std::string_view expected, 51 | std::string_view actual, 52 | int line) 53 | : ConfirmException(line), 54 | mExpected(expected), 55 | mActual(actual) 56 | { 57 | formatReason(); 58 | } 59 | 60 | private: 61 | void formatReason () 62 | { 63 | mReason += " Expected: " + mExpected + "\n"; 64 | mReason += " Actual : " + mActual; 65 | } 66 | 67 | std::string mExpected; 68 | std::string mActual; 69 | }; 70 | 71 | class MissingException 72 | { 73 | public: 74 | MissingException (std::string_view exType) 75 | : mExType(exType) 76 | { } 77 | 78 | std::string_view exType () const 79 | { 80 | return mExType; 81 | } 82 | 83 | private: 84 | std::string mExType; 85 | }; 86 | 87 | inline void confirm ( 88 | bool expected, 89 | bool actual, 90 | int line) 91 | { 92 | if (actual != expected) 93 | { 94 | throw BoolConfirmException(expected, line); 95 | } 96 | } 97 | 98 | inline void confirm ( 99 | std::string_view expected, 100 | std::string_view actual, 101 | int line) 102 | { 103 | if (actual != expected) 104 | { 105 | throw ActualConfirmException( 106 | expected, 107 | actual, 108 | line); 109 | } 110 | } 111 | 112 | inline void confirm ( 113 | std::string const & expected, 114 | std::string const & actual, 115 | int line) 116 | { 117 | confirm( 118 | std::string_view(expected), 119 | std::string_view(actual), 120 | line); 121 | } 122 | 123 | inline void confirm ( 124 | float expected, 125 | float actual, 126 | int line) 127 | { 128 | if (actual < (expected - 0.0001f) || 129 | actual > (expected + 0.0001f)) 130 | { 131 | throw ActualConfirmException( 132 | std::to_string(expected), 133 | std::to_string(actual), 134 | line); 135 | } 136 | } 137 | 138 | inline void confirm ( 139 | double expected, 140 | double actual, 141 | int line) 142 | { 143 | if (actual < (expected - 0.000001) || 144 | actual > (expected + 0.000001)) 145 | { 146 | throw ActualConfirmException( 147 | std::to_string(expected), 148 | std::to_string(actual), 149 | line); 150 | } 151 | } 152 | 153 | inline void confirm ( 154 | long double expected, 155 | long double actual, 156 | int line) 157 | { 158 | if (actual < (expected - 0.000001) || 159 | actual > (expected + 0.000001)) 160 | { 161 | throw ActualConfirmException( 162 | std::to_string(expected), 163 | std::to_string(actual), 164 | line); 165 | } 166 | } 167 | 168 | template 169 | void confirm ( 170 | T const & expected, 171 | T const & actual, 172 | int line) 173 | { 174 | if (actual != expected) 175 | { 176 | throw ActualConfirmException( 177 | std::to_string(expected), 178 | std::to_string(actual), 179 | line); 180 | } 181 | } 182 | 183 | class TestBase; 184 | 185 | inline std::vector & getTests () 186 | { 187 | static std::vector tests; 188 | 189 | return tests; 190 | } 191 | 192 | class TestBase 193 | { 194 | public: 195 | TestBase (std::string_view name) 196 | : mName(name), mPassed(true), mConfirmLocation(-1) 197 | { 198 | getTests().push_back(this); 199 | } 200 | 201 | virtual ~TestBase () = default; 202 | 203 | virtual void runEx () 204 | { 205 | run(); 206 | } 207 | 208 | virtual void run () = 0; 209 | 210 | std::string_view name () const 211 | { 212 | return mName; 213 | } 214 | 215 | bool passed () const 216 | { 217 | return mPassed; 218 | } 219 | 220 | std::string_view reason () const 221 | { 222 | return mReason; 223 | } 224 | 225 | int confirmLocation () const 226 | { 227 | return mConfirmLocation; 228 | } 229 | 230 | std::string_view expectedReason () const 231 | { 232 | return mExpectedReason; 233 | } 234 | 235 | void setFailed (std::string_view reason, int confirmLocation = -1) 236 | { 237 | mPassed = false; 238 | mReason = reason; 239 | mConfirmLocation = confirmLocation; 240 | } 241 | 242 | void setExpectedFailureReason (std::string_view reason) 243 | { 244 | mExpectedReason = reason; 245 | } 246 | 247 | private: 248 | std::string mName; 249 | bool mPassed; 250 | std::string mReason; 251 | std::string mExpectedReason; 252 | int mConfirmLocation; 253 | }; 254 | 255 | template 256 | class TestExBase : public TestBase 257 | { 258 | public: 259 | TestExBase (std::string_view name, 260 | std::string_view exceptionName) 261 | : TestBase(name), mExceptionName(exceptionName) 262 | { } 263 | 264 | void runEx () override 265 | { 266 | try 267 | { 268 | run(); 269 | } 270 | catch (ExceptionT const &) 271 | { 272 | return; 273 | } 274 | 275 | throw MissingException(mExceptionName); 276 | } 277 | 278 | private: 279 | std::string mExceptionName; 280 | }; 281 | 282 | inline int runTests (std::ostream & output) 283 | { 284 | output << "Running " 285 | << getTests().size() 286 | << " tests\n"; 287 | 288 | int numPassed = 0; 289 | int numMissedFailed = 0; 290 | int numFailed = 0; 291 | for (auto * test: getTests()) 292 | { 293 | output << "---------------\n" 294 | << test->name() 295 | << std::endl; 296 | 297 | try 298 | { 299 | test->runEx(); 300 | } 301 | catch (ConfirmException const & ex) 302 | { 303 | test->setFailed(ex.reason(), ex.line()); 304 | } 305 | catch (MissingException const & ex) 306 | { 307 | std::string message = "Expected exception type "; 308 | message += ex.exType(); 309 | message += " was not thrown."; 310 | test->setFailed(message); 311 | } 312 | catch (...) 313 | { 314 | test->setFailed("Unexpected exception thrown."); 315 | } 316 | 317 | if (test->passed()) 318 | { 319 | if (not test->expectedReason().empty()) 320 | { 321 | // This test passed but it was supposed 322 | // to have failed. 323 | ++numMissedFailed; 324 | output << "Missed expected failure\n" 325 | << "Test passed but was expected to fail." 326 | << std::endl; 327 | } 328 | else 329 | { 330 | ++numPassed; 331 | output << "Passed" 332 | << std::endl; 333 | } 334 | } 335 | else if (not test->expectedReason().empty() && 336 | test->expectedReason() == test->reason()) 337 | { 338 | ++numPassed; 339 | output << "Expected failure\n" 340 | << test->reason() 341 | << std::endl; 342 | } 343 | else 344 | { 345 | ++numFailed; 346 | if (test->confirmLocation() != -1) 347 | { 348 | output << "Failed confirm on line " 349 | << test->confirmLocation() << "\n"; 350 | } 351 | else 352 | { 353 | output << "Failed\n"; 354 | } 355 | output << test->reason() 356 | << std::endl; 357 | } 358 | } 359 | 360 | output << "---------------\n"; 361 | output << "Tests passed: " << numPassed 362 | << "\nTests failed: " << numFailed; 363 | if (numMissedFailed != 0) 364 | { 365 | output << "\nTests failures missed: " << numMissedFailed; 366 | } 367 | output << std::endl; 368 | 369 | return numFailed; 370 | } 371 | 372 | } // namespace MereTDD 373 | 374 | #define MERETDD_CLASS_FINAL( line ) Test ## line 375 | #define MERETDD_CLASS_RELAY( line ) MERETDD_CLASS_FINAL( line ) 376 | #define MERETDD_CLASS MERETDD_CLASS_RELAY( __LINE__ ) 377 | 378 | #define MERETDD_INSTANCE_FINAL( line ) test ## line 379 | #define MERETDD_INSTANCE_RELAY( line ) MERETDD_INSTANCE_FINAL( line ) 380 | #define MERETDD_INSTANCE MERETDD_INSTANCE_RELAY( __LINE__ ) 381 | 382 | #define TEST( testName ) \ 383 | namespace { \ 384 | class MERETDD_CLASS : public MereTDD::TestBase \ 385 | { \ 386 | public: \ 387 | MERETDD_CLASS (std::string_view name) \ 388 | : TestBase(name) \ 389 | { } \ 390 | void run () override; \ 391 | }; \ 392 | } /* end of unnamed namespace */ \ 393 | MERETDD_CLASS MERETDD_INSTANCE(testName); \ 394 | void MERETDD_CLASS::run () 395 | 396 | #define TEST_EX( testName, exceptionType ) \ 397 | namespace { \ 398 | class MERETDD_CLASS : public MereTDD::TestExBase \ 399 | { \ 400 | public: \ 401 | MERETDD_CLASS (std::string_view name, \ 402 | std::string_view exceptionName) \ 403 | : TestExBase(name, exceptionName) \ 404 | { } \ 405 | void run () override; \ 406 | }; \ 407 | } /* end of unnamed namespace */ \ 408 | MERETDD_CLASS MERETDD_INSTANCE(testName, #exceptionType); \ 409 | void MERETDD_CLASS::run () 410 | 411 | #define CONFIRM_FALSE( actual ) \ 412 | MereTDD::confirm(false, actual, __LINE__) 413 | #define CONFIRM_TRUE( actual ) \ 414 | MereTDD::confirm(true, actual, __LINE__) 415 | #define CONFIRM( expected, actual ) \ 416 | MereTDD::confirm(expected, actual, __LINE__) 417 | 418 | #endif // MERETDD_TEST_H 419 | -------------------------------------------------------------------------------- /Chapter06/MereTDD/tests/Confirm.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | bool isNegative (int value) 4 | { 5 | return value < 0; 6 | } 7 | 8 | int multiplyBy2 (int value) 9 | { 10 | return value * 2; 11 | } 12 | 13 | long multiplyBy2 (long value) 14 | { 15 | return value * 2L; 16 | } 17 | 18 | long long multiplyBy2 (long long value) 19 | { 20 | return value * 2LL; 21 | } 22 | 23 | TEST("Test will pass without any confirms") 24 | { 25 | } 26 | 27 | TEST("Test bool confirms") 28 | { 29 | bool result = isNegative(0); 30 | CONFIRM_FALSE(result); 31 | 32 | result = isNegative(-1); 33 | CONFIRM_TRUE(result); 34 | } 35 | 36 | TEST("Test int confirms") 37 | { 38 | int result = multiplyBy2(0); 39 | CONFIRM(0, result); 40 | 41 | result = multiplyBy2(1); 42 | CONFIRM(2, result); 43 | 44 | result = multiplyBy2(-1); 45 | CONFIRM(-2, result); 46 | } 47 | 48 | TEST("Test long confirms") 49 | { 50 | long result = multiplyBy2(0L); 51 | CONFIRM(0L, result); 52 | 53 | result = multiplyBy2(1L); 54 | CONFIRM(2L, result); 55 | 56 | result = multiplyBy2(-1L); 57 | CONFIRM(-2L, result); 58 | } 59 | 60 | TEST("Test long long confirms") 61 | { 62 | long long result = multiplyBy2(0LL); 63 | CONFIRM(0LL, result); 64 | 65 | result = multiplyBy2(10'000'000'000LL); 66 | CONFIRM(20'000'000'000LL, result); 67 | 68 | result = multiplyBy2(-10'000'000'000LL); 69 | CONFIRM(-20'000'000'000LL, result); 70 | } 71 | 72 | TEST("Test string confirms") 73 | { 74 | std::string result = "abc"; 75 | std::string expected = "abc"; 76 | CONFIRM(expected, result); 77 | } 78 | 79 | TEST("Test string and string literal confirms") 80 | { 81 | std::string result = "abc"; 82 | CONFIRM("abc", result); 83 | } 84 | 85 | TEST("Test float confirms") 86 | { 87 | float f1 = 0.1f; 88 | float f2 = 0.2f; 89 | float sum = f1 + f2; 90 | float expected = 0.3f; 91 | CONFIRM(expected, sum); 92 | } 93 | 94 | TEST("Test double confirms") 95 | { 96 | double d1 = 0.1; 97 | double d2 = 0.2; 98 | double sum = d1 + d2; 99 | double expected = 0.3; 100 | CONFIRM(expected, sum); 101 | } 102 | 103 | TEST("Test long double confirms") 104 | { 105 | long double ld1 = 0.1; 106 | long double ld2 = 0.2; 107 | long double sum = ld1 + ld2; 108 | long double expected = 0.3; 109 | CONFIRM(expected, sum); 110 | } 111 | 112 | TEST("Test bool confirm failure") 113 | { 114 | std::string reason = " Expected: true"; 115 | setExpectedFailureReason(reason); 116 | 117 | bool result = isNegative(0); 118 | CONFIRM_TRUE(result); 119 | } 120 | 121 | TEST("Test int confirm failure") 122 | { 123 | std::string reason = " Expected: 0\n"; 124 | reason += " Actual : 2"; 125 | setExpectedFailureReason(reason); 126 | 127 | int result = multiplyBy2(1); 128 | CONFIRM(0, result); 129 | } 130 | 131 | TEST("Test long confirm failure") 132 | { 133 | std::string reason = " Expected: 0\n"; 134 | reason += " Actual : 2"; 135 | setExpectedFailureReason(reason); 136 | 137 | long result = multiplyBy2(1L); 138 | CONFIRM(0L, result); 139 | } 140 | 141 | TEST("Test long long confirm failure") 142 | { 143 | std::string reason = " Expected: 10000000000\n"; 144 | reason += " Actual : 20000000000"; 145 | setExpectedFailureReason(reason); 146 | 147 | long long result = multiplyBy2(10'000'000'000LL); 148 | CONFIRM(10'000'000'000LL, result); 149 | } 150 | 151 | TEST("Test string confirm failure") 152 | { 153 | std::string reason = " Expected: def\n"; 154 | reason += " Actual : abc"; 155 | setExpectedFailureReason(reason); 156 | 157 | std::string result = "abc"; 158 | std::string expected = "def"; 159 | CONFIRM(expected, result); 160 | } 161 | -------------------------------------------------------------------------------- /Chapter06/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter06/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter07/MereTDD/tests/Confirm.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | bool isNegative (int value) 4 | { 5 | return value < 0; 6 | } 7 | 8 | int multiplyBy2 (int value) 9 | { 10 | return value * 2; 11 | } 12 | 13 | long multiplyBy2 (long value) 14 | { 15 | return value * 2L; 16 | } 17 | 18 | long long multiplyBy2 (long long value) 19 | { 20 | return value * 2LL; 21 | } 22 | 23 | TEST("Test will pass without any confirms") 24 | { 25 | } 26 | 27 | TEST("Test bool confirms") 28 | { 29 | bool result = isNegative(0); 30 | CONFIRM_FALSE(result); 31 | 32 | result = isNegative(-1); 33 | CONFIRM_TRUE(result); 34 | } 35 | 36 | TEST("Test int confirms") 37 | { 38 | int result = multiplyBy2(0); 39 | CONFIRM(0, result); 40 | 41 | result = multiplyBy2(1); 42 | CONFIRM(2, result); 43 | 44 | result = multiplyBy2(-1); 45 | CONFIRM(-2, result); 46 | } 47 | 48 | TEST("Test long confirms") 49 | { 50 | long result = multiplyBy2(0L); 51 | CONFIRM(0L, result); 52 | 53 | result = multiplyBy2(1L); 54 | CONFIRM(2L, result); 55 | 56 | result = multiplyBy2(-1L); 57 | CONFIRM(-2L, result); 58 | } 59 | 60 | TEST("Test long long confirms") 61 | { 62 | long long result = multiplyBy2(0LL); 63 | CONFIRM(0LL, result); 64 | 65 | result = multiplyBy2(10'000'000'000LL); 66 | CONFIRM(20'000'000'000LL, result); 67 | 68 | result = multiplyBy2(-10'000'000'000LL); 69 | CONFIRM(-20'000'000'000LL, result); 70 | } 71 | 72 | TEST("Test string confirms") 73 | { 74 | std::string result = "abc"; 75 | std::string expected = "abc"; 76 | CONFIRM(expected, result); 77 | } 78 | 79 | TEST("Test string and string literal confirms") 80 | { 81 | std::string result = "abc"; 82 | CONFIRM("abc", result); 83 | } 84 | 85 | TEST("Test float confirms") 86 | { 87 | float f1 = 0.1f; 88 | float f2 = 0.2f; 89 | float sum = f1 + f2; 90 | float expected = 0.3f; 91 | CONFIRM(expected, sum); 92 | } 93 | 94 | TEST("Test double confirms") 95 | { 96 | double d1 = 0.1; 97 | double d2 = 0.2; 98 | double sum = d1 + d2; 99 | double expected = 0.3; 100 | CONFIRM(expected, sum); 101 | } 102 | 103 | TEST("Test long double confirms") 104 | { 105 | long double ld1 = 0.1; 106 | long double ld2 = 0.2; 107 | long double sum = ld1 + ld2; 108 | long double expected = 0.3; 109 | CONFIRM(expected, sum); 110 | } 111 | 112 | TEST("Test bool confirm failure") 113 | { 114 | std::string reason = " Expected: true"; 115 | setExpectedFailureReason(reason); 116 | 117 | bool result = isNegative(0); 118 | CONFIRM_TRUE(result); 119 | } 120 | 121 | TEST("Test int confirm failure") 122 | { 123 | std::string reason = " Expected: 0\n"; 124 | reason += " Actual : 2"; 125 | setExpectedFailureReason(reason); 126 | 127 | int result = multiplyBy2(1); 128 | CONFIRM(0, result); 129 | } 130 | 131 | TEST("Test long confirm failure") 132 | { 133 | std::string reason = " Expected: 0\n"; 134 | reason += " Actual : 2"; 135 | setExpectedFailureReason(reason); 136 | 137 | long result = multiplyBy2(1L); 138 | CONFIRM(0L, result); 139 | } 140 | 141 | TEST("Test long long confirm failure") 142 | { 143 | std::string reason = " Expected: 10000000000\n"; 144 | reason += " Actual : 20000000000"; 145 | setExpectedFailureReason(reason); 146 | 147 | long long result = multiplyBy2(10'000'000'000LL); 148 | CONFIRM(10'000'000'000LL, result); 149 | } 150 | 151 | TEST("Test string confirm failure") 152 | { 153 | std::string reason = " Expected: def\n"; 154 | reason += " Actual : abc"; 155 | setExpectedFailureReason(reason); 156 | 157 | std::string result = "abc"; 158 | std::string expected = "def"; 159 | CONFIRM(expected, result); 160 | } 161 | -------------------------------------------------------------------------------- /Chapter07/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter07/MereTDD/tests/Setup.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | #include 5 | 6 | std::string createTestTable () 7 | { 8 | // If this was real code, it might open a 9 | // connection to a database, create a temp 10 | // table with a random name, and return the 11 | // table name. 12 | return "test_data_01"; 13 | } 14 | 15 | void dropTestTable (std::string_view /*name*/) 16 | { 17 | // Real code would use the name to drop 18 | // the table. 19 | } 20 | 21 | int createTestEntry () 22 | { 23 | // If this was real code, it might open a 24 | // connection to a database, insert a row 25 | // of data, and return the row identifier. 26 | return 100; 27 | } 28 | 29 | void updateTestEntryName (int /*id*/, std::string_view name) 30 | { 31 | if (name.empty()) 32 | { 33 | throw 1; 34 | } 35 | // Real code would proceed to update the 36 | // data with the new name. 37 | } 38 | 39 | void deleteTestEntry (int /*id*/) 40 | { 41 | // Real code would use the id to delete 42 | // the temporary row of data. 43 | } 44 | 45 | class TempEntry 46 | { 47 | public: 48 | void setup () 49 | { 50 | mId = createTestEntry(); 51 | } 52 | 53 | void teardown () 54 | { 55 | deleteTestEntry(mId); 56 | } 57 | 58 | int id () 59 | { 60 | return mId; 61 | } 62 | 63 | private: 64 | int mId; 65 | }; 66 | 67 | class TempTable 68 | { 69 | public: 70 | void setup () 71 | { 72 | mName = createTestTable(); 73 | } 74 | 75 | void teardown () 76 | { 77 | dropTestTable(mName); 78 | } 79 | 80 | std::string tableName () 81 | { 82 | return mName; 83 | } 84 | 85 | private: 86 | std::string mName; 87 | }; 88 | 89 | TEST_EX("Test will run setup and teardown code", int) 90 | { 91 | MereTDD::SetupAndTeardown entry; 92 | 93 | // If this was a project test, it might be called 94 | // "Updating empty name throws". And the type thrown 95 | // would not be an int. 96 | updateTestEntryName(entry.id(), ""); 97 | } 98 | 99 | TEST("Test will run multiple setup and teardown code") 100 | { 101 | MereTDD::SetupAndTeardown entry1; 102 | MereTDD::SetupAndTeardown entry2; 103 | 104 | // If this was a project test, it might need 105 | // more than one temporary entry. The TempEntry 106 | // policy could either create multiple data records 107 | // or it is easier to just have multiple instances 108 | // that each create a single data entry. 109 | updateTestEntryName(entry1.id(), "abc"); 110 | updateTestEntryName(entry2.id(), "def"); 111 | } 112 | 113 | MereTDD::TestSuiteSetupAndTeardown 114 | gTable1("Test suite setup/teardown 1", "Suite 1"); 115 | 116 | MereTDD::TestSuiteSetupAndTeardown 117 | gTable2("Test suite setup/teardown 2", "Suite 1"); 118 | 119 | TEST_SUITE("Test part 1 of suite", "Suite 1") 120 | { 121 | // If this was a project test, it could use 122 | // the table names from gTable1 and gTable2. 123 | CONFIRM("test_data_01", gTable1.tableName()); 124 | CONFIRM("test_data_01", gTable2.tableName()); 125 | } 126 | 127 | TEST_SUITE_EX("Test part 2 of suite", "Suite 1", int) 128 | { 129 | // If this was a project test, it could use 130 | // the table names from gTable1 and gTable2. 131 | throw 1; 132 | } 133 | -------------------------------------------------------------------------------- /Chapter07/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter09/MereMemo/Log.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_LOG_H 2 | #define MEREMEMO_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace MereMemo 13 | { 14 | 15 | inline std::fstream log () 16 | { 17 | auto const now = std::chrono::system_clock::now(); 18 | std::time_t const tmNow = std::chrono::system_clock::to_time_t(now); 19 | auto const ms = duration_cast( 20 | now.time_since_epoch()) % 1000; 21 | 22 | std::fstream logFile("application.log", std::ios::app); 23 | logFile << std::endl 24 | << std::put_time(std::gmtime(&tmNow), "%Y-%m-%dT%H:%M:%S.") 25 | << std::setw(3) << std::setfill('0') << std::to_string(ms.count()) 26 | << " "; 27 | 28 | return logFile; 29 | } 30 | 31 | } // namespace MereMemo 32 | 33 | #endif // MEREMEMO_LOG_H 34 | -------------------------------------------------------------------------------- /Chapter09/MereMemo/tests/Construction.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "Util.h" 4 | 5 | #include 6 | 7 | TEST("Simple message can be logged") 8 | { 9 | std::string message = "simple "; 10 | message += Util::randomString(); 11 | MereMemo::log() << message << " with more text."; 12 | 13 | bool result = Util::isTextInFile(message, "application.log"); 14 | CONFIRM_TRUE(result); 15 | } 16 | 17 | TEST("Complicated message can be logged") 18 | { 19 | std::string message = "complicated "; 20 | message += Util::randomString(); 21 | MereMemo::log() << message 22 | << " double=" << 3.14 23 | << " quoted=" << std::quoted("in quotes"); 24 | 25 | bool result = Util::isTextInFile(message, "application.log"); 26 | CONFIRM_TRUE(result); 27 | } 28 | -------------------------------------------------------------------------------- /Chapter09/MereMemo/tests/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | std::string Util::randomString () 8 | { 9 | static bool firstCall = true; 10 | static std::mt19937 rng; 11 | if (firstCall) 12 | { 13 | // We only need to set the seed once. 14 | firstCall = false; 15 | 16 | unsigned int seed = static_cast( 17 | std::chrono::system_clock::now(). 18 | time_since_epoch().count()); 19 | rng.seed(seed); 20 | } 21 | std::uniform_int_distribution dist(1, 10000); 22 | 23 | return std::to_string(dist(rng)); 24 | } 25 | 26 | bool Util::isTextInFile ( 27 | std::string_view text, 28 | std::string_view fileName) 29 | { 30 | std::ifstream logfile(fileName.data()); 31 | std::string line; 32 | while (getline(logfile, line)) 33 | { 34 | if (line.find(text) != std::string::npos) 35 | { 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | -------------------------------------------------------------------------------- /Chapter09/MereMemo/tests/Util.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_TESTS_UTIL_H 2 | #define MEREMEMO_TESTS_UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | struct Util 8 | { 9 | static std::string randomString (); 10 | 11 | static bool isTextInFile ( 12 | std::string_view text, 13 | std::string_view fileName); 14 | }; 15 | 16 | #endif // MEREMEMO_TESTS_UTIL_H 17 | -------------------------------------------------------------------------------- /Chapter09/MereMemo/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter10/MereMemo/Log.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_LOG_H 2 | #define MEREMEMO_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace MereMemo 18 | { 19 | 20 | class Tag 21 | { 22 | public: 23 | virtual ~Tag () = default; 24 | 25 | std::string key () const 26 | { 27 | return mKey; 28 | } 29 | 30 | std::string text () const 31 | { 32 | return mText; 33 | } 34 | 35 | virtual std::unique_ptr clone () const = 0; 36 | 37 | virtual bool match (Tag const & other) const = 0; 38 | 39 | protected: 40 | Tag (std::string const & key, std::string const & value) 41 | : mKey(key), mText(key + "=\"" + value + "\"") 42 | { } 43 | 44 | Tag (std::string const & key, int value) 45 | : mKey(key), mText(key + "=" + std::to_string(value)) 46 | { } 47 | 48 | Tag (std::string const & key, long long value) 49 | : mKey(key), mText(key + "=" + std::to_string(value)) 50 | { } 51 | 52 | Tag (std::string const & key, double value) 53 | : mKey(key), mText(key + "=" + std::to_string(value)) 54 | { } 55 | 56 | Tag (std::string const & key, bool value) 57 | : mKey(key), mText(key + "=" + (value?"true":"false")) 58 | { } 59 | 60 | private: 61 | std::string mKey; 62 | std::string const mText; 63 | }; 64 | 65 | inline std::string to_string (Tag const & tag) 66 | { 67 | return tag.text(); 68 | } 69 | 70 | inline std::ostream & operator << (std::ostream && stream, Tag const & tag) 71 | { 72 | stream << to_string(tag); 73 | return stream; 74 | } 75 | 76 | enum class TagOperation 77 | { 78 | None, 79 | Equal, 80 | LessThan, 81 | LessThanOrEqual, 82 | GreaterThan, 83 | GreaterThanOrEqual 84 | }; 85 | 86 | template 87 | class TagType : public Tag 88 | { 89 | public: 90 | std::unique_ptr clone () const override 91 | { 92 | return std::unique_ptr( 93 | new T(*static_cast(this))); 94 | } 95 | 96 | bool match (Tag const & other) const override 97 | { 98 | if (key() != other.key()) 99 | { 100 | return false; 101 | } 102 | TagType const & otherCast = static_cast(other); 103 | if (mOperation == TagOperation::None) 104 | { 105 | switch (otherCast.mOperation) 106 | { 107 | case TagOperation::None: 108 | return mValue == otherCast.mValue; 109 | 110 | default: 111 | return compareTagTypes(mValue, 112 | otherCast.mOperation, 113 | otherCast.mValue); 114 | } 115 | } 116 | switch (otherCast.mOperation) 117 | { 118 | case TagOperation::None: 119 | return compareTagTypes(otherCast.mValue, 120 | mOperation, 121 | mValue); 122 | 123 | default: 124 | return false; 125 | } 126 | } 127 | 128 | ValueT value () const 129 | { 130 | return mValue; 131 | } 132 | 133 | protected: 134 | TagType (ValueT const & value, 135 | TagOperation operation) 136 | : Tag(T::key, value), mValue(value), mOperation(operation) 137 | { } 138 | 139 | virtual bool compareTagTypes (ValueT const & value, 140 | TagOperation operation, 141 | ValueT const & criteria) const 142 | { 143 | return false; 144 | } 145 | 146 | ValueT mValue; 147 | TagOperation mOperation; 148 | }; 149 | 150 | template 151 | class StringTagType : public TagType 152 | { 153 | protected: 154 | StringTagType (std::string const & value, 155 | TagOperation operation) 156 | : TagType(value, operation) 157 | { } 158 | 159 | bool compareTagTypes (std::string const & value, 160 | TagOperation operation, 161 | std::string const & criteria) const override 162 | { 163 | int result = value.compare(criteria); 164 | switch (operation) 165 | { 166 | case TagOperation::Equal: 167 | return result == 0; 168 | 169 | case TagOperation::LessThan: 170 | return result == -1; 171 | 172 | case TagOperation::LessThanOrEqual: 173 | return result == 0 || result == -1; 174 | 175 | case TagOperation::GreaterThan: 176 | return result == 1; 177 | 178 | case TagOperation::GreaterThanOrEqual: 179 | return result == 0 || result == 1; 180 | 181 | default: 182 | return false; 183 | } 184 | } 185 | }; 186 | 187 | template 188 | class IntTagType : public TagType 189 | { 190 | protected: 191 | IntTagType (int const & value, 192 | TagOperation operation) 193 | : TagType(value, operation) 194 | { } 195 | 196 | bool compareTagTypes (int const & value, 197 | TagOperation operation, 198 | int const & criteria) const override 199 | { 200 | switch (operation) 201 | { 202 | case TagOperation::Equal: 203 | return value == criteria; 204 | 205 | case TagOperation::LessThan: 206 | return value < criteria; 207 | 208 | case TagOperation::LessThanOrEqual: 209 | return value <= criteria; 210 | 211 | case TagOperation::GreaterThan: 212 | return value > criteria; 213 | 214 | case TagOperation::GreaterThanOrEqual: 215 | return value >= criteria; 216 | 217 | default: 218 | return false; 219 | } 220 | } 221 | }; 222 | 223 | template 224 | class LongLongTagType : public TagType 225 | { 226 | protected: 227 | LongLongTagType (long long const & value, 228 | TagOperation operation) 229 | : TagType(value, operation) 230 | { } 231 | 232 | bool compareTagTypes (long long const & value, 233 | TagOperation operation, 234 | long long const & criteria) const override 235 | { 236 | switch (operation) 237 | { 238 | case TagOperation::Equal: 239 | return value == criteria; 240 | 241 | case TagOperation::LessThan: 242 | return value < criteria; 243 | 244 | case TagOperation::LessThanOrEqual: 245 | return value <= criteria; 246 | 247 | case TagOperation::GreaterThan: 248 | return value > criteria; 249 | 250 | case TagOperation::GreaterThanOrEqual: 251 | return value >= criteria; 252 | 253 | default: 254 | return false; 255 | } 256 | } 257 | }; 258 | 259 | template 260 | class DoubleTagType : public TagType 261 | { 262 | protected: 263 | DoubleTagType (double const & value, 264 | TagOperation operation) 265 | : TagType(value, operation) 266 | { } 267 | 268 | bool compareTagTypes (double const & value, 269 | TagOperation operation, 270 | double const & criteria) const override 271 | { 272 | switch (operation) 273 | { 274 | case TagOperation::Equal: 275 | return value == criteria; 276 | 277 | case TagOperation::LessThan: 278 | return value < criteria; 279 | 280 | case TagOperation::LessThanOrEqual: 281 | return value <= criteria; 282 | 283 | case TagOperation::GreaterThan: 284 | return value > criteria; 285 | 286 | case TagOperation::GreaterThanOrEqual: 287 | return value >= criteria; 288 | 289 | default: 290 | return false; 291 | } 292 | } 293 | }; 294 | 295 | template 296 | class BoolTagType : public TagType 297 | { 298 | protected: 299 | BoolTagType (bool const & value, 300 | TagOperation operation) 301 | : TagType(value, operation) 302 | { } 303 | 304 | bool compareTagTypes (bool const & value, 305 | TagOperation operation, 306 | bool const & criteria) const override 307 | { 308 | switch (operation) 309 | { 310 | case TagOperation::Equal: 311 | return value == criteria; 312 | 313 | default: 314 | return false; 315 | } 316 | } 317 | }; 318 | 319 | class LogLevel : public StringTagType 320 | { 321 | public: 322 | static constexpr char key[] = "log_level"; 323 | 324 | LogLevel (std::string const & value, 325 | TagOperation operation = TagOperation::None) 326 | : StringTagType(value, operation) 327 | { } 328 | }; 329 | 330 | inline std::map> & getDefaultTags () 331 | { 332 | static std::map> tags; 333 | return tags; 334 | } 335 | 336 | inline void addDefaultTag (Tag const & tag) 337 | { 338 | auto & tags = getDefaultTags(); 339 | tags[tag.key()] = tag.clone(); 340 | } 341 | 342 | struct FilterClause 343 | { 344 | std::vector> normalLiterals; 345 | std::vector> invertedLiterals; 346 | }; 347 | 348 | inline std::map & getFilterClauses () 349 | { 350 | static std::map clauses; 351 | return clauses; 352 | } 353 | 354 | inline int createFilterClause () 355 | { 356 | static int currentId = 0; 357 | ++currentId; 358 | auto & clauses = getFilterClauses(); 359 | clauses[currentId] = FilterClause(); 360 | 361 | return currentId; 362 | } 363 | 364 | inline void addFilterLiteral (int filterId, 365 | Tag const & tag, 366 | bool normal = true) 367 | { 368 | auto & clauses = getFilterClauses(); 369 | if (clauses.contains(filterId)) 370 | { 371 | if (normal) 372 | { 373 | clauses[filterId].normalLiterals.push_back( 374 | tag.clone()); 375 | } 376 | else 377 | { 378 | clauses[filterId].invertedLiterals.push_back( 379 | tag.clone()); 380 | } 381 | } 382 | } 383 | 384 | inline void clearFilterClause (int filterId) 385 | { 386 | auto & clauses = getFilterClauses(); 387 | clauses.erase(filterId); 388 | } 389 | 390 | class LogStream : public std::stringstream 391 | { 392 | public: 393 | LogStream (std::string const & filename, 394 | std::ios_base::openmode mode = ios_base::app) 395 | : mProceed(true), mFile(filename, mode) 396 | { } 397 | 398 | LogStream (LogStream const & other) = delete; 399 | 400 | LogStream (LogStream && other) 401 | : std::stringstream(std::move(other)), 402 | mProceed(other.mProceed), mFile(std::move(other.mFile)) 403 | { } 404 | 405 | ~LogStream () 406 | { 407 | if (not mProceed) 408 | { 409 | return; 410 | } 411 | mFile << this->str(); 412 | mFile << std::endl; 413 | } 414 | 415 | LogStream & operator = (LogStream const & rhs) = delete; 416 | LogStream & operator = (LogStream && rhs) = delete; 417 | 418 | void ignore () 419 | { 420 | mProceed = false; 421 | } 422 | 423 | private: 424 | bool mProceed; 425 | std::fstream mFile; 426 | }; 427 | 428 | inline LogStream log (std::vector tags = {}) 429 | { 430 | auto const now = std::chrono::system_clock::now(); 431 | std::time_t const tmNow = std::chrono::system_clock::to_time_t(now); 432 | auto const ms = duration_cast( 433 | now.time_since_epoch()) % 1000; 434 | 435 | LogStream ls("application.log"); 436 | ls << std::put_time(std::gmtime(&tmNow), "%Y-%m-%dT%H:%M:%S.") 437 | << std::setw(3) << std::setfill('0') << std::to_string(ms.count()); 438 | 439 | std::map activeTags; 440 | for (auto const & defaultTag: getDefaultTags()) 441 | { 442 | activeTags[defaultTag.first] = defaultTag.second.get(); 443 | } 444 | for (auto const & tag: tags) 445 | { 446 | activeTags[tag->key()] = tag; 447 | } 448 | for (auto const & activeEntry: activeTags) 449 | { 450 | ls << " " << activeEntry.second->text(); 451 | } 452 | ls << " "; 453 | 454 | bool proceed = true; 455 | for (auto const & clause: getFilterClauses()) 456 | { 457 | proceed = false; 458 | bool allLiteralsMatch = true; 459 | for (auto const & normal: clause.second.normalLiterals) 460 | { 461 | // We need to make sure that the tag is 462 | // present and with the correct value. 463 | if (not activeTags.contains(normal->key())) 464 | { 465 | allLiteralsMatch = false; 466 | break; 467 | } 468 | if (not activeTags[normal->key()]->match(*normal)) 469 | { 470 | allLiteralsMatch = false; 471 | break; 472 | } 473 | } 474 | if (not allLiteralsMatch) 475 | { 476 | continue; 477 | } 478 | for (auto const & inverted: clause.second.invertedLiterals) 479 | { 480 | // We need to make sure that the tag is either 481 | // not present or has a mismatched value. 482 | if (activeTags.contains(inverted->key())) 483 | { 484 | if (activeTags[inverted->key()]->match(*inverted)) 485 | { 486 | allLiteralsMatch = false; 487 | } 488 | break; 489 | } 490 | } 491 | if (allLiteralsMatch) 492 | { 493 | proceed = true; 494 | break; 495 | } 496 | } 497 | 498 | if (not proceed) 499 | { 500 | ls.ignore(); 501 | } 502 | return ls; 503 | } 504 | 505 | inline auto log (Tag const & tag1) 506 | { 507 | return log({&tag1}); 508 | } 509 | 510 | inline auto log (Tag const & tag1, 511 | Tag const & tag2) 512 | { 513 | return log({&tag1, &tag2}); 514 | } 515 | 516 | inline auto log (Tag const & tag1, 517 | Tag const & tag2, 518 | Tag const & tag3) 519 | { 520 | return log({&tag1, &tag2, &tag3}); 521 | } 522 | 523 | } // namespace MereMemo 524 | 525 | #endif // MEREMEMO_LOG_H 526 | -------------------------------------------------------------------------------- /Chapter10/MereMemo/tests/Construction.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "Util.h" 4 | 5 | #include 6 | 7 | TEST("Simple message can be logged") 8 | { 9 | std::string message = "simple "; 10 | message += Util::randomString(); 11 | MereMemo::log() << message << " with more text."; 12 | 13 | bool result = Util::isTextInFile(message, "application.log"); 14 | CONFIRM_TRUE(result); 15 | } 16 | 17 | TEST("Complicated message can be logged") 18 | { 19 | std::string message = "complicated "; 20 | message += Util::randomString(); 21 | MereMemo::log() << message 22 | << " double=" << 3.14 23 | << " quoted=" << std::quoted("in quotes"); 24 | 25 | bool result = Util::isTextInFile(message, "application.log"); 26 | CONFIRM_TRUE(result); 27 | } 28 | -------------------------------------------------------------------------------- /Chapter10/MereMemo/tests/LogTags.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_TESTS_LOGTAGS_H 2 | #define MEREMEMO_TESTS_LOGTAGS_H 3 | 4 | #include "../Log.h" 5 | 6 | inline MereMemo::LogLevel error("error"); 7 | inline MereMemo::LogLevel info("info"); 8 | inline MereMemo::LogLevel debug("debug"); 9 | 10 | class Color : public MereMemo::StringTagType 11 | { 12 | public: 13 | static constexpr char key[] = "color"; 14 | 15 | Color (std::string const & value, 16 | MereMemo::TagOperation operation = 17 | MereMemo::TagOperation::None) 18 | : StringTagType(value, operation) 19 | { } 20 | }; 21 | 22 | inline Color red("red"); 23 | inline Color green("green"); 24 | inline Color blue("blue"); 25 | 26 | class Size : public MereMemo::StringTagType 27 | { 28 | public: 29 | static constexpr char key[] = "size"; 30 | 31 | Size (std::string const & value, 32 | MereMemo::TagOperation operation = 33 | MereMemo::TagOperation::None) 34 | : StringTagType(value, operation) 35 | { } 36 | }; 37 | 38 | inline Size small("small"); 39 | inline Size medium("medium"); 40 | inline Size large("large"); 41 | 42 | class Count : public MereMemo::IntTagType 43 | { 44 | public: 45 | static constexpr char key[] = "count"; 46 | 47 | Count (int value, 48 | MereMemo::TagOperation operation = 49 | MereMemo::TagOperation::None) 50 | : IntTagType(value, operation) 51 | { } 52 | }; 53 | 54 | class Identity : public MereMemo::LongLongTagType 55 | { 56 | public: 57 | static constexpr char key[] = "id"; 58 | 59 | Identity (long long value, 60 | MereMemo::TagOperation operation = 61 | MereMemo::TagOperation::None) 62 | : LongLongTagType(value, operation) 63 | { } 64 | }; 65 | 66 | class Scale : public MereMemo::DoubleTagType 67 | { 68 | public: 69 | static constexpr char key[] = "scale"; 70 | 71 | Scale (double value, 72 | MereMemo::TagOperation operation = 73 | MereMemo::TagOperation::None) 74 | : DoubleTagType(value, operation) 75 | { } 76 | }; 77 | 78 | class CacheHit : public MereMemo::BoolTagType 79 | { 80 | public: 81 | static constexpr char key[] = "cache_hit"; 82 | 83 | CacheHit (bool value, 84 | MereMemo::TagOperation operation = 85 | MereMemo::TagOperation::None) 86 | : BoolTagType(value, operation) 87 | { } 88 | }; 89 | 90 | inline CacheHit cacheHit(true); 91 | inline CacheHit cacheMiss(false); 92 | 93 | #endif // MEREMEMO_TESTS_LOGTAGS_H 94 | -------------------------------------------------------------------------------- /Chapter10/MereMemo/tests/Tags.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "LogTags.h" 4 | #include "Util.h" 5 | 6 | #include 7 | 8 | class TempFilterClause 9 | { 10 | public: 11 | void setup () 12 | { 13 | mId = MereMemo::createFilterClause(); 14 | } 15 | 16 | void teardown () 17 | { 18 | MereMemo::clearFilterClause(mId); 19 | } 20 | 21 | int id () const 22 | { 23 | return mId; 24 | } 25 | 26 | private: 27 | int mId; 28 | }; 29 | 30 | TEST("Message can be tagged in log") 31 | { 32 | std::string message = "simple tag "; 33 | message += Util::randomString(); 34 | MereMemo::log(error) << message; 35 | 36 | // Confirm that the error tag value exists and that the 37 | // default info tag value does not. 38 | std::string logLevelTag = " log_level=\"error\" "; 39 | std::string defaultLogLevelTag = " log_level=\"info\" "; 40 | bool result = Util::isTextInFile(message, "application.log", 41 | {logLevelTag}, {defaultLogLevelTag}); 42 | CONFIRM_TRUE(result); 43 | } 44 | 45 | TEST("log needs no namespace when used with LogLevel") 46 | { 47 | log(error) << "no namespace"; 48 | } 49 | 50 | TEST("Default tags set in main appear in log") 51 | { 52 | std::string message = "default tag "; 53 | message += Util::randomString(); 54 | MereMemo::log() << message; 55 | 56 | std::string logLevelTag = " log_level=\"info\" "; 57 | std::string colorTag = " color=\"green\" "; 58 | bool result = Util::isTextInFile(message, "application.log", 59 | {logLevelTag, colorTag}); 60 | CONFIRM_TRUE(result); 61 | } 62 | 63 | TEST("Multiple tags can be used in log") 64 | { 65 | std::string message = "multi tags "; 66 | message += Util::randomString(); 67 | MereMemo::log(debug, red, large) << message; 68 | 69 | std::string logLevelTag = " log_level=\"debug\" "; 70 | std::string colorTag = " color=\"red\" "; 71 | std::string sizeTag = " size=\"large\" "; 72 | bool result = Util::isTextInFile(message, "application.log", 73 | {logLevelTag, colorTag, sizeTag}); 74 | CONFIRM_TRUE(result); 75 | } 76 | 77 | TEST("Tags can be streamed to log") 78 | { 79 | std::string messageBase = " 1 type "; 80 | std::string message = messageBase + Util::randomString(); 81 | MereMemo::log(info) << Count(1) << message; 82 | 83 | std::string countTag = " count=1 "; 84 | bool result = Util::isTextInFile(message, "application.log", 85 | {countTag}); 86 | CONFIRM_TRUE(result); 87 | 88 | messageBase = " 2 type "; 89 | message = messageBase + Util::randomString(); 90 | MereMemo::log(info) << Identity(123456789012345) << message; 91 | 92 | std::string idTag = " id=123456789012345 "; 93 | result = Util::isTextInFile(message, "application.log", 94 | {idTag}); 95 | CONFIRM_TRUE(result); 96 | 97 | messageBase = " 3 type "; 98 | message = messageBase + Util::randomString(); 99 | MereMemo::log(info) << Scale(1.5) << message; 100 | 101 | std::string scaleTag = " scale=1.500000 "; 102 | result = Util::isTextInFile(message, "application.log", 103 | {scaleTag}); 104 | CONFIRM_TRUE(result); 105 | 106 | messageBase = " 4 type "; 107 | message = messageBase + Util::randomString(); 108 | MereMemo::log(info) << cacheMiss << message; 109 | 110 | std::string cacheTag = " cache_hit=false "; 111 | result = Util::isTextInFile(message, "application.log", 112 | {cacheTag}); 113 | CONFIRM_TRUE(result); 114 | } 115 | 116 | TEST("Tags can be used to filter messages") 117 | { 118 | MereTDD::SetupAndTeardown filter; 119 | MereMemo::addFilterLiteral(filter.id(), error); 120 | 121 | std::string message = "filter "; 122 | message += Util::randomString(); 123 | MereMemo::log(info) << message; 124 | 125 | bool result = Util::isTextInFile(message, "application.log"); 126 | CONFIRM_FALSE(result); 127 | 128 | MereMemo::clearFilterClause(filter.id()); 129 | 130 | MereMemo::log(info) << message; 131 | 132 | result = Util::isTextInFile(message, "application.log"); 133 | CONFIRM_TRUE(result); 134 | } 135 | 136 | TEST("Overridden default tag not used to filter messages") 137 | { 138 | MereTDD::SetupAndTeardown filter; 139 | MereMemo::addFilterLiteral(filter.id(), info); 140 | 141 | std::string message = "override default "; 142 | message += Util::randomString(); 143 | MereMemo::log(debug) << message; 144 | 145 | bool result = Util::isTextInFile(message, "application.log"); 146 | CONFIRM_FALSE(result); 147 | } 148 | 149 | TEST("Inverted tag can be used to filter messages") 150 | { 151 | MereTDD::SetupAndTeardown filter; 152 | MereMemo::addFilterLiteral(filter.id(), green, false); 153 | 154 | std::string message = "inverted "; 155 | message += Util::randomString(); 156 | MereMemo::log(info) << message; 157 | 158 | bool result = Util::isTextInFile(message, "application.log"); 159 | CONFIRM_FALSE(result); 160 | } 161 | 162 | TEST("Tag values can be used to filter messages") 163 | { 164 | MereTDD::SetupAndTeardown filter; 165 | MereMemo::addFilterLiteral(filter.id(), 166 | Count(100, MereMemo::TagOperation::GreaterThan)); 167 | 168 | std::string message = "values "; 169 | message += Util::randomString(); 170 | MereMemo::log(Count(1)) << message; 171 | 172 | bool result = Util::isTextInFile(message, "application.log"); 173 | CONFIRM_FALSE(result); 174 | 175 | MereMemo::log() << Count(101) << message; 176 | 177 | result = Util::isTextInFile(message, "application.log"); 178 | CONFIRM_FALSE(result); 179 | 180 | MereMemo::log(Count(101)) << message; 181 | 182 | result = Util::isTextInFile(message, "application.log"); 183 | CONFIRM_TRUE(result); 184 | } 185 | -------------------------------------------------------------------------------- /Chapter10/MereMemo/tests/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | std::string Util::randomString () 8 | { 9 | static bool firstCall = true; 10 | static std::mt19937 rng; 11 | if (firstCall) 12 | { 13 | // We only need to set the seed once. 14 | firstCall = false; 15 | 16 | unsigned int seed = static_cast( 17 | std::chrono::system_clock::now(). 18 | time_since_epoch().count()); 19 | rng.seed(seed); 20 | } 21 | std::uniform_int_distribution dist(1, 10000); 22 | 23 | return std::to_string(dist(rng)); 24 | } 25 | 26 | bool Util::isTextInFile ( 27 | std::string_view text, 28 | std::string_view fileName, 29 | std::vector const & wantedTags, 30 | std::vector const & unwantedTags) 31 | { 32 | std::ifstream logfile(fileName.data()); 33 | std::string line; 34 | while (getline(logfile, line)) 35 | { 36 | if (line.find(text) != std::string::npos) 37 | { 38 | for (auto const & tag: wantedTags) 39 | { 40 | if (line.find(tag) == std::string::npos) 41 | { 42 | return false; 43 | } 44 | } 45 | for (auto const & tag: unwantedTags) 46 | { 47 | if (line.find(tag) != std::string::npos) 48 | { 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | -------------------------------------------------------------------------------- /Chapter10/MereMemo/tests/Util.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_TESTS_UTIL_H 2 | #define MEREMEMO_TESTS_UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct Util 9 | { 10 | static std::string randomString (); 11 | 12 | static bool isTextInFile ( 13 | std::string_view text, 14 | std::string_view fileName, 15 | std::vector const & wantedTags = {}, 16 | std::vector const & unwantedTags = {}); 17 | }; 18 | 19 | #endif // MEREMEMO_TESTS_UTIL_H 20 | -------------------------------------------------------------------------------- /Chapter10/MereMemo/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "LogTags.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | int main () 10 | { 11 | MereMemo::addDefaultTag(info); 12 | MereMemo::addDefaultTag(green); 13 | 14 | return MereTDD::runTests(std::cout); 15 | } 16 | -------------------------------------------------------------------------------- /Chapter11/MereMemo/Log.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_LOG_H 2 | #define MEREMEMO_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace MereMemo 19 | { 20 | 21 | class Tag 22 | { 23 | public: 24 | virtual ~Tag () = default; 25 | 26 | std::string key () const 27 | { 28 | return mKey; 29 | } 30 | 31 | std::string text () const 32 | { 33 | return mText; 34 | } 35 | 36 | virtual std::unique_ptr clone () const = 0; 37 | 38 | virtual bool match (Tag const & other) const = 0; 39 | 40 | protected: 41 | Tag (std::string const & key, std::string const & value) 42 | : mKey(key), mText(key + "=\"" + value + "\"") 43 | { } 44 | 45 | Tag (std::string const & key, int value) 46 | : mKey(key), mText(key + "=" + std::to_string(value)) 47 | { } 48 | 49 | Tag (std::string const & key, long long value) 50 | : mKey(key), mText(key + "=" + std::to_string(value)) 51 | { } 52 | 53 | Tag (std::string const & key, double value) 54 | : mKey(key), mText(key + "=" + std::to_string(value)) 55 | { } 56 | 57 | Tag (std::string const & key, bool value) 58 | : mKey(key), mText(key + "=" + (value?"true":"false")) 59 | { } 60 | 61 | private: 62 | std::string mKey; 63 | std::string const mText; 64 | }; 65 | 66 | inline std::string to_string (Tag const & tag) 67 | { 68 | return tag.text(); 69 | } 70 | 71 | inline std::ostream & operator << (std::ostream && stream, Tag const & tag) 72 | { 73 | stream << to_string(tag); 74 | return stream; 75 | } 76 | 77 | enum class TagOperation 78 | { 79 | None, 80 | Equal, 81 | LessThan, 82 | LessThanOrEqual, 83 | GreaterThan, 84 | GreaterThanOrEqual 85 | }; 86 | 87 | template 88 | class TagType : public Tag 89 | { 90 | public: 91 | std::unique_ptr clone () const override 92 | { 93 | return std::unique_ptr( 94 | new T(*static_cast(this))); 95 | } 96 | 97 | bool match (Tag const & other) const override 98 | { 99 | if (key() != other.key()) 100 | { 101 | return false; 102 | } 103 | TagType const & otherCast = static_cast(other); 104 | if (mOperation == TagOperation::None) 105 | { 106 | switch (otherCast.mOperation) 107 | { 108 | case TagOperation::None: 109 | return mValue == otherCast.mValue; 110 | 111 | default: 112 | return compareTagTypes(mValue, 113 | otherCast.mOperation, 114 | otherCast.mValue); 115 | } 116 | } 117 | switch (otherCast.mOperation) 118 | { 119 | case TagOperation::None: 120 | return compareTagTypes(otherCast.mValue, 121 | mOperation, 122 | mValue); 123 | 124 | default: 125 | return false; 126 | } 127 | } 128 | 129 | ValueT value () const 130 | { 131 | return mValue; 132 | } 133 | 134 | protected: 135 | TagType (ValueT const & value, 136 | TagOperation operation) 137 | : Tag(T::key, value), mValue(value), mOperation(operation) 138 | { } 139 | 140 | virtual bool compareTagTypes (ValueT const & value, 141 | TagOperation operation, 142 | ValueT const & criteria) const 143 | { 144 | return false; 145 | } 146 | 147 | ValueT mValue; 148 | TagOperation mOperation; 149 | }; 150 | 151 | template 152 | class StringTagType : public TagType 153 | { 154 | protected: 155 | StringTagType (std::string const & value, 156 | TagOperation operation) 157 | : TagType(value, operation) 158 | { } 159 | 160 | bool compareTagTypes (std::string const & value, 161 | TagOperation operation, 162 | std::string const & criteria) const override 163 | { 164 | int result = value.compare(criteria); 165 | switch (operation) 166 | { 167 | case TagOperation::Equal: 168 | return result == 0; 169 | 170 | case TagOperation::LessThan: 171 | return result == -1; 172 | 173 | case TagOperation::LessThanOrEqual: 174 | return result == 0 || result == -1; 175 | 176 | case TagOperation::GreaterThan: 177 | return result == 1; 178 | 179 | case TagOperation::GreaterThanOrEqual: 180 | return result == 0 || result == 1; 181 | 182 | default: 183 | return false; 184 | } 185 | } 186 | }; 187 | 188 | template 189 | class IntTagType : public TagType 190 | { 191 | protected: 192 | IntTagType (int const & value, 193 | TagOperation operation) 194 | : TagType(value, operation) 195 | { } 196 | 197 | bool compareTagTypes (int const & value, 198 | TagOperation operation, 199 | int const & criteria) const override 200 | { 201 | switch (operation) 202 | { 203 | case TagOperation::Equal: 204 | return value == criteria; 205 | 206 | case TagOperation::LessThan: 207 | return value < criteria; 208 | 209 | case TagOperation::LessThanOrEqual: 210 | return value <= criteria; 211 | 212 | case TagOperation::GreaterThan: 213 | return value > criteria; 214 | 215 | case TagOperation::GreaterThanOrEqual: 216 | return value >= criteria; 217 | 218 | default: 219 | return false; 220 | } 221 | } 222 | }; 223 | 224 | template 225 | class LongLongTagType : public TagType 226 | { 227 | protected: 228 | LongLongTagType (long long const & value, 229 | TagOperation operation) 230 | : TagType(value, operation) 231 | { } 232 | 233 | bool compareTagTypes (long long const & value, 234 | TagOperation operation, 235 | long long const & criteria) const override 236 | { 237 | switch (operation) 238 | { 239 | case TagOperation::Equal: 240 | return value == criteria; 241 | 242 | case TagOperation::LessThan: 243 | return value < criteria; 244 | 245 | case TagOperation::LessThanOrEqual: 246 | return value <= criteria; 247 | 248 | case TagOperation::GreaterThan: 249 | return value > criteria; 250 | 251 | case TagOperation::GreaterThanOrEqual: 252 | return value >= criteria; 253 | 254 | default: 255 | return false; 256 | } 257 | } 258 | }; 259 | 260 | template 261 | class DoubleTagType : public TagType 262 | { 263 | protected: 264 | DoubleTagType (double const & value, 265 | TagOperation operation) 266 | : TagType(value, operation) 267 | { } 268 | 269 | bool compareTagTypes (double const & value, 270 | TagOperation operation, 271 | double const & criteria) const override 272 | { 273 | switch (operation) 274 | { 275 | case TagOperation::Equal: 276 | return value == criteria; 277 | 278 | case TagOperation::LessThan: 279 | return value < criteria; 280 | 281 | case TagOperation::LessThanOrEqual: 282 | return value <= criteria; 283 | 284 | case TagOperation::GreaterThan: 285 | return value > criteria; 286 | 287 | case TagOperation::GreaterThanOrEqual: 288 | return value >= criteria; 289 | 290 | default: 291 | return false; 292 | } 293 | } 294 | }; 295 | 296 | template 297 | class BoolTagType : public TagType 298 | { 299 | protected: 300 | BoolTagType (bool const & value, 301 | TagOperation operation) 302 | : TagType(value, operation) 303 | { } 304 | 305 | bool compareTagTypes (bool const & value, 306 | TagOperation operation, 307 | bool const & criteria) const override 308 | { 309 | switch (operation) 310 | { 311 | case TagOperation::Equal: 312 | return value == criteria; 313 | 314 | default: 315 | return false; 316 | } 317 | } 318 | }; 319 | 320 | class LogLevel : public StringTagType 321 | { 322 | public: 323 | static constexpr char key[] = "log_level"; 324 | 325 | LogLevel (std::string const & value, 326 | TagOperation operation = TagOperation::None) 327 | : StringTagType(value, operation) 328 | { } 329 | }; 330 | 331 | inline std::map> & getDefaultTags () 332 | { 333 | static std::map> tags; 334 | return tags; 335 | } 336 | 337 | inline void addDefaultTag (Tag const & tag) 338 | { 339 | auto & tags = getDefaultTags(); 340 | tags[tag.key()] = tag.clone(); 341 | } 342 | 343 | struct FilterClause 344 | { 345 | std::vector> normalLiterals; 346 | std::vector> invertedLiterals; 347 | }; 348 | 349 | inline std::map & getFilterClauses () 350 | { 351 | static std::map clauses; 352 | return clauses; 353 | } 354 | 355 | inline int createFilterClause () 356 | { 357 | static int currentId = 0; 358 | ++currentId; 359 | auto & clauses = getFilterClauses(); 360 | clauses[currentId] = FilterClause(); 361 | 362 | return currentId; 363 | } 364 | 365 | inline void addFilterLiteral (int filterId, 366 | Tag const & tag, 367 | bool normal = true) 368 | { 369 | auto & clauses = getFilterClauses(); 370 | if (clauses.contains(filterId)) 371 | { 372 | if (normal) 373 | { 374 | clauses[filterId].normalLiterals.push_back( 375 | tag.clone()); 376 | } 377 | else 378 | { 379 | clauses[filterId].invertedLiterals.push_back( 380 | tag.clone()); 381 | } 382 | } 383 | } 384 | 385 | inline void clearFilterClause (int filterId) 386 | { 387 | auto & clauses = getFilterClauses(); 388 | clauses.erase(filterId); 389 | } 390 | 391 | class Output 392 | { 393 | public: 394 | virtual ~Output () = default; 395 | Output (Output const & other) = delete; 396 | Output (Output && other) = delete; 397 | 398 | virtual std::unique_ptr clone () const = 0; 399 | 400 | virtual void sendLine (std::string const & line) = 0; 401 | 402 | Output & operator = (Output const & rhs) = delete; 403 | Output & operator = (Output && rhs) = delete; 404 | 405 | protected: 406 | Output () = default; 407 | }; 408 | 409 | inline std::vector> & getOutputs () 410 | { 411 | static std::vector> outputs; 412 | return outputs; 413 | } 414 | 415 | inline void addLogOutput (Output const & output) 416 | { 417 | auto & outputs = getOutputs(); 418 | outputs.push_back(output.clone()); 419 | } 420 | 421 | class FileOutput : public Output 422 | { 423 | public: 424 | FileOutput (std::string_view dir) 425 | : mOutputDir(dir), 426 | mFileNamePattern("{}"), 427 | mMaxSize(0), 428 | mRolloverCount(0) 429 | { } 430 | 431 | FileOutput (FileOutput const & rhs) 432 | : mOutputDir(rhs.mOutputDir), 433 | mFileNamePattern(rhs.mFileNamePattern), 434 | mMaxSize(rhs.mMaxSize), 435 | mRolloverCount(rhs.mRolloverCount) 436 | { } 437 | 438 | FileOutput (FileOutput && rhs) 439 | : mOutputDir(rhs.mOutputDir), 440 | mFileNamePattern(rhs.mFileNamePattern), 441 | mMaxSize(rhs.mMaxSize), 442 | mRolloverCount(rhs.mRolloverCount), 443 | mFile(std::move(rhs.mFile)) 444 | { } 445 | 446 | ~FileOutput () 447 | { 448 | mFile.close(); 449 | } 450 | 451 | std::unique_ptr clone () const override 452 | { 453 | return std::unique_ptr( 454 | new FileOutput(*this)); 455 | } 456 | 457 | void sendLine (std::string const & line) override 458 | { 459 | if (not mFile.is_open()) 460 | { 461 | mFile.open("application.log", std::ios::app); 462 | } 463 | mFile << line << std::endl; 464 | mFile.flush(); 465 | } 466 | 467 | protected: 468 | std::filesystem::path mOutputDir; 469 | std::string mFileNamePattern; 470 | std::size_t mMaxSize; 471 | unsigned int mRolloverCount; 472 | std::fstream mFile; 473 | }; 474 | 475 | class StreamOutput : public Output 476 | { 477 | public: 478 | StreamOutput (std::ostream & stream) 479 | : mStream(stream) 480 | { } 481 | 482 | StreamOutput (StreamOutput const & rhs) 483 | : mStream(rhs.mStream) 484 | { } 485 | 486 | std::unique_ptr clone () const override 487 | { 488 | return std::unique_ptr( 489 | new StreamOutput(*this)); 490 | } 491 | 492 | void sendLine (std::string const & line) override 493 | { 494 | mStream << line << std::endl; 495 | } 496 | 497 | protected: 498 | std::ostream & mStream; 499 | }; 500 | 501 | class LogStream : public std::stringstream 502 | { 503 | public: 504 | LogStream () 505 | : mProceed(true) 506 | { } 507 | 508 | LogStream (LogStream const & other) = delete; 509 | 510 | LogStream (LogStream && other) 511 | : std::stringstream(std::move(other)), 512 | mProceed(other.mProceed) 513 | { } 514 | 515 | ~LogStream () 516 | { 517 | if (not mProceed) 518 | { 519 | return; 520 | } 521 | 522 | auto & outputs = getOutputs(); 523 | for (auto const & output: outputs) 524 | { 525 | output->sendLine(this->str()); 526 | } 527 | } 528 | 529 | LogStream & operator = (LogStream const & rhs) = delete; 530 | LogStream & operator = (LogStream && rhs) = delete; 531 | 532 | void ignore () 533 | { 534 | mProceed = false; 535 | } 536 | 537 | private: 538 | bool mProceed; 539 | }; 540 | 541 | inline LogStream log (std::vector tags = {}) 542 | { 543 | auto const now = std::chrono::system_clock::now(); 544 | std::time_t const tmNow = std::chrono::system_clock::to_time_t(now); 545 | auto const ms = duration_cast( 546 | now.time_since_epoch()) % 1000; 547 | 548 | LogStream ls; 549 | ls << std::put_time(std::gmtime(&tmNow), "%Y-%m-%dT%H:%M:%S.") 550 | << std::setw(3) << std::setfill('0') << std::to_string(ms.count()); 551 | 552 | std::map activeTags; 553 | for (auto const & defaultTag: getDefaultTags()) 554 | { 555 | activeTags[defaultTag.first] = defaultTag.second.get(); 556 | } 557 | for (auto const & tag: tags) 558 | { 559 | activeTags[tag->key()] = tag; 560 | } 561 | for (auto const & activeEntry: activeTags) 562 | { 563 | ls << " " << activeEntry.second->text(); 564 | } 565 | ls << " "; 566 | 567 | bool proceed = true; 568 | for (auto const & clause: getFilterClauses()) 569 | { 570 | proceed = false; 571 | bool allLiteralsMatch = true; 572 | for (auto const & normal: clause.second.normalLiterals) 573 | { 574 | // We need to make sure that the tag is 575 | // present and with the correct value. 576 | if (not activeTags.contains(normal->key())) 577 | { 578 | allLiteralsMatch = false; 579 | break; 580 | } 581 | if (not activeTags[normal->key()]->match(*normal)) 582 | { 583 | allLiteralsMatch = false; 584 | break; 585 | } 586 | } 587 | if (not allLiteralsMatch) 588 | { 589 | continue; 590 | } 591 | for (auto const & inverted: clause.second.invertedLiterals) 592 | { 593 | // We need to make sure that the tag is either 594 | // not present or has a mismatched value. 595 | if (activeTags.contains(inverted->key())) 596 | { 597 | if (activeTags[inverted->key()]->match(*inverted)) 598 | { 599 | allLiteralsMatch = false; 600 | } 601 | break; 602 | } 603 | } 604 | if (allLiteralsMatch) 605 | { 606 | proceed = true; 607 | break; 608 | } 609 | } 610 | 611 | if (not proceed) 612 | { 613 | ls.ignore(); 614 | } 615 | return ls; 616 | } 617 | 618 | inline auto log (Tag const & tag1) 619 | { 620 | return log({&tag1}); 621 | } 622 | 623 | inline auto log (Tag const & tag1, 624 | Tag const & tag2) 625 | { 626 | return log({&tag1, &tag2}); 627 | } 628 | 629 | inline auto log (Tag const & tag1, 630 | Tag const & tag2, 631 | Tag const & tag3) 632 | { 633 | return log({&tag1, &tag2, &tag3}); 634 | } 635 | 636 | } // namespace MereMemo 637 | 638 | #endif // MEREMEMO_LOG_H 639 | -------------------------------------------------------------------------------- /Chapter11/MereMemo/tests/Construction.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "Util.h" 4 | 5 | #include 6 | 7 | TEST("Simple message can be logged") 8 | { 9 | std::string message = "simple "; 10 | message += Util::randomString(); 11 | MereMemo::log() << message << " with more text."; 12 | 13 | bool result = Util::isTextInFile(message, "application.log"); 14 | CONFIRM_TRUE(result); 15 | } 16 | 17 | TEST("Complicated message can be logged") 18 | { 19 | std::string message = "complicated "; 20 | message += Util::randomString(); 21 | MereMemo::log() << message 22 | << " double=" << 3.14 23 | << " quoted=" << std::quoted("in quotes"); 24 | 25 | bool result = Util::isTextInFile(message, "application.log"); 26 | CONFIRM_TRUE(result); 27 | } 28 | -------------------------------------------------------------------------------- /Chapter11/MereMemo/tests/LogTags.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_TESTS_LOGTAGS_H 2 | #define MEREMEMO_TESTS_LOGTAGS_H 3 | 4 | #include "../Log.h" 5 | 6 | inline MereMemo::LogLevel error("error"); 7 | inline MereMemo::LogLevel info("info"); 8 | inline MereMemo::LogLevel debug("debug"); 9 | 10 | class Color : public MereMemo::StringTagType 11 | { 12 | public: 13 | static constexpr char key[] = "color"; 14 | 15 | Color (std::string const & value, 16 | MereMemo::TagOperation operation = 17 | MereMemo::TagOperation::None) 18 | : StringTagType(value, operation) 19 | { } 20 | }; 21 | 22 | inline Color red("red"); 23 | inline Color green("green"); 24 | inline Color blue("blue"); 25 | 26 | class Size : public MereMemo::StringTagType 27 | { 28 | public: 29 | static constexpr char key[] = "size"; 30 | 31 | Size (std::string const & value, 32 | MereMemo::TagOperation operation = 33 | MereMemo::TagOperation::None) 34 | : StringTagType(value, operation) 35 | { } 36 | }; 37 | 38 | inline Size small("small"); 39 | inline Size medium("medium"); 40 | inline Size large("large"); 41 | 42 | class Count : public MereMemo::IntTagType 43 | { 44 | public: 45 | static constexpr char key[] = "count"; 46 | 47 | Count (int value, 48 | MereMemo::TagOperation operation = 49 | MereMemo::TagOperation::None) 50 | : IntTagType(value, operation) 51 | { } 52 | }; 53 | 54 | class Identity : public MereMemo::LongLongTagType 55 | { 56 | public: 57 | static constexpr char key[] = "id"; 58 | 59 | Identity (long long value, 60 | MereMemo::TagOperation operation = 61 | MereMemo::TagOperation::None) 62 | : LongLongTagType(value, operation) 63 | { } 64 | }; 65 | 66 | class Scale : public MereMemo::DoubleTagType 67 | { 68 | public: 69 | static constexpr char key[] = "scale"; 70 | 71 | Scale (double value, 72 | MereMemo::TagOperation operation = 73 | MereMemo::TagOperation::None) 74 | : DoubleTagType(value, operation) 75 | { } 76 | }; 77 | 78 | class CacheHit : public MereMemo::BoolTagType 79 | { 80 | public: 81 | static constexpr char key[] = "cache_hit"; 82 | 83 | CacheHit (bool value, 84 | MereMemo::TagOperation operation = 85 | MereMemo::TagOperation::None) 86 | : BoolTagType(value, operation) 87 | { } 88 | }; 89 | 90 | inline CacheHit cacheHit(true); 91 | inline CacheHit cacheMiss(false); 92 | 93 | #endif // MEREMEMO_TESTS_LOGTAGS_H 94 | -------------------------------------------------------------------------------- /Chapter11/MereMemo/tests/Tags.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "LogTags.h" 4 | #include "Util.h" 5 | 6 | #include 7 | 8 | class TempFilterClause 9 | { 10 | public: 11 | void setup () 12 | { 13 | mId = MereMemo::createFilterClause(); 14 | } 15 | 16 | void teardown () 17 | { 18 | MereMemo::clearFilterClause(mId); 19 | } 20 | 21 | int id () const 22 | { 23 | return mId; 24 | } 25 | 26 | private: 27 | int mId; 28 | }; 29 | 30 | TEST("Message can be tagged in log") 31 | { 32 | std::string message = "simple tag "; 33 | message += Util::randomString(); 34 | MereMemo::log(error) << message; 35 | 36 | // Confirm that the error tag value exists and that the 37 | // default info tag value does not. 38 | std::string logLevelTag = " log_level=\"error\" "; 39 | std::string defaultLogLevelTag = " log_level=\"info\" "; 40 | bool result = Util::isTextInFile(message, "application.log", 41 | {logLevelTag}, {defaultLogLevelTag}); 42 | CONFIRM_TRUE(result); 43 | } 44 | 45 | TEST("log needs no namespace when used with LogLevel") 46 | { 47 | log(error) << "no namespace"; 48 | } 49 | 50 | TEST("Default tags set in main appear in log") 51 | { 52 | std::string message = "default tag "; 53 | message += Util::randomString(); 54 | MereMemo::log() << message; 55 | 56 | std::string logLevelTag = " log_level=\"info\" "; 57 | std::string colorTag = " color=\"green\" "; 58 | bool result = Util::isTextInFile(message, "application.log", 59 | {logLevelTag, colorTag}); 60 | CONFIRM_TRUE(result); 61 | } 62 | 63 | TEST("Multiple tags can be used in log") 64 | { 65 | std::string message = "multi tags "; 66 | message += Util::randomString(); 67 | MereMemo::log(debug, red, large) << message; 68 | 69 | std::string logLevelTag = " log_level=\"debug\" "; 70 | std::string colorTag = " color=\"red\" "; 71 | std::string sizeTag = " size=\"large\" "; 72 | bool result = Util::isTextInFile(message, "application.log", 73 | {logLevelTag, colorTag, sizeTag}); 74 | CONFIRM_TRUE(result); 75 | } 76 | 77 | TEST("Tags can be streamed to log") 78 | { 79 | std::string messageBase = " 1 type "; 80 | std::string message = messageBase + Util::randomString(); 81 | MereMemo::log(info) << Count(1) << message; 82 | 83 | std::string countTag = " count=1 "; 84 | bool result = Util::isTextInFile(message, "application.log", 85 | {countTag}); 86 | CONFIRM_TRUE(result); 87 | 88 | messageBase = " 2 type "; 89 | message = messageBase + Util::randomString(); 90 | MereMemo::log(info) << Identity(123456789012345) << message; 91 | 92 | std::string idTag = " id=123456789012345 "; 93 | result = Util::isTextInFile(message, "application.log", 94 | {idTag}); 95 | CONFIRM_TRUE(result); 96 | 97 | messageBase = " 3 type "; 98 | message = messageBase + Util::randomString(); 99 | MereMemo::log(info) << Scale(1.5) << message; 100 | 101 | std::string scaleTag = " scale=1.500000 "; 102 | result = Util::isTextInFile(message, "application.log", 103 | {scaleTag}); 104 | CONFIRM_TRUE(result); 105 | 106 | messageBase = " 4 type "; 107 | message = messageBase + Util::randomString(); 108 | MereMemo::log(info) << cacheMiss << message; 109 | 110 | std::string cacheTag = " cache_hit=false "; 111 | result = Util::isTextInFile(message, "application.log", 112 | {cacheTag}); 113 | CONFIRM_TRUE(result); 114 | } 115 | 116 | TEST("Tags can be used to filter messages") 117 | { 118 | MereTDD::SetupAndTeardown filter; 119 | MereMemo::addFilterLiteral(filter.id(), error); 120 | 121 | std::string message = "filter "; 122 | message += Util::randomString(); 123 | MereMemo::log(info) << message; 124 | 125 | bool result = Util::isTextInFile(message, "application.log"); 126 | CONFIRM_FALSE(result); 127 | 128 | MereMemo::clearFilterClause(filter.id()); 129 | 130 | MereMemo::log(info) << message; 131 | 132 | result = Util::isTextInFile(message, "application.log"); 133 | CONFIRM_TRUE(result); 134 | } 135 | 136 | TEST("Overridden default tag not used to filter messages") 137 | { 138 | MereTDD::SetupAndTeardown filter; 139 | MereMemo::addFilterLiteral(filter.id(), info); 140 | 141 | std::string message = "override default "; 142 | message += Util::randomString(); 143 | MereMemo::log(debug) << message; 144 | 145 | bool result = Util::isTextInFile(message, "application.log"); 146 | CONFIRM_FALSE(result); 147 | } 148 | 149 | TEST("Inverted tag can be used to filter messages") 150 | { 151 | MereTDD::SetupAndTeardown filter; 152 | MereMemo::addFilterLiteral(filter.id(), green, false); 153 | 154 | std::string message = "inverted "; 155 | message += Util::randomString(); 156 | MereMemo::log(info) << message; 157 | 158 | bool result = Util::isTextInFile(message, "application.log"); 159 | CONFIRM_FALSE(result); 160 | } 161 | 162 | TEST("Tag values can be used to filter messages") 163 | { 164 | MereTDD::SetupAndTeardown filter; 165 | MereMemo::addFilterLiteral(filter.id(), 166 | Count(100, MereMemo::TagOperation::GreaterThan)); 167 | 168 | std::string message = "values "; 169 | message += Util::randomString(); 170 | MereMemo::log(Count(1)) << message; 171 | 172 | bool result = Util::isTextInFile(message, "application.log"); 173 | CONFIRM_FALSE(result); 174 | 175 | MereMemo::log() << Count(101) << message; 176 | 177 | result = Util::isTextInFile(message, "application.log"); 178 | CONFIRM_FALSE(result); 179 | 180 | MereMemo::log(Count(101)) << message; 181 | 182 | result = Util::isTextInFile(message, "application.log"); 183 | CONFIRM_TRUE(result); 184 | } 185 | -------------------------------------------------------------------------------- /Chapter11/MereMemo/tests/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | std::string Util::randomString () 8 | { 9 | static bool firstCall = true; 10 | static std::mt19937 rng; 11 | if (firstCall) 12 | { 13 | // We only need to set the seed once. 14 | firstCall = false; 15 | 16 | unsigned int seed = static_cast( 17 | std::chrono::system_clock::now(). 18 | time_since_epoch().count()); 19 | rng.seed(seed); 20 | } 21 | std::uniform_int_distribution dist(1, 10000); 22 | 23 | return std::to_string(dist(rng)); 24 | } 25 | 26 | bool Util::isTextInFile ( 27 | std::string_view text, 28 | std::string_view fileName, 29 | std::vector const & wantedTags, 30 | std::vector const & unwantedTags) 31 | { 32 | std::ifstream logfile(fileName.data()); 33 | std::string line; 34 | while (getline(logfile, line)) 35 | { 36 | if (line.find(text) != std::string::npos) 37 | { 38 | for (auto const & tag: wantedTags) 39 | { 40 | if (line.find(tag) == std::string::npos) 41 | { 42 | return false; 43 | } 44 | } 45 | for (auto const & tag: unwantedTags) 46 | { 47 | if (line.find(tag) != std::string::npos) 48 | { 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | -------------------------------------------------------------------------------- /Chapter11/MereMemo/tests/Util.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_TESTS_UTIL_H 2 | #define MEREMEMO_TESTS_UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct Util 9 | { 10 | static std::string randomString (); 11 | 12 | static bool isTextInFile ( 13 | std::string_view text, 14 | std::string_view fileName, 15 | std::vector const & wantedTags = {}, 16 | std::vector const & unwantedTags = {}); 17 | }; 18 | 19 | #endif // MEREMEMO_TESTS_UTIL_H 20 | -------------------------------------------------------------------------------- /Chapter11/MereMemo/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "LogTags.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | int main () 10 | { 11 | MereMemo::FileOutput appFile("logs"); 12 | //appFile.namePattern() = "application-{}.log"; 13 | //appFile.maxSize() = 10'000'000; 14 | //appFile.rolloverCount() = 5; 15 | MereMemo::addLogOutput(appFile); 16 | 17 | MereMemo::StreamOutput consoleStream(std::cout); 18 | MereMemo::addLogOutput(consoleStream); 19 | 20 | MereMemo::addDefaultTag(info); 21 | MereMemo::addDefaultTag(green); 22 | 23 | return MereTDD::runTests(std::cout); 24 | } 25 | -------------------------------------------------------------------------------- /Chapter12/MereTDD/tests/Confirm.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | bool isNegative (int value) 4 | { 5 | return value < 0; 6 | } 7 | 8 | int multiplyBy2 (int value) 9 | { 10 | return value * 2; 11 | } 12 | 13 | long multiplyBy2 (long value) 14 | { 15 | return value * 2L; 16 | } 17 | 18 | long long multiplyBy2 (long long value) 19 | { 20 | return value * 2LL; 21 | } 22 | 23 | TEST("Test will pass without any confirms") 24 | { 25 | } 26 | 27 | TEST("Test bool confirms") 28 | { 29 | bool result = isNegative(0); 30 | CONFIRM_FALSE(result); 31 | 32 | result = isNegative(-1); 33 | CONFIRM_TRUE(result); 34 | } 35 | 36 | TEST("Test int confirms") 37 | { 38 | int result = multiplyBy2(0); 39 | CONFIRM(0, result); 40 | 41 | result = multiplyBy2(1); 42 | CONFIRM(2, result); 43 | 44 | result = multiplyBy2(-1); 45 | CONFIRM(-2, result); 46 | } 47 | 48 | TEST("Test long confirms") 49 | { 50 | long result = multiplyBy2(0L); 51 | CONFIRM(0L, result); 52 | 53 | result = multiplyBy2(1L); 54 | CONFIRM(2L, result); 55 | 56 | result = multiplyBy2(-1L); 57 | CONFIRM(-2L, result); 58 | } 59 | 60 | TEST("Test long long confirms") 61 | { 62 | long long result = multiplyBy2(0LL); 63 | CONFIRM(0LL, result); 64 | 65 | result = multiplyBy2(10'000'000'000LL); 66 | CONFIRM(20'000'000'000LL, result); 67 | 68 | result = multiplyBy2(-10'000'000'000LL); 69 | CONFIRM(-20'000'000'000LL, result); 70 | } 71 | 72 | TEST("Test string confirms") 73 | { 74 | std::string result = "abc"; 75 | std::string expected = "abc"; 76 | CONFIRM(expected, result); 77 | } 78 | 79 | TEST("Test string and string literal confirms") 80 | { 81 | std::string result = "abc"; 82 | CONFIRM("abc", result); 83 | } 84 | 85 | TEST("Test float confirms") 86 | { 87 | float f1 = 0.1f; 88 | float f2 = 0.2f; 89 | float sum = f1 + f2; 90 | float expected = 0.3f; 91 | CONFIRM(expected, sum); 92 | } 93 | 94 | TEST("Test double confirms") 95 | { 96 | double d1 = 0.1; 97 | double d2 = 0.2; 98 | double sum = d1 + d2; 99 | double expected = 0.3; 100 | CONFIRM(expected, sum); 101 | } 102 | 103 | TEST("Test long double confirms") 104 | { 105 | long double ld1 = 0.1; 106 | long double ld2 = 0.2; 107 | long double sum = ld1 + ld2; 108 | long double expected = 0.3; 109 | CONFIRM(expected, sum); 110 | } 111 | 112 | TEST("Test bool confirm failure") 113 | { 114 | std::string reason = " Expected: true"; 115 | setExpectedFailureReason(reason); 116 | 117 | bool result = isNegative(0); 118 | CONFIRM_TRUE(result); 119 | } 120 | 121 | TEST("Test int confirm failure") 122 | { 123 | std::string reason = " Expected: 0\n"; 124 | reason += " Actual : 2"; 125 | setExpectedFailureReason(reason); 126 | 127 | int result = multiplyBy2(1); 128 | CONFIRM(0, result); 129 | } 130 | 131 | TEST("Test long confirm failure") 132 | { 133 | std::string reason = " Expected: 0\n"; 134 | reason += " Actual : 2"; 135 | setExpectedFailureReason(reason); 136 | 137 | long result = multiplyBy2(1L); 138 | CONFIRM(0L, result); 139 | } 140 | 141 | TEST("Test long long confirm failure") 142 | { 143 | std::string reason = " Expected: 10000000000\n"; 144 | reason += " Actual : 20000000000"; 145 | setExpectedFailureReason(reason); 146 | 147 | long long result = multiplyBy2(10'000'000'000LL); 148 | CONFIRM(10'000'000'000LL, result); 149 | } 150 | 151 | TEST("Test string confirm failure") 152 | { 153 | std::string reason = " Expected: def\n"; 154 | reason += " Actual : abc"; 155 | setExpectedFailureReason(reason); 156 | 157 | std::string result = "abc"; 158 | std::string expected = "def"; 159 | CONFIRM(expected, result); 160 | } 161 | -------------------------------------------------------------------------------- /Chapter12/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter12/MereTDD/tests/Hamcrest.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | using namespace MereTDD; 4 | 5 | TEST("Test can use hamcrest style confirm") 6 | { 7 | int ten = 10; 8 | CONFIRM_THAT(ten, Equals(10)); 9 | } 10 | 11 | TEST("Test hamcrest style confirm failure") 12 | { 13 | std::string reason = " Expected: 9\n"; 14 | reason += " Actual : 10"; 15 | setExpectedFailureReason(reason); 16 | 17 | int ten = 10; 18 | CONFIRM_THAT(ten, Equals(9)); 19 | } 20 | 21 | TEST("Test other hamcrest style integer confirms") 22 | { 23 | char c1 = 'A'; 24 | char c2 = 'A'; 25 | CONFIRM_THAT(c1, Equals(c2)); 26 | CONFIRM_THAT(c1, Equals('A')); 27 | 28 | short s1 = 10; 29 | short s2 = 10; 30 | CONFIRM_THAT(s1, Equals(s2)); 31 | CONFIRM_THAT(s1, Equals(10)); 32 | 33 | unsigned int ui1 = 3'000'000'000; 34 | unsigned int ui2 = 3'000'000'000; 35 | CONFIRM_THAT(ui1, Equals(ui2)); 36 | CONFIRM_THAT(ui1, Equals(3'000'000'000)); 37 | 38 | long long ll1 = 5'000'000'000'000LL; 39 | long long ll2 = 5'000'000'000'000LL; 40 | CONFIRM_THAT(ll1, Equals(ll2)); 41 | CONFIRM_THAT(ll1, Equals(5'000'000'000'000LL)); 42 | } 43 | 44 | TEST("Test hamcrest style bool confirms") 45 | { 46 | bool b1 = true; 47 | bool b2 = true; 48 | CONFIRM_THAT(b1, Equals(b2)); 49 | 50 | // This works but probably won't be used much. 51 | CONFIRM_THAT(b1, Equals(true)); 52 | 53 | // When checking a bool variable for a known value, 54 | // the classic style is probably better. 55 | CONFIRM_TRUE(b1); 56 | } 57 | 58 | TEST("Test hamcrest style string confirms") 59 | { 60 | std::string s1 = "abc"; 61 | std::string s2 = "abc"; 62 | CONFIRM_THAT(s1, Equals(s2)); // string vs. string 63 | CONFIRM_THAT(s1, Equals("abc")); // string vs. literal 64 | CONFIRM_THAT("abc", Equals(s1)); // literal vs. string 65 | 66 | // Probably not needed, but this works too. 67 | CONFIRM_THAT("abc", Equals("abc")); // literal vs. literal 68 | } 69 | 70 | TEST("Test hamcrest style string pointer confirms") 71 | { 72 | char const * sp1 = "abc"; 73 | std::string s1 = "abc"; 74 | char const * sp2 = s1.c_str(); // avoid sp1 and sp2 being same 75 | CONFIRM_THAT(sp1, Equals(sp2)); // pointer vs. pointer 76 | CONFIRM_THAT(sp2, Equals("abc")); // pointer vs. literal 77 | CONFIRM_THAT("abc", Equals(sp2)); // literal vs. pointer 78 | CONFIRM_THAT(sp1, Equals(s1)); // pointer vs. string 79 | CONFIRM_THAT(s1, Equals(sp1)); // string vs. pointer 80 | } 81 | -------------------------------------------------------------------------------- /Chapter12/MereTDD/tests/Setup.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | #include 5 | 6 | std::string createTestTable () 7 | { 8 | // If this was real code, it might open a 9 | // connection to a database, create a temp 10 | // table with a random name, and return the 11 | // table name. 12 | return "test_data_01"; 13 | } 14 | 15 | void dropTestTable (std::string_view /*name*/) 16 | { 17 | // Real code would use the name to drop 18 | // the table. 19 | } 20 | 21 | int createTestEntry () 22 | { 23 | // If this was real code, it might open a 24 | // connection to a database, insert a row 25 | // of data, and return the row identifier. 26 | return 100; 27 | } 28 | 29 | void updateTestEntryName (int /*id*/, std::string_view name) 30 | { 31 | if (name.empty()) 32 | { 33 | throw 1; 34 | } 35 | // Real code would proceed to update the 36 | // data with the new name. 37 | } 38 | 39 | void deleteTestEntry (int /*id*/) 40 | { 41 | // Real code would use the id to delete 42 | // the temporary row of data. 43 | } 44 | 45 | class TempEntry 46 | { 47 | public: 48 | void setup () 49 | { 50 | mId = createTestEntry(); 51 | } 52 | 53 | void teardown () 54 | { 55 | deleteTestEntry(mId); 56 | } 57 | 58 | int id () 59 | { 60 | return mId; 61 | } 62 | 63 | private: 64 | int mId; 65 | }; 66 | 67 | class TempTable 68 | { 69 | public: 70 | void setup () 71 | { 72 | mName = createTestTable(); 73 | } 74 | 75 | void teardown () 76 | { 77 | dropTestTable(mName); 78 | } 79 | 80 | std::string tableName () 81 | { 82 | return mName; 83 | } 84 | 85 | private: 86 | std::string mName; 87 | }; 88 | 89 | TEST_EX("Test will run setup and teardown code", int) 90 | { 91 | MereTDD::SetupAndTeardown entry; 92 | 93 | // If this was a project test, it might be called 94 | // "Updating empty name throws". And the type thrown 95 | // would not be an int. 96 | updateTestEntryName(entry.id(), ""); 97 | } 98 | 99 | TEST("Test will run multiple setup and teardown code") 100 | { 101 | MereTDD::SetupAndTeardown entry1; 102 | MereTDD::SetupAndTeardown entry2; 103 | 104 | // If this was a project test, it might need 105 | // more than one temporary entry. The TempEntry 106 | // policy could either create multiple data records 107 | // or it is easier to just have multiple instances 108 | // that each create a single data entry. 109 | updateTestEntryName(entry1.id(), "abc"); 110 | updateTestEntryName(entry2.id(), "def"); 111 | } 112 | 113 | MereTDD::TestSuiteSetupAndTeardown 114 | gTable1("Test suite setup/teardown 1", "Suite 1"); 115 | 116 | MereTDD::TestSuiteSetupAndTeardown 117 | gTable2("Test suite setup/teardown 2", "Suite 1"); 118 | 119 | TEST_SUITE("Test part 1 of suite", "Suite 1") 120 | { 121 | // If this was a project test, it could use 122 | // the table names from gTable1 and gTable2. 123 | CONFIRM("test_data_01", gTable1.tableName()); 124 | CONFIRM("test_data_01", gTable2.tableName()); 125 | } 126 | 127 | TEST_SUITE_EX("Test part 2 of suite", "Suite 1", int) 128 | { 129 | // If this was a project test, it could use 130 | // the table names from gTable1 and gTable2. 131 | throw 1; 132 | } 133 | -------------------------------------------------------------------------------- /Chapter12/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter13/MereTDD/tests/Confirm.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | bool isNegative (int value) 4 | { 5 | return value < 0; 6 | } 7 | 8 | int multiplyBy2 (int value) 9 | { 10 | return value * 2; 11 | } 12 | 13 | long multiplyBy2 (long value) 14 | { 15 | return value * 2L; 16 | } 17 | 18 | long long multiplyBy2 (long long value) 19 | { 20 | return value * 2LL; 21 | } 22 | 23 | TEST("Test will pass without any confirms") 24 | { 25 | } 26 | 27 | TEST("Test bool confirms") 28 | { 29 | bool result = isNegative(0); 30 | CONFIRM_FALSE(result); 31 | 32 | result = isNegative(-1); 33 | CONFIRM_TRUE(result); 34 | } 35 | 36 | TEST("Test int confirms") 37 | { 38 | int result = multiplyBy2(0); 39 | CONFIRM(0, result); 40 | 41 | result = multiplyBy2(1); 42 | CONFIRM(2, result); 43 | 44 | result = multiplyBy2(-1); 45 | CONFIRM(-2, result); 46 | } 47 | 48 | TEST("Test long confirms") 49 | { 50 | long result = multiplyBy2(0L); 51 | CONFIRM(0L, result); 52 | 53 | result = multiplyBy2(1L); 54 | CONFIRM(2L, result); 55 | 56 | result = multiplyBy2(-1L); 57 | CONFIRM(-2L, result); 58 | } 59 | 60 | TEST("Test long long confirms") 61 | { 62 | long long result = multiplyBy2(0LL); 63 | CONFIRM(0LL, result); 64 | 65 | result = multiplyBy2(10'000'000'000LL); 66 | CONFIRM(20'000'000'000LL, result); 67 | 68 | result = multiplyBy2(-10'000'000'000LL); 69 | CONFIRM(-20'000'000'000LL, result); 70 | } 71 | 72 | TEST("Test string confirms") 73 | { 74 | std::string result = "abc"; 75 | std::string expected = "abc"; 76 | CONFIRM(expected, result); 77 | } 78 | 79 | TEST("Test string and string literal confirms") 80 | { 81 | std::string result = "abc"; 82 | CONFIRM("abc", result); 83 | } 84 | 85 | TEST("Test float confirms") 86 | { 87 | float f1 = 0.1f; 88 | float f2 = 0.2f; 89 | float sum = f1 + f2; 90 | float expected = 0.3f; 91 | CONFIRM(expected, sum); 92 | } 93 | 94 | TEST("Test double confirms") 95 | { 96 | double d1 = 0.1; 97 | double d2 = 0.2; 98 | double sum = d1 + d2; 99 | double expected = 0.3; 100 | CONFIRM(expected, sum); 101 | } 102 | 103 | TEST("Test long double confirms") 104 | { 105 | long double ld1 = 0.1; 106 | long double ld2 = 0.2; 107 | long double sum = ld1 + ld2; 108 | long double expected = 0.3; 109 | CONFIRM(expected, sum); 110 | } 111 | 112 | TEST("Test bool confirm failure") 113 | { 114 | std::string reason = " Expected: true"; 115 | setExpectedFailureReason(reason); 116 | 117 | bool result = isNegative(0); 118 | CONFIRM_TRUE(result); 119 | } 120 | 121 | TEST("Test int confirm failure") 122 | { 123 | std::string reason = " Expected: 0\n"; 124 | reason += " Actual : 2"; 125 | setExpectedFailureReason(reason); 126 | 127 | int result = multiplyBy2(1); 128 | CONFIRM(0, result); 129 | } 130 | 131 | TEST("Test long confirm failure") 132 | { 133 | std::string reason = " Expected: 0\n"; 134 | reason += " Actual : 2"; 135 | setExpectedFailureReason(reason); 136 | 137 | long result = multiplyBy2(1L); 138 | CONFIRM(0L, result); 139 | } 140 | 141 | TEST("Test long long confirm failure") 142 | { 143 | std::string reason = " Expected: 10000000000\n"; 144 | reason += " Actual : 20000000000"; 145 | setExpectedFailureReason(reason); 146 | 147 | long long result = multiplyBy2(10'000'000'000LL); 148 | CONFIRM(10'000'000'000LL, result); 149 | } 150 | 151 | TEST("Test string confirm failure") 152 | { 153 | std::string reason = " Expected: def\n"; 154 | reason += " Actual : abc"; 155 | setExpectedFailureReason(reason); 156 | 157 | std::string result = "abc"; 158 | std::string expected = "def"; 159 | CONFIRM(expected, result); 160 | } 161 | -------------------------------------------------------------------------------- /Chapter13/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter13/MereTDD/tests/Hamcrest.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | using namespace MereTDD; 4 | 5 | template 6 | T calculateFraction (T input) 7 | { 8 | T denominator {10}; 9 | return input / denominator; 10 | } 11 | 12 | template 13 | T accumulateError (T input) 14 | { 15 | // First add many small amounts. 16 | T partialAmount {0.1}; 17 | for (int i = 0; i < 10; ++i) 18 | { 19 | input += partialAmount; 20 | } 21 | // Then subtract to get back to the original. 22 | T wholeAmount {1}; 23 | input -= wholeAmount; 24 | return input; 25 | } 26 | 27 | template 28 | int performComparisons (int totalCount) 29 | { 30 | int passCount {0}; 31 | for (int i = 0; i < totalCount; ++i) 32 | { 33 | T expected = static_cast(i); 34 | expected = calculateFraction(expected); 35 | T actual = accumulateError(expected); 36 | if (compareEq(actual, expected)) 37 | { 38 | ++passCount; 39 | } 40 | } 41 | return passCount; 42 | } 43 | 44 | TEST("Test can use hamcrest style confirm") 45 | { 46 | int ten = 10; 47 | CONFIRM_THAT(ten, Equals(10)); 48 | } 49 | 50 | TEST("Test hamcrest style confirm failure") 51 | { 52 | std::string reason = " Expected: 9\n"; 53 | reason += " Actual : 10"; 54 | setExpectedFailureReason(reason); 55 | 56 | int ten = 10; 57 | CONFIRM_THAT(ten, Equals(9)); 58 | } 59 | 60 | TEST("Test other hamcrest style integer confirms") 61 | { 62 | char c1 = 'A'; 63 | char c2 = 'A'; 64 | CONFIRM_THAT(c1, Equals(c2)); 65 | CONFIRM_THAT(c1, Equals('A')); 66 | 67 | short s1 = 10; 68 | short s2 = 10; 69 | CONFIRM_THAT(s1, Equals(s2)); 70 | CONFIRM_THAT(s1, Equals(10)); 71 | 72 | unsigned int ui1 = 3'000'000'000; 73 | unsigned int ui2 = 3'000'000'000; 74 | CONFIRM_THAT(ui1, Equals(ui2)); 75 | CONFIRM_THAT(ui1, Equals(3'000'000'000)); 76 | 77 | long long ll1 = 5'000'000'000'000LL; 78 | long long ll2 = 5'000'000'000'000LL; 79 | CONFIRM_THAT(ll1, Equals(ll2)); 80 | CONFIRM_THAT(ll1, Equals(5'000'000'000'000LL)); 81 | } 82 | 83 | TEST("Test hamcrest style bool confirms") 84 | { 85 | bool b1 = true; 86 | bool b2 = true; 87 | CONFIRM_THAT(b1, Equals(b2)); 88 | 89 | // This works but probably won't be used much. 90 | CONFIRM_THAT(b1, Equals(true)); 91 | 92 | // When checking a bool variable for a known value, 93 | // the classic style is probably better. 94 | CONFIRM_TRUE(b1); 95 | } 96 | 97 | TEST("Test hamcrest style string confirms") 98 | { 99 | std::string s1 = "abc"; 100 | std::string s2 = "abc"; 101 | CONFIRM_THAT(s1, Equals(s2)); // string vs. string 102 | CONFIRM_THAT(s1, Equals("abc")); // string vs. literal 103 | CONFIRM_THAT("abc", Equals(s1)); // literal vs. string 104 | 105 | // Probably not needed, but this works too. 106 | CONFIRM_THAT("abc", Equals("abc")); // literal vs. literal 107 | 108 | std::string s3 = "def"; 109 | CONFIRM_THAT(s1, NotEquals(s3)); // string vs. string 110 | CONFIRM_THAT(s1, NotEquals("def")); // string vs. literal 111 | CONFIRM_THAT("def", NotEquals(s1)); // literal vs. string 112 | } 113 | 114 | TEST("Test hamcrest style string pointer confirms") 115 | { 116 | char const * sp1 = "abc"; 117 | std::string s1 = "abc"; 118 | char const * sp2 = s1.c_str(); // avoid sp1 and sp2 being same 119 | CONFIRM_THAT(sp1, Equals(sp2)); // pointer vs. pointer 120 | CONFIRM_THAT(sp2, Equals("abc")); // pointer vs. literal 121 | CONFIRM_THAT("abc", Equals(sp2)); // literal vs. pointer 122 | CONFIRM_THAT(sp1, Equals(s1)); // pointer vs. string 123 | CONFIRM_THAT(s1, Equals(sp1)); // string vs. pointer 124 | 125 | char const * sp3 = "def"; 126 | CONFIRM_THAT(sp1, NotEquals(sp3)); // pointer vs. pointer 127 | CONFIRM_THAT(sp1, NotEquals("def")); // pointer vs. literal 128 | CONFIRM_THAT("def", NotEquals(sp1)); // literal vs. pointer 129 | CONFIRM_THAT(sp3, NotEquals(s1)); // pointer vs. string 130 | CONFIRM_THAT(s1, NotEquals(sp3)); // string vs. pointer 131 | } 132 | 133 | TEST("Test many float comparisons") 134 | { 135 | int totalCount {1'000}; 136 | int passCount = performComparisons(totalCount); 137 | CONFIRM_THAT(passCount, Equals(totalCount)); 138 | } 139 | 140 | TEST("Test many double comparisons") 141 | { 142 | int totalCount {1'000}; 143 | int passCount = performComparisons(totalCount); 144 | CONFIRM_THAT(passCount, Equals(totalCount)); 145 | } 146 | 147 | TEST("Test many long double comparisons") 148 | { 149 | int totalCount {1'000}; 150 | int passCount = performComparisons(totalCount); 151 | CONFIRM_THAT(passCount, Equals(totalCount)); 152 | } 153 | 154 | TEST("Test small float values") 155 | { 156 | // Based on float epsilon = 1.1920928955078125e-07 157 | CONFIRM_THAT(0.000001f, NotEquals(0.000002f)); 158 | } 159 | 160 | TEST("Test large float values") 161 | { 162 | // Based on float epsilon = 1.1920928955078125e-07 163 | CONFIRM_THAT(9'999.0f, Equals(9'999.001f)); 164 | } 165 | 166 | TEST("Test small double values") 167 | { 168 | // Based on double epsilon = 2.2204460492503130808e-16 169 | CONFIRM_THAT(0.000000000000001, NotEquals(0.000000000000002)); 170 | } 171 | 172 | TEST("Test large double values") 173 | { 174 | // Based on double epsilon = 2.2204460492503130808e-16 175 | CONFIRM_THAT(1'500'000'000'000.0, Equals(1'500'000'000'000.0003)); 176 | } 177 | 178 | TEST("Test small long double values") 179 | { 180 | // Based on double epsilon = 2.2204460492503130808e-16 181 | CONFIRM_THAT(0.000000000000001L, NotEquals(0.000000000000002L)); 182 | } 183 | 184 | TEST("Test large long double values") 185 | { 186 | // Based on double epsilon = 2.2204460492503130808e-16 187 | CONFIRM_THAT(1'500'000'000'000.0L, Equals(1'500'000'000'000.0003L)); 188 | } 189 | 190 | TEST("Test even integral value") 191 | { 192 | CONFIRM_THAT(10, IsEven()); 193 | } 194 | 195 | TEST("Test even integral value confirm failure") 196 | { 197 | std::string reason = " Expected: is even\n"; 198 | reason += " Actual : 11"; 199 | setExpectedFailureReason(reason); 200 | 201 | CONFIRM_THAT(11, IsEven()); 202 | } 203 | -------------------------------------------------------------------------------- /Chapter13/MereTDD/tests/Setup.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | #include 5 | 6 | std::string createTestTable () 7 | { 8 | // If this was real code, it might open a 9 | // connection to a database, create a temp 10 | // table with a random name, and return the 11 | // table name. 12 | return "test_data_01"; 13 | } 14 | 15 | void dropTestTable (std::string_view /*name*/) 16 | { 17 | // Real code would use the name to drop 18 | // the table. 19 | } 20 | 21 | int createTestEntry () 22 | { 23 | // If this was real code, it might open a 24 | // connection to a database, insert a row 25 | // of data, and return the row identifier. 26 | return 100; 27 | } 28 | 29 | void updateTestEntryName (int /*id*/, std::string_view name) 30 | { 31 | if (name.empty()) 32 | { 33 | throw 1; 34 | } 35 | // Real code would proceed to update the 36 | // data with the new name. 37 | } 38 | 39 | void deleteTestEntry (int /*id*/) 40 | { 41 | // Real code would use the id to delete 42 | // the temporary row of data. 43 | } 44 | 45 | class TempEntry 46 | { 47 | public: 48 | void setup () 49 | { 50 | mId = createTestEntry(); 51 | } 52 | 53 | void teardown () 54 | { 55 | deleteTestEntry(mId); 56 | } 57 | 58 | int id () 59 | { 60 | return mId; 61 | } 62 | 63 | private: 64 | int mId; 65 | }; 66 | 67 | class TempTable 68 | { 69 | public: 70 | void setup () 71 | { 72 | mName = createTestTable(); 73 | } 74 | 75 | void teardown () 76 | { 77 | dropTestTable(mName); 78 | } 79 | 80 | std::string tableName () 81 | { 82 | return mName; 83 | } 84 | 85 | private: 86 | std::string mName; 87 | }; 88 | 89 | TEST_EX("Test will run setup and teardown code", int) 90 | { 91 | MereTDD::SetupAndTeardown entry; 92 | 93 | // If this was a project test, it might be called 94 | // "Updating empty name throws". And the type thrown 95 | // would not be an int. 96 | updateTestEntryName(entry.id(), ""); 97 | } 98 | 99 | TEST("Test will run multiple setup and teardown code") 100 | { 101 | MereTDD::SetupAndTeardown entry1; 102 | MereTDD::SetupAndTeardown entry2; 103 | 104 | // If this was a project test, it might need 105 | // more than one temporary entry. The TempEntry 106 | // policy could either create multiple data records 107 | // or it is easier to just have multiple instances 108 | // that each create a single data entry. 109 | updateTestEntryName(entry1.id(), "abc"); 110 | updateTestEntryName(entry2.id(), "def"); 111 | } 112 | 113 | MereTDD::TestSuiteSetupAndTeardown 114 | gTable1("Test suite setup/teardown 1", "Suite 1"); 115 | 116 | MereTDD::TestSuiteSetupAndTeardown 117 | gTable2("Test suite setup/teardown 2", "Suite 1"); 118 | 119 | TEST_SUITE("Test part 1 of suite", "Suite 1") 120 | { 121 | // If this was a project test, it could use 122 | // the table names from gTable1 and gTable2. 123 | CONFIRM("test_data_01", gTable1.tableName()); 124 | CONFIRM("test_data_01", gTable2.tableName()); 125 | } 126 | 127 | TEST_SUITE_EX("Test part 2 of suite", "Suite 1", int) 128 | { 129 | // If this was a project test, it could use 130 | // the table names from gTable1 and gTable2. 131 | throw 1; 132 | } 133 | -------------------------------------------------------------------------------- /Chapter13/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter14/MereMemo/Log.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_LOG_H 2 | #define MEREMEMO_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace MereMemo 19 | { 20 | 21 | class Tag 22 | { 23 | public: 24 | virtual ~Tag () = default; 25 | 26 | std::string key () const 27 | { 28 | return mKey; 29 | } 30 | 31 | std::string text () const 32 | { 33 | return mText; 34 | } 35 | 36 | virtual std::unique_ptr clone () const = 0; 37 | 38 | virtual bool match (Tag const & other) const = 0; 39 | 40 | protected: 41 | Tag (std::string const & key, std::string const & value) 42 | : mKey(key), mText(key + "=\"" + value + "\"") 43 | { } 44 | 45 | Tag (std::string const & key, int value) 46 | : mKey(key), mText(key + "=" + std::to_string(value)) 47 | { } 48 | 49 | Tag (std::string const & key, long long value) 50 | : mKey(key), mText(key + "=" + std::to_string(value)) 51 | { } 52 | 53 | Tag (std::string const & key, double value) 54 | : mKey(key), mText(key + "=" + std::to_string(value)) 55 | { } 56 | 57 | Tag (std::string const & key, bool value) 58 | : mKey(key), mText(key + "=" + (value?"true":"false")) 59 | { } 60 | 61 | private: 62 | std::string mKey; 63 | std::string const mText; 64 | }; 65 | 66 | inline std::string to_string (Tag const & tag) 67 | { 68 | return tag.text(); 69 | } 70 | 71 | inline std::ostream & operator << (std::ostream && stream, Tag const & tag) 72 | { 73 | stream << to_string(tag); 74 | return stream; 75 | } 76 | 77 | enum class TagOperation 78 | { 79 | None, 80 | Equal, 81 | LessThan, 82 | LessThanOrEqual, 83 | GreaterThan, 84 | GreaterThanOrEqual 85 | }; 86 | 87 | template 88 | class TagType : public Tag 89 | { 90 | public: 91 | std::unique_ptr clone () const override 92 | { 93 | return std::unique_ptr( 94 | new T(*static_cast(this))); 95 | } 96 | 97 | bool match (Tag const & other) const override 98 | { 99 | if (key() != other.key()) 100 | { 101 | return false; 102 | } 103 | TagType const & otherCast = static_cast(other); 104 | if (mOperation == TagOperation::None) 105 | { 106 | switch (otherCast.mOperation) 107 | { 108 | case TagOperation::None: 109 | return mValue == otherCast.mValue; 110 | 111 | default: 112 | return compareTagTypes(mValue, 113 | otherCast.mOperation, 114 | otherCast.mValue); 115 | } 116 | } 117 | switch (otherCast.mOperation) 118 | { 119 | case TagOperation::None: 120 | return compareTagTypes(otherCast.mValue, 121 | mOperation, 122 | mValue); 123 | 124 | default: 125 | return false; 126 | } 127 | } 128 | 129 | ValueT value () const 130 | { 131 | return mValue; 132 | } 133 | 134 | protected: 135 | TagType (ValueT const & value, 136 | TagOperation operation) 137 | : Tag(T::key, value), mValue(value), mOperation(operation) 138 | { } 139 | 140 | virtual bool compareTagTypes (ValueT const & value, 141 | TagOperation operation, 142 | ValueT const & criteria) const 143 | { 144 | return false; 145 | } 146 | 147 | ValueT mValue; 148 | TagOperation mOperation; 149 | }; 150 | 151 | template 152 | class StringTagType : public TagType 153 | { 154 | protected: 155 | StringTagType (std::string const & value, 156 | TagOperation operation) 157 | : TagType(value, operation) 158 | { } 159 | 160 | bool compareTagTypes (std::string const & value, 161 | TagOperation operation, 162 | std::string const & criteria) const override 163 | { 164 | int result = value.compare(criteria); 165 | switch (operation) 166 | { 167 | case TagOperation::Equal: 168 | return result == 0; 169 | 170 | case TagOperation::LessThan: 171 | return result == -1; 172 | 173 | case TagOperation::LessThanOrEqual: 174 | return result == 0 || result == -1; 175 | 176 | case TagOperation::GreaterThan: 177 | return result == 1; 178 | 179 | case TagOperation::GreaterThanOrEqual: 180 | return result == 0 || result == 1; 181 | 182 | default: 183 | return false; 184 | } 185 | } 186 | }; 187 | 188 | template 189 | class IntTagType : public TagType 190 | { 191 | protected: 192 | IntTagType (int const & value, 193 | TagOperation operation) 194 | : TagType(value, operation) 195 | { } 196 | 197 | bool compareTagTypes (int const & value, 198 | TagOperation operation, 199 | int const & criteria) const override 200 | { 201 | switch (operation) 202 | { 203 | case TagOperation::Equal: 204 | return value == criteria; 205 | 206 | case TagOperation::LessThan: 207 | return value < criteria; 208 | 209 | case TagOperation::LessThanOrEqual: 210 | return value <= criteria; 211 | 212 | case TagOperation::GreaterThan: 213 | return value > criteria; 214 | 215 | case TagOperation::GreaterThanOrEqual: 216 | return value >= criteria; 217 | 218 | default: 219 | return false; 220 | } 221 | } 222 | }; 223 | 224 | template 225 | class LongLongTagType : public TagType 226 | { 227 | protected: 228 | LongLongTagType (long long const & value, 229 | TagOperation operation) 230 | : TagType(value, operation) 231 | { } 232 | 233 | bool compareTagTypes (long long const & value, 234 | TagOperation operation, 235 | long long const & criteria) const override 236 | { 237 | switch (operation) 238 | { 239 | case TagOperation::Equal: 240 | return value == criteria; 241 | 242 | case TagOperation::LessThan: 243 | return value < criteria; 244 | 245 | case TagOperation::LessThanOrEqual: 246 | return value <= criteria; 247 | 248 | case TagOperation::GreaterThan: 249 | return value > criteria; 250 | 251 | case TagOperation::GreaterThanOrEqual: 252 | return value >= criteria; 253 | 254 | default: 255 | return false; 256 | } 257 | } 258 | }; 259 | 260 | template 261 | class DoubleTagType : public TagType 262 | { 263 | protected: 264 | DoubleTagType (double const & value, 265 | TagOperation operation) 266 | : TagType(value, operation) 267 | { } 268 | 269 | bool compareTagTypes (double const & value, 270 | TagOperation operation, 271 | double const & criteria) const override 272 | { 273 | switch (operation) 274 | { 275 | case TagOperation::Equal: 276 | return value == criteria; 277 | 278 | case TagOperation::LessThan: 279 | return value < criteria; 280 | 281 | case TagOperation::LessThanOrEqual: 282 | return value <= criteria; 283 | 284 | case TagOperation::GreaterThan: 285 | return value > criteria; 286 | 287 | case TagOperation::GreaterThanOrEqual: 288 | return value >= criteria; 289 | 290 | default: 291 | return false; 292 | } 293 | } 294 | }; 295 | 296 | template 297 | class BoolTagType : public TagType 298 | { 299 | protected: 300 | BoolTagType (bool const & value, 301 | TagOperation operation) 302 | : TagType(value, operation) 303 | { } 304 | 305 | bool compareTagTypes (bool const & value, 306 | TagOperation operation, 307 | bool const & criteria) const override 308 | { 309 | switch (operation) 310 | { 311 | case TagOperation::Equal: 312 | return value == criteria; 313 | 314 | default: 315 | return false; 316 | } 317 | } 318 | }; 319 | 320 | class LogLevel : public StringTagType 321 | { 322 | public: 323 | static constexpr char key[] = "log_level"; 324 | 325 | LogLevel (std::string const & value, 326 | TagOperation operation = TagOperation::None) 327 | : StringTagType(value, operation) 328 | { } 329 | }; 330 | 331 | inline std::map> & getDefaultTags () 332 | { 333 | static std::map> tags; 334 | return tags; 335 | } 336 | 337 | inline void addDefaultTag (Tag const & tag) 338 | { 339 | auto & tags = getDefaultTags(); 340 | tags[tag.key()] = tag.clone(); 341 | } 342 | 343 | struct FilterClause 344 | { 345 | std::vector> normalLiterals; 346 | std::vector> invertedLiterals; 347 | }; 348 | 349 | inline std::map & getFilterClauses () 350 | { 351 | static std::map clauses; 352 | return clauses; 353 | } 354 | 355 | inline int createFilterClause () 356 | { 357 | static int currentId = 0; 358 | ++currentId; 359 | auto & clauses = getFilterClauses(); 360 | clauses[currentId] = FilterClause(); 361 | 362 | return currentId; 363 | } 364 | 365 | inline void addFilterLiteral (int filterId, 366 | Tag const & tag, 367 | bool normal = true) 368 | { 369 | auto & clauses = getFilterClauses(); 370 | if (clauses.contains(filterId)) 371 | { 372 | if (normal) 373 | { 374 | clauses[filterId].normalLiterals.push_back( 375 | tag.clone()); 376 | } 377 | else 378 | { 379 | clauses[filterId].invertedLiterals.push_back( 380 | tag.clone()); 381 | } 382 | } 383 | } 384 | 385 | inline void clearFilterClause (int filterId) 386 | { 387 | auto & clauses = getFilterClauses(); 388 | clauses.erase(filterId); 389 | } 390 | 391 | class Output 392 | { 393 | public: 394 | virtual ~Output () = default; 395 | Output (Output const & other) = delete; 396 | Output (Output && other) = delete; 397 | 398 | virtual std::unique_ptr clone () const = 0; 399 | 400 | virtual void sendLine (std::string const & line) = 0; 401 | 402 | Output & operator = (Output const & rhs) = delete; 403 | Output & operator = (Output && rhs) = delete; 404 | 405 | protected: 406 | Output () = default; 407 | }; 408 | 409 | inline std::vector> & getOutputs () 410 | { 411 | static std::vector> outputs; 412 | return outputs; 413 | } 414 | 415 | inline void addLogOutput (Output const & output) 416 | { 417 | auto & outputs = getOutputs(); 418 | outputs.push_back(output.clone()); 419 | } 420 | 421 | class FileOutput : public Output 422 | { 423 | public: 424 | FileOutput (std::string_view dir) 425 | : mOutputDir(dir), 426 | mFileNamePattern("{}"), 427 | mMaxSize(0), 428 | mRolloverCount(0) 429 | { } 430 | 431 | FileOutput (FileOutput const & rhs) 432 | : mOutputDir(rhs.mOutputDir), 433 | mFileNamePattern(rhs.mFileNamePattern), 434 | mMaxSize(rhs.mMaxSize), 435 | mRolloverCount(rhs.mRolloverCount) 436 | { } 437 | 438 | FileOutput (FileOutput && rhs) 439 | : mOutputDir(rhs.mOutputDir), 440 | mFileNamePattern(rhs.mFileNamePattern), 441 | mMaxSize(rhs.mMaxSize), 442 | mRolloverCount(rhs.mRolloverCount), 443 | mFile(std::move(rhs.mFile)) 444 | { } 445 | 446 | ~FileOutput () 447 | { 448 | mFile.close(); 449 | } 450 | 451 | std::unique_ptr clone () const override 452 | { 453 | return std::unique_ptr( 454 | new FileOutput(*this)); 455 | } 456 | 457 | void sendLine (std::string const & line) override 458 | { 459 | if (not mFile.is_open()) 460 | { 461 | mFile.open("application.log", std::ios::app); 462 | } 463 | mFile << line << std::endl; 464 | mFile.flush(); 465 | } 466 | 467 | protected: 468 | std::filesystem::path mOutputDir; 469 | std::string mFileNamePattern; 470 | std::size_t mMaxSize; 471 | unsigned int mRolloverCount; 472 | std::fstream mFile; 473 | }; 474 | 475 | class StreamOutput : public Output 476 | { 477 | public: 478 | StreamOutput (std::ostream & stream) 479 | : mStream(stream) 480 | { } 481 | 482 | StreamOutput (StreamOutput const & rhs) 483 | : mStream(rhs.mStream) 484 | { } 485 | 486 | std::unique_ptr clone () const override 487 | { 488 | return std::unique_ptr( 489 | new StreamOutput(*this)); 490 | } 491 | 492 | void sendLine (std::string const & line) override 493 | { 494 | mStream << line << std::endl; 495 | } 496 | 497 | protected: 498 | std::ostream & mStream; 499 | }; 500 | 501 | class LogStream : public std::stringstream 502 | { 503 | public: 504 | LogStream () 505 | : mProceed(true) 506 | { } 507 | 508 | LogStream (LogStream const & other) = delete; 509 | 510 | LogStream (LogStream && other) 511 | : std::stringstream(std::move(other)), 512 | mProceed(other.mProceed) 513 | { } 514 | 515 | ~LogStream () 516 | { 517 | if (not mProceed) 518 | { 519 | return; 520 | } 521 | 522 | auto & outputs = getOutputs(); 523 | for (auto const & output: outputs) 524 | { 525 | output->sendLine(this->str()); 526 | } 527 | } 528 | 529 | LogStream & operator = (LogStream const & rhs) = delete; 530 | LogStream & operator = (LogStream && rhs) = delete; 531 | 532 | void ignore () 533 | { 534 | mProceed = false; 535 | } 536 | 537 | private: 538 | bool mProceed; 539 | }; 540 | 541 | inline LogStream log (std::vector tags = {}) 542 | { 543 | auto const now = std::chrono::system_clock::now(); 544 | std::time_t const tmNow = std::chrono::system_clock::to_time_t(now); 545 | auto const ms = duration_cast( 546 | now.time_since_epoch()) % 1000; 547 | 548 | LogStream ls; 549 | ls << std::put_time(std::gmtime(&tmNow), "%Y-%m-%dT%H:%M:%S.") 550 | << std::setw(3) << std::setfill('0') << std::to_string(ms.count()); 551 | 552 | std::map activeTags; 553 | for (auto const & defaultTag: getDefaultTags()) 554 | { 555 | activeTags[defaultTag.first] = defaultTag.second.get(); 556 | } 557 | for (auto const & tag: tags) 558 | { 559 | activeTags[tag->key()] = tag; 560 | } 561 | for (auto const & activeEntry: activeTags) 562 | { 563 | ls << " " << activeEntry.second->text(); 564 | } 565 | ls << " "; 566 | 567 | bool proceed = true; 568 | for (auto const & clause: getFilterClauses()) 569 | { 570 | proceed = false; 571 | bool allLiteralsMatch = true; 572 | for (auto const & normal: clause.second.normalLiterals) 573 | { 574 | // We need to make sure that the tag is 575 | // present and with the correct value. 576 | if (not activeTags.contains(normal->key())) 577 | { 578 | allLiteralsMatch = false; 579 | break; 580 | } 581 | if (not activeTags[normal->key()]->match(*normal)) 582 | { 583 | allLiteralsMatch = false; 584 | break; 585 | } 586 | } 587 | if (not allLiteralsMatch) 588 | { 589 | continue; 590 | } 591 | for (auto const & inverted: clause.second.invertedLiterals) 592 | { 593 | // We need to make sure that the tag is either 594 | // not present or has a mismatched value. 595 | if (activeTags.contains(inverted->key())) 596 | { 597 | if (activeTags[inverted->key()]->match(*inverted)) 598 | { 599 | allLiteralsMatch = false; 600 | } 601 | break; 602 | } 603 | } 604 | if (allLiteralsMatch) 605 | { 606 | proceed = true; 607 | break; 608 | } 609 | } 610 | 611 | if (not proceed) 612 | { 613 | ls.ignore(); 614 | } 615 | return ls; 616 | } 617 | 618 | inline auto log (Tag const & tag1) 619 | { 620 | return log({&tag1}); 621 | } 622 | 623 | inline auto log (Tag const & tag1, 624 | Tag const & tag2) 625 | { 626 | return log({&tag1, &tag2}); 627 | } 628 | 629 | inline auto log (Tag const & tag1, 630 | Tag const & tag2, 631 | Tag const & tag3) 632 | { 633 | return log({&tag1, &tag2, &tag3}); 634 | } 635 | 636 | } // namespace MereMemo 637 | 638 | #endif // MEREMEMO_LOG_H 639 | -------------------------------------------------------------------------------- /Chapter14/SimpleService/LogTags.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLESERVICE_LOGTAGS_H 2 | #define SIMPLESERVICE_LOGTAGS_H 3 | 4 | #include 5 | 6 | namespace SimpleService 7 | { 8 | 9 | inline MereMemo::LogLevel error("error"); 10 | inline MereMemo::LogLevel info("info"); 11 | inline MereMemo::LogLevel debug("debug"); 12 | 13 | class User : public MereMemo::StringTagType 14 | { 15 | public: 16 | static constexpr char key[] = "user"; 17 | 18 | User (std::string const & value, 19 | MereMemo::TagOperation operation = 20 | MereMemo::TagOperation::None) 21 | : StringTagType(value, operation) 22 | { } 23 | }; 24 | 25 | class LogPath : public MereMemo::StringTagType 26 | { 27 | public: 28 | static constexpr char key[] = "logpath"; 29 | 30 | LogPath (std::string const & value, 31 | MereMemo::TagOperation operation = 32 | MereMemo::TagOperation::None) 33 | : StringTagType(value, operation) 34 | { } 35 | }; 36 | 37 | class Request : public MereMemo::StringTagType 38 | { 39 | public: 40 | static constexpr char key[] = "request"; 41 | 42 | Request (std::string const & value, 43 | MereMemo::TagOperation operation = 44 | MereMemo::TagOperation::None) 45 | : StringTagType(value, operation) 46 | { } 47 | }; 48 | 49 | class Response : public MereMemo::StringTagType 50 | { 51 | public: 52 | static constexpr char key[] = "response"; 53 | 54 | Response (std::string const & value, 55 | MereMemo::TagOperation operation = 56 | MereMemo::TagOperation::None) 57 | : StringTagType(value, operation) 58 | { } 59 | }; 60 | 61 | } // namespace SimpleService 62 | 63 | #endif // SIMPLESERVICE_LOGTAGS_H 64 | -------------------------------------------------------------------------------- /Chapter14/SimpleService/Service.cpp: -------------------------------------------------------------------------------- 1 | #include "Service.h" 2 | 3 | #include "LogTags.h" 4 | 5 | #include 6 | 7 | void SimpleService::Service::start () 8 | { 9 | MereMemo::FileOutput appFile("logs"); 10 | MereMemo::addLogOutput(appFile); 11 | 12 | MereMemo::log(info) << "Service is starting."; 13 | } 14 | 15 | std::string SimpleService::Service::handleRequest ( 16 | std::string const & user, 17 | std::string const & path, 18 | std::string const & request) 19 | { 20 | MereMemo::log(debug, User(user), LogPath(path)) 21 | << "Received: " << Request(request); 22 | 23 | std::string response; 24 | if (request == "Hello") 25 | { 26 | response = "Hi, " + user; 27 | } 28 | else 29 | { 30 | response = "Unrecognized request."; 31 | } 32 | 33 | MereMemo::log(debug, User(user), LogPath(path)) 34 | << "Sending: " << Response(response); 35 | return response; 36 | } 37 | -------------------------------------------------------------------------------- /Chapter14/SimpleService/Service.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLESERVICE_SERVICE_H 2 | #define SIMPLESERVICE_SERVICE_H 3 | 4 | #include 5 | 6 | namespace SimpleService 7 | { 8 | 9 | class Service 10 | { 11 | public: 12 | void start (); 13 | 14 | std::string handleRequest (std::string const & user, 15 | std::string const & path, 16 | std::string const & request); 17 | }; 18 | 19 | } // namespace SimpleService 20 | 21 | #endif // SIMPLESERVICE_SERVICE_H 22 | -------------------------------------------------------------------------------- /Chapter14/SimpleService/tests/Message.cpp: -------------------------------------------------------------------------------- 1 | #include "../Service.h" 2 | 3 | #include "SetupTeardown.h" 4 | 5 | #include 6 | 7 | using namespace MereTDD; 8 | 9 | TEST_SUITE("Request can be sent and response received", "Service 1") 10 | { 11 | std::string user = "123"; 12 | std::string path = ""; 13 | std::string request = "Hello"; 14 | std::string expectedResponse = "Hi, " + user; 15 | 16 | std::string response = gService1.service().handleRequest( 17 | user, path, request); 18 | CONFIRM_THAT(response, Equals(expectedResponse)); 19 | } 20 | -------------------------------------------------------------------------------- /Chapter14/SimpleService/tests/SetupTeardown.cpp: -------------------------------------------------------------------------------- 1 | #include "SetupTeardown.h" 2 | 3 | MereTDD::TestSuiteSetupAndTeardown 4 | gService1("Greeting Service", "Service 1"); 5 | -------------------------------------------------------------------------------- /Chapter14/SimpleService/tests/SetupTeardown.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLESERVICE_TESTS_SUITES_H 2 | #define SIMPLESERVICE_TESTS_SUITES_H 3 | 4 | #include "../Service.h" 5 | 6 | #include 7 | #include 8 | 9 | class ServiceSetup 10 | { 11 | public: 12 | void setup () 13 | { 14 | mService.start(); 15 | } 16 | 17 | void teardown () 18 | { 19 | } 20 | 21 | SimpleService::Service & service () 22 | { 23 | return mService; 24 | } 25 | 26 | private: 27 | SimpleService::Service mService; 28 | }; 29 | 30 | extern MereTDD::TestSuiteSetupAndTeardown 31 | gService1; 32 | 33 | #endif // SIMPLESERVICE_TESTS_SUITES_H 34 | -------------------------------------------------------------------------------- /Chapter14/SimpleService/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter15/MereMemo/tests/Construction.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "Util.h" 4 | 5 | #include 6 | 7 | TEST("Simple message can be logged") 8 | { 9 | std::string message = "simple "; 10 | message += Util::randomString(); 11 | MereMemo::log() << message << " with more text."; 12 | 13 | bool result = Util::isTextInFile(message, "application.log"); 14 | CONFIRM_TRUE(result); 15 | } 16 | 17 | TEST("Complicated message can be logged") 18 | { 19 | std::string message = "complicated "; 20 | message += Util::randomString(); 21 | MereMemo::log() << message 22 | << " double=" << 3.14 23 | << " quoted=" << std::quoted("in quotes"); 24 | 25 | bool result = Util::isTextInFile(message, "application.log"); 26 | CONFIRM_TRUE(result); 27 | } 28 | -------------------------------------------------------------------------------- /Chapter15/MereMemo/tests/LogTags.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_TESTS_LOGTAGS_H 2 | #define MEREMEMO_TESTS_LOGTAGS_H 3 | 4 | #include "../Log.h" 5 | 6 | inline MereMemo::LogLevel error("error"); 7 | inline MereMemo::LogLevel info("info"); 8 | inline MereMemo::LogLevel debug("debug"); 9 | 10 | class Color : public MereMemo::StringTagType 11 | { 12 | public: 13 | static constexpr char key[] = "color"; 14 | 15 | Color (std::string const & value, 16 | MereMemo::TagOperation operation = 17 | MereMemo::TagOperation::None) 18 | : StringTagType(value, operation) 19 | { } 20 | }; 21 | 22 | inline Color red("red"); 23 | inline Color green("green"); 24 | inline Color blue("blue"); 25 | 26 | class Size : public MereMemo::StringTagType 27 | { 28 | public: 29 | static constexpr char key[] = "size"; 30 | 31 | Size (std::string const & value, 32 | MereMemo::TagOperation operation = 33 | MereMemo::TagOperation::None) 34 | : StringTagType(value, operation) 35 | { } 36 | }; 37 | 38 | inline Size small("small"); 39 | inline Size medium("medium"); 40 | inline Size large("large"); 41 | 42 | class Count : public MereMemo::IntTagType 43 | { 44 | public: 45 | static constexpr char key[] = "count"; 46 | 47 | Count (int value, 48 | MereMemo::TagOperation operation = 49 | MereMemo::TagOperation::None) 50 | : IntTagType(value, operation) 51 | { } 52 | }; 53 | 54 | class Identity : public MereMemo::LongLongTagType 55 | { 56 | public: 57 | static constexpr char key[] = "id"; 58 | 59 | Identity (long long value, 60 | MereMemo::TagOperation operation = 61 | MereMemo::TagOperation::None) 62 | : LongLongTagType(value, operation) 63 | { } 64 | }; 65 | 66 | class Scale : public MereMemo::DoubleTagType 67 | { 68 | public: 69 | static constexpr char key[] = "scale"; 70 | 71 | Scale (double value, 72 | MereMemo::TagOperation operation = 73 | MereMemo::TagOperation::None) 74 | : DoubleTagType(value, operation) 75 | { } 76 | }; 77 | 78 | class CacheHit : public MereMemo::BoolTagType 79 | { 80 | public: 81 | static constexpr char key[] = "cache_hit"; 82 | 83 | CacheHit (bool value, 84 | MereMemo::TagOperation operation = 85 | MereMemo::TagOperation::None) 86 | : BoolTagType(value, operation) 87 | { } 88 | }; 89 | 90 | inline CacheHit cacheHit(true); 91 | inline CacheHit cacheMiss(false); 92 | 93 | #endif // MEREMEMO_TESTS_LOGTAGS_H 94 | -------------------------------------------------------------------------------- /Chapter15/MereMemo/tests/Tags.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "LogTags.h" 4 | #include "Util.h" 5 | 6 | #include 7 | 8 | class TempFilterClause 9 | { 10 | public: 11 | void setup () 12 | { 13 | mId = MereMemo::createFilterClause(); 14 | } 15 | 16 | void teardown () 17 | { 18 | MereMemo::clearFilterClause(mId); 19 | } 20 | 21 | int id () const 22 | { 23 | return mId; 24 | } 25 | 26 | private: 27 | int mId; 28 | }; 29 | 30 | TEST("Message can be tagged in log") 31 | { 32 | std::string message = "simple tag "; 33 | message += Util::randomString(); 34 | MereMemo::log(error) << message; 35 | 36 | // Confirm that the error tag value exists and that the 37 | // default info tag value does not. 38 | std::string logLevelTag = " log_level=\"error\" "; 39 | std::string defaultLogLevelTag = " log_level=\"info\" "; 40 | bool result = Util::isTextInFile(message, "application.log", 41 | {logLevelTag}, {defaultLogLevelTag}); 42 | CONFIRM_TRUE(result); 43 | } 44 | 45 | TEST("log needs no namespace when used with LogLevel") 46 | { 47 | log(error) << "no namespace"; 48 | } 49 | 50 | TEST("Default tags set in main appear in log") 51 | { 52 | std::string message = "default tag "; 53 | message += Util::randomString(); 54 | MereMemo::log() << message; 55 | 56 | std::string logLevelTag = " log_level=\"info\" "; 57 | std::string colorTag = " color=\"green\" "; 58 | bool result = Util::isTextInFile(message, "application.log", 59 | {logLevelTag, colorTag}); 60 | CONFIRM_TRUE(result); 61 | } 62 | 63 | TEST("Multiple tags can be used in log") 64 | { 65 | std::string message = "multi tags "; 66 | message += Util::randomString(); 67 | MereMemo::log(debug, red, large) << message; 68 | 69 | std::string logLevelTag = " log_level=\"debug\" "; 70 | std::string colorTag = " color=\"red\" "; 71 | std::string sizeTag = " size=\"large\" "; 72 | bool result = Util::isTextInFile(message, "application.log", 73 | {logLevelTag, colorTag, sizeTag}); 74 | CONFIRM_TRUE(result); 75 | } 76 | 77 | TEST("Tags can be streamed to log") 78 | { 79 | std::string messageBase = " 1 type "; 80 | std::string message = messageBase + Util::randomString(); 81 | MereMemo::log(info) << Count(1) << message; 82 | 83 | std::string countTag = " count=1 "; 84 | bool result = Util::isTextInFile(message, "application.log", 85 | {countTag}); 86 | CONFIRM_TRUE(result); 87 | 88 | messageBase = " 2 type "; 89 | message = messageBase + Util::randomString(); 90 | MereMemo::log(info) << Identity(123456789012345) << message; 91 | 92 | std::string idTag = " id=123456789012345 "; 93 | result = Util::isTextInFile(message, "application.log", 94 | {idTag}); 95 | CONFIRM_TRUE(result); 96 | 97 | messageBase = " 3 type "; 98 | message = messageBase + Util::randomString(); 99 | MereMemo::log(info) << Scale(1.5) << message; 100 | 101 | std::string scaleTag = " scale=1.500000 "; 102 | result = Util::isTextInFile(message, "application.log", 103 | {scaleTag}); 104 | CONFIRM_TRUE(result); 105 | 106 | messageBase = " 4 type "; 107 | message = messageBase + Util::randomString(); 108 | MereMemo::log(info) << cacheMiss << message; 109 | 110 | std::string cacheTag = " cache_hit=false "; 111 | result = Util::isTextInFile(message, "application.log", 112 | {cacheTag}); 113 | CONFIRM_TRUE(result); 114 | } 115 | 116 | TEST("Tags can be used to filter messages") 117 | { 118 | MereTDD::SetupAndTeardown filter; 119 | MereMemo::addFilterLiteral(filter.id(), error); 120 | 121 | std::string message = "filter "; 122 | message += Util::randomString(); 123 | MereMemo::log(info) << message; 124 | 125 | bool result = Util::isTextInFile(message, "application.log"); 126 | CONFIRM_FALSE(result); 127 | 128 | MereMemo::clearFilterClause(filter.id()); 129 | 130 | MereMemo::log(info) << message; 131 | 132 | result = Util::isTextInFile(message, "application.log"); 133 | CONFIRM_TRUE(result); 134 | } 135 | 136 | TEST("Overridden default tag not used to filter messages") 137 | { 138 | MereTDD::SetupAndTeardown filter; 139 | MereMemo::addFilterLiteral(filter.id(), info); 140 | 141 | std::string message = "override default "; 142 | message += Util::randomString(); 143 | MereMemo::log(debug) << message; 144 | 145 | bool result = Util::isTextInFile(message, "application.log"); 146 | CONFIRM_FALSE(result); 147 | } 148 | 149 | TEST("Inverted tag can be used to filter messages") 150 | { 151 | MereTDD::SetupAndTeardown filter; 152 | MereMemo::addFilterLiteral(filter.id(), green, false); 153 | 154 | std::string message = "inverted "; 155 | message += Util::randomString(); 156 | MereMemo::log(info) << message; 157 | 158 | bool result = Util::isTextInFile(message, "application.log"); 159 | CONFIRM_FALSE(result); 160 | } 161 | 162 | TEST("Tag values can be used to filter messages") 163 | { 164 | MereTDD::SetupAndTeardown filter; 165 | MereMemo::addFilterLiteral(filter.id(), 166 | Count(100, MereMemo::TagOperation::GreaterThan)); 167 | 168 | std::string message = "values "; 169 | message += Util::randomString(); 170 | MereMemo::log(Count(1)) << message; 171 | 172 | bool result = Util::isTextInFile(message, "application.log"); 173 | CONFIRM_FALSE(result); 174 | 175 | MereMemo::log() << Count(101) << message; 176 | 177 | result = Util::isTextInFile(message, "application.log"); 178 | CONFIRM_FALSE(result); 179 | 180 | MereMemo::log(Count(101)) << message; 181 | 182 | result = Util::isTextInFile(message, "application.log"); 183 | CONFIRM_TRUE(result); 184 | } 185 | -------------------------------------------------------------------------------- /Chapter15/MereMemo/tests/Thread.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "Util.h" 4 | 5 | #include 6 | #include 7 | 8 | TEST("log can be called from multiple threads") 9 | { 10 | // We'll have 3 threads with 50 messages each. 11 | std::vector messages; 12 | for (int i = 0; i < 150; ++i) 13 | { 14 | std::string message = std::to_string(i); 15 | message += " thread-safe message "; 16 | message += Util::randomString(); 17 | messages.push_back(message); 18 | } 19 | 20 | std::vector threads; 21 | for (int c = 0; c < 3; ++c) 22 | { 23 | threads.emplace_back( 24 | [c, &messages]() 25 | { 26 | int indexStart = c * 50; 27 | for (int i = 0; i < 50; ++i) 28 | { 29 | MereMemo::log() << messages[indexStart + i]; 30 | } 31 | }); 32 | } 33 | 34 | for (auto & t : threads) 35 | { 36 | t.join(); 37 | } 38 | for (auto const & message: messages) 39 | { 40 | bool result = Util::isTextInFile(message, "application.log"); 41 | CONFIRM_TRUE(result); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Chapter15/MereMemo/tests/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | std::string Util::randomString () 8 | { 9 | static bool firstCall = true; 10 | static std::mt19937 rng; 11 | if (firstCall) 12 | { 13 | // We only need to set the seed once. 14 | firstCall = false; 15 | 16 | unsigned int seed = static_cast( 17 | std::chrono::system_clock::now(). 18 | time_since_epoch().count()); 19 | rng.seed(seed); 20 | } 21 | std::uniform_int_distribution dist(1, 10000); 22 | 23 | return std::to_string(dist(rng)); 24 | } 25 | 26 | bool Util::isTextInFile ( 27 | std::string_view text, 28 | std::string_view fileName, 29 | std::vector const & wantedTags, 30 | std::vector const & unwantedTags) 31 | { 32 | std::ifstream logfile(fileName.data()); 33 | std::string line; 34 | while (getline(logfile, line)) 35 | { 36 | if (line.find(text) != std::string::npos) 37 | { 38 | for (auto const & tag: wantedTags) 39 | { 40 | if (line.find(tag) == std::string::npos) 41 | { 42 | return false; 43 | } 44 | } 45 | for (auto const & tag: unwantedTags) 46 | { 47 | if (line.find(tag) != std::string::npos) 48 | { 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | -------------------------------------------------------------------------------- /Chapter15/MereMemo/tests/Util.h: -------------------------------------------------------------------------------- 1 | #ifndef MEREMEMO_TESTS_UTIL_H 2 | #define MEREMEMO_TESTS_UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct Util 9 | { 10 | static std::string randomString (); 11 | 12 | static bool isTextInFile ( 13 | std::string_view text, 14 | std::string_view fileName, 15 | std::vector const & wantedTags = {}, 16 | std::vector const & unwantedTags = {}); 17 | }; 18 | 19 | #endif // MEREMEMO_TESTS_UTIL_H 20 | -------------------------------------------------------------------------------- /Chapter15/MereMemo/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Log.h" 2 | 3 | #include "LogTags.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | int main () 10 | { 11 | MereMemo::FileOutput appFile("logs"); 12 | //appFile.namePattern() = "application-{}.log"; 13 | //appFile.maxSize() = 10'000'000; 14 | //appFile.rolloverCount() = 5; 15 | MereMemo::addLogOutput(appFile); 16 | 17 | MereMemo::StreamOutput consoleStream(std::cout); 18 | MereMemo::addLogOutput(consoleStream); 19 | 20 | MereMemo::addDefaultTag(info); 21 | MereMemo::addDefaultTag(green); 22 | 23 | return MereTDD::runTests(std::cout); 24 | } 25 | -------------------------------------------------------------------------------- /Chapter15/MereTDD/tests/Confirm.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | bool isNegative (int value) 4 | { 5 | return value < 0; 6 | } 7 | 8 | int multiplyBy2 (int value) 9 | { 10 | return value * 2; 11 | } 12 | 13 | long multiplyBy2 (long value) 14 | { 15 | return value * 2L; 16 | } 17 | 18 | long long multiplyBy2 (long long value) 19 | { 20 | return value * 2LL; 21 | } 22 | 23 | TEST("Test will pass without any confirms") 24 | { 25 | } 26 | 27 | TEST("Test bool confirms") 28 | { 29 | bool result = isNegative(0); 30 | CONFIRM_FALSE(result); 31 | 32 | result = isNegative(-1); 33 | CONFIRM_TRUE(result); 34 | } 35 | 36 | TEST("Test int confirms") 37 | { 38 | int result = multiplyBy2(0); 39 | CONFIRM(0, result); 40 | 41 | result = multiplyBy2(1); 42 | CONFIRM(2, result); 43 | 44 | result = multiplyBy2(-1); 45 | CONFIRM(-2, result); 46 | } 47 | 48 | TEST("Test long confirms") 49 | { 50 | long result = multiplyBy2(0L); 51 | CONFIRM(0L, result); 52 | 53 | result = multiplyBy2(1L); 54 | CONFIRM(2L, result); 55 | 56 | result = multiplyBy2(-1L); 57 | CONFIRM(-2L, result); 58 | } 59 | 60 | TEST("Test long long confirms") 61 | { 62 | long long result = multiplyBy2(0LL); 63 | CONFIRM(0LL, result); 64 | 65 | result = multiplyBy2(10'000'000'000LL); 66 | CONFIRM(20'000'000'000LL, result); 67 | 68 | result = multiplyBy2(-10'000'000'000LL); 69 | CONFIRM(-20'000'000'000LL, result); 70 | } 71 | 72 | TEST("Test string confirms") 73 | { 74 | std::string result = "abc"; 75 | std::string expected = "abc"; 76 | CONFIRM(expected, result); 77 | } 78 | 79 | TEST("Test string and string literal confirms") 80 | { 81 | std::string result = "abc"; 82 | CONFIRM("abc", result); 83 | } 84 | 85 | TEST("Test float confirms") 86 | { 87 | float f1 = 0.1f; 88 | float f2 = 0.2f; 89 | float sum = f1 + f2; 90 | float expected = 0.3f; 91 | CONFIRM(expected, sum); 92 | } 93 | 94 | TEST("Test double confirms") 95 | { 96 | double d1 = 0.1; 97 | double d2 = 0.2; 98 | double sum = d1 + d2; 99 | double expected = 0.3; 100 | CONFIRM(expected, sum); 101 | } 102 | 103 | TEST("Test long double confirms") 104 | { 105 | long double ld1 = 0.1; 106 | long double ld2 = 0.2; 107 | long double sum = ld1 + ld2; 108 | long double expected = 0.3; 109 | CONFIRM(expected, sum); 110 | } 111 | 112 | TEST("Test bool confirm failure") 113 | { 114 | std::string reason = " Expected: true"; 115 | setExpectedFailureReason(reason); 116 | 117 | bool result = isNegative(0); 118 | CONFIRM_TRUE(result); 119 | } 120 | 121 | TEST("Test int confirm failure") 122 | { 123 | std::string reason = " Expected: 0\n"; 124 | reason += " Actual : 2"; 125 | setExpectedFailureReason(reason); 126 | 127 | int result = multiplyBy2(1); 128 | CONFIRM(0, result); 129 | } 130 | 131 | TEST("Test long confirm failure") 132 | { 133 | std::string reason = " Expected: 0\n"; 134 | reason += " Actual : 2"; 135 | setExpectedFailureReason(reason); 136 | 137 | long result = multiplyBy2(1L); 138 | CONFIRM(0L, result); 139 | } 140 | 141 | TEST("Test long long confirm failure") 142 | { 143 | std::string reason = " Expected: 10000000000\n"; 144 | reason += " Actual : 20000000000"; 145 | setExpectedFailureReason(reason); 146 | 147 | long long result = multiplyBy2(10'000'000'000LL); 148 | CONFIRM(10'000'000'000LL, result); 149 | } 150 | 151 | TEST("Test string confirm failure") 152 | { 153 | std::string reason = " Expected: def\n"; 154 | reason += " Actual : abc"; 155 | setExpectedFailureReason(reason); 156 | 157 | std::string result = "abc"; 158 | std::string expected = "def"; 159 | CONFIRM(expected, result); 160 | } 161 | -------------------------------------------------------------------------------- /Chapter15/MereTDD/tests/Creation.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | TEST("Test can be created") 4 | { 5 | } 6 | 7 | // This test should fail because it throws an 8 | // unexpected exception. 9 | TEST("Test that throws unexpectedly can be created") 10 | { 11 | setExpectedFailureReason( 12 | "Unexpected exception thrown."); 13 | 14 | throw "Unexpected"; 15 | } 16 | 17 | // This test should throw an unexpected exception 18 | // but it doesn't. We need to somehow let the user 19 | // know what happened. This will result in a missed failure. 20 | TEST("Test that should throw unexpectedly can be created") 21 | { 22 | setExpectedFailureReason( 23 | "Unexpected exception thrown."); 24 | } 25 | 26 | TEST_EX("Test with throw can be created", int) 27 | { 28 | throw 1; 29 | } 30 | 31 | // This test should fail because it does not throw 32 | // an exception that it is expecting to be thrown. 33 | TEST_EX("Test that never throws can be created", int) 34 | { 35 | setExpectedFailureReason( 36 | "Expected exception type int was not thrown."); 37 | } 38 | 39 | // This test should fail because it throws an 40 | // exception that does not match the expected type. 41 | TEST_EX("Test that throws wrong type can be created", int) 42 | { 43 | setExpectedFailureReason( 44 | "Unexpected exception thrown."); 45 | 46 | throw "Wrong type"; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter15/MereTDD/tests/Hamcrest.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | using namespace MereTDD; 4 | 5 | template 6 | T calculateFraction (T input) 7 | { 8 | T denominator {10}; 9 | return input / denominator; 10 | } 11 | 12 | template 13 | T accumulateError (T input) 14 | { 15 | // First add many small amounts. 16 | T partialAmount {0.1}; 17 | for (int i = 0; i < 10; ++i) 18 | { 19 | input += partialAmount; 20 | } 21 | // Then subtract to get back to the original. 22 | T wholeAmount {1}; 23 | input -= wholeAmount; 24 | return input; 25 | } 26 | 27 | template 28 | int performComparisons (int totalCount) 29 | { 30 | int passCount {0}; 31 | for (int i = 0; i < totalCount; ++i) 32 | { 33 | T expected = static_cast(i); 34 | expected = calculateFraction(expected); 35 | T actual = accumulateError(expected); 36 | if (compareEq(actual, expected)) 37 | { 38 | ++passCount; 39 | } 40 | } 41 | return passCount; 42 | } 43 | 44 | TEST("Test can use hamcrest style confirm") 45 | { 46 | int ten = 10; 47 | CONFIRM_THAT(ten, Equals(10)); 48 | } 49 | 50 | TEST("Test hamcrest style confirm failure") 51 | { 52 | std::string reason = " Expected: 9\n"; 53 | reason += " Actual : 10"; 54 | setExpectedFailureReason(reason); 55 | 56 | int ten = 10; 57 | CONFIRM_THAT(ten, Equals(9)); 58 | } 59 | 60 | TEST("Test other hamcrest style integer confirms") 61 | { 62 | char c1 = 'A'; 63 | char c2 = 'A'; 64 | CONFIRM_THAT(c1, Equals(c2)); 65 | CONFIRM_THAT(c1, Equals('A')); 66 | 67 | short s1 = 10; 68 | short s2 = 10; 69 | CONFIRM_THAT(s1, Equals(s2)); 70 | CONFIRM_THAT(s1, Equals(10)); 71 | 72 | unsigned int ui1 = 3'000'000'000; 73 | unsigned int ui2 = 3'000'000'000; 74 | CONFIRM_THAT(ui1, Equals(ui2)); 75 | CONFIRM_THAT(ui1, Equals(3'000'000'000)); 76 | 77 | long long ll1 = 5'000'000'000'000LL; 78 | long long ll2 = 5'000'000'000'000LL; 79 | CONFIRM_THAT(ll1, Equals(ll2)); 80 | CONFIRM_THAT(ll1, Equals(5'000'000'000'000LL)); 81 | } 82 | 83 | TEST("Test hamcrest style bool confirms") 84 | { 85 | bool b1 = true; 86 | bool b2 = true; 87 | CONFIRM_THAT(b1, Equals(b2)); 88 | 89 | // This works but probably won't be used much. 90 | CONFIRM_THAT(b1, Equals(true)); 91 | 92 | // When checking a bool variable for a known value, 93 | // the classic style is probably better. 94 | CONFIRM_TRUE(b1); 95 | } 96 | 97 | TEST("Test hamcrest style string confirms") 98 | { 99 | std::string s1 = "abc"; 100 | std::string s2 = "abc"; 101 | CONFIRM_THAT(s1, Equals(s2)); // string vs. string 102 | CONFIRM_THAT(s1, Equals("abc")); // string vs. literal 103 | CONFIRM_THAT("abc", Equals(s1)); // literal vs. string 104 | 105 | // Probably not needed, but this works too. 106 | CONFIRM_THAT("abc", Equals("abc")); // literal vs. literal 107 | 108 | std::string s3 = "def"; 109 | CONFIRM_THAT(s1, NotEquals(s3)); // string vs. string 110 | CONFIRM_THAT(s1, NotEquals("def")); // string vs. literal 111 | CONFIRM_THAT("def", NotEquals(s1)); // literal vs. string 112 | } 113 | 114 | TEST("Test hamcrest style string pointer confirms") 115 | { 116 | char const * sp1 = "abc"; 117 | std::string s1 = "abc"; 118 | char const * sp2 = s1.c_str(); // avoid sp1 and sp2 being same 119 | CONFIRM_THAT(sp1, Equals(sp2)); // pointer vs. pointer 120 | CONFIRM_THAT(sp2, Equals("abc")); // pointer vs. literal 121 | CONFIRM_THAT("abc", Equals(sp2)); // literal vs. pointer 122 | CONFIRM_THAT(sp1, Equals(s1)); // pointer vs. string 123 | CONFIRM_THAT(s1, Equals(sp1)); // string vs. pointer 124 | 125 | char const * sp3 = "def"; 126 | CONFIRM_THAT(sp1, NotEquals(sp3)); // pointer vs. pointer 127 | CONFIRM_THAT(sp1, NotEquals("def")); // pointer vs. literal 128 | CONFIRM_THAT("def", NotEquals(sp1)); // literal vs. pointer 129 | CONFIRM_THAT(sp3, NotEquals(s1)); // pointer vs. string 130 | CONFIRM_THAT(s1, NotEquals(sp3)); // string vs. pointer 131 | } 132 | 133 | TEST("Test many float comparisons") 134 | { 135 | int totalCount {1'000}; 136 | int passCount = performComparisons(totalCount); 137 | CONFIRM_THAT(passCount, Equals(totalCount)); 138 | } 139 | 140 | TEST("Test many double comparisons") 141 | { 142 | int totalCount {1'000}; 143 | int passCount = performComparisons(totalCount); 144 | CONFIRM_THAT(passCount, Equals(totalCount)); 145 | } 146 | 147 | TEST("Test many long double comparisons") 148 | { 149 | int totalCount {1'000}; 150 | int passCount = performComparisons(totalCount); 151 | CONFIRM_THAT(passCount, Equals(totalCount)); 152 | } 153 | 154 | TEST("Test small float values") 155 | { 156 | // Based on float epsilon = 1.1920928955078125e-07 157 | CONFIRM_THAT(0.000001f, NotEquals(0.000002f)); 158 | } 159 | 160 | TEST("Test large float values") 161 | { 162 | // Based on float epsilon = 1.1920928955078125e-07 163 | CONFIRM_THAT(9'999.0f, Equals(9'999.001f)); 164 | } 165 | 166 | TEST("Test small double values") 167 | { 168 | // Based on double epsilon = 2.2204460492503130808e-16 169 | CONFIRM_THAT(0.000000000000001, NotEquals(0.000000000000002)); 170 | } 171 | 172 | TEST("Test large double values") 173 | { 174 | // Based on double epsilon = 2.2204460492503130808e-16 175 | CONFIRM_THAT(1'500'000'000'000.0, Equals(1'500'000'000'000.0003)); 176 | } 177 | 178 | TEST("Test small long double values") 179 | { 180 | // Based on double epsilon = 2.2204460492503130808e-16 181 | CONFIRM_THAT(0.000000000000001L, NotEquals(0.000000000000002L)); 182 | } 183 | 184 | TEST("Test large long double values") 185 | { 186 | // Based on double epsilon = 2.2204460492503130808e-16 187 | CONFIRM_THAT(1'500'000'000'000.0L, Equals(1'500'000'000'000.0003L)); 188 | } 189 | 190 | TEST("Test even integral value") 191 | { 192 | CONFIRM_THAT(10, IsEven()); 193 | } 194 | 195 | TEST("Test even integral value confirm failure") 196 | { 197 | std::string reason = " Expected: is even\n"; 198 | reason += " Actual : 11"; 199 | setExpectedFailureReason(reason); 200 | 201 | CONFIRM_THAT(11, IsEven()); 202 | } 203 | -------------------------------------------------------------------------------- /Chapter15/MereTDD/tests/Setup.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | #include 5 | 6 | std::string createTestTable () 7 | { 8 | // If this was real code, it might open a 9 | // connection to a database, create a temp 10 | // table with a random name, and return the 11 | // table name. 12 | return "test_data_01"; 13 | } 14 | 15 | void dropTestTable (std::string_view /*name*/) 16 | { 17 | // Real code would use the name to drop 18 | // the table. 19 | } 20 | 21 | int createTestEntry () 22 | { 23 | // If this was real code, it might open a 24 | // connection to a database, insert a row 25 | // of data, and return the row identifier. 26 | return 100; 27 | } 28 | 29 | void updateTestEntryName (int /*id*/, std::string_view name) 30 | { 31 | if (name.empty()) 32 | { 33 | throw 1; 34 | } 35 | // Real code would proceed to update the 36 | // data with the new name. 37 | } 38 | 39 | void deleteTestEntry (int /*id*/) 40 | { 41 | // Real code would use the id to delete 42 | // the temporary row of data. 43 | } 44 | 45 | class TempEntry 46 | { 47 | public: 48 | void setup () 49 | { 50 | mId = createTestEntry(); 51 | } 52 | 53 | void teardown () 54 | { 55 | deleteTestEntry(mId); 56 | } 57 | 58 | int id () 59 | { 60 | return mId; 61 | } 62 | 63 | private: 64 | int mId; 65 | }; 66 | 67 | class TempTable 68 | { 69 | public: 70 | void setup () 71 | { 72 | mName = createTestTable(); 73 | } 74 | 75 | void teardown () 76 | { 77 | dropTestTable(mName); 78 | } 79 | 80 | std::string tableName () 81 | { 82 | return mName; 83 | } 84 | 85 | private: 86 | std::string mName; 87 | }; 88 | 89 | TEST_EX("Test will run setup and teardown code", int) 90 | { 91 | MereTDD::SetupAndTeardown entry; 92 | 93 | // If this was a project test, it might be called 94 | // "Updating empty name throws". And the type thrown 95 | // would not be an int. 96 | updateTestEntryName(entry.id(), ""); 97 | } 98 | 99 | TEST("Test will run multiple setup and teardown code") 100 | { 101 | MereTDD::SetupAndTeardown entry1; 102 | MereTDD::SetupAndTeardown entry2; 103 | 104 | // If this was a project test, it might need 105 | // more than one temporary entry. The TempEntry 106 | // policy could either create multiple data records 107 | // or it is easier to just have multiple instances 108 | // that each create a single data entry. 109 | updateTestEntryName(entry1.id(), "abc"); 110 | updateTestEntryName(entry2.id(), "def"); 111 | } 112 | 113 | MereTDD::TestSuiteSetupAndTeardown 114 | gTable1("Test suite setup/teardown 1", "Suite 1"); 115 | 116 | MereTDD::TestSuiteSetupAndTeardown 117 | gTable2("Test suite setup/teardown 2", "Suite 1"); 118 | 119 | TEST_SUITE("Test part 1 of suite", "Suite 1") 120 | { 121 | // If this was a project test, it could use 122 | // the table names from gTable1 and gTable2. 123 | CONFIRM("test_data_01", gTable1.tableName()); 124 | CONFIRM("test_data_01", gTable2.tableName()); 125 | } 126 | 127 | TEST_SUITE_EX("Test part 2 of suite", "Suite 1", int) 128 | { 129 | // If this was a project test, it could use 130 | // the table names from gTable1 and gTable2. 131 | throw 1; 132 | } 133 | -------------------------------------------------------------------------------- /Chapter15/MereTDD/tests/Thread.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace MereTDD; 7 | 8 | TEST("Test can use additional threads") 9 | { 10 | std::vector threadExs(2); 11 | std::atomic count {0}; 12 | std::vector threads; 13 | for (int c = 0; c < 2; ++c) 14 | { 15 | threads.emplace_back( 16 | [&threadEx = threadExs[c], &count]() 17 | { 18 | try 19 | { 20 | for (int i = 0; i < 100'000; ++i) 21 | { 22 | ++count; 23 | } 24 | CONFIRM_THAT(count, NotEquals(200'001)); 25 | } 26 | catch (ConfirmException const & ex) 27 | { 28 | threadEx.setFailure(ex.line(), ex.reason()); 29 | } 30 | }); 31 | } 32 | 33 | for (auto & t : threads) 34 | { 35 | t.join(); 36 | } 37 | for (auto const & ex: threadExs) 38 | { 39 | ex.checkFailure(); 40 | } 41 | CONFIRM_THAT(count, Equals(200'000)); 42 | } 43 | -------------------------------------------------------------------------------- /Chapter15/MereTDD/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../Test.h" 2 | 3 | #include 4 | 5 | int main () 6 | { 7 | return MereTDD::runTests(std::cout); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter15/SimpleService/LogTags.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLESERVICE_LOGTAGS_H 2 | #define SIMPLESERVICE_LOGTAGS_H 3 | 4 | #include 5 | 6 | namespace SimpleService 7 | { 8 | 9 | inline MereMemo::LogLevel error("error"); 10 | inline MereMemo::LogLevel info("info"); 11 | inline MereMemo::LogLevel debug("debug"); 12 | 13 | class User : public MereMemo::StringTagType 14 | { 15 | public: 16 | static constexpr char key[] = "user"; 17 | 18 | User (std::string const & value, 19 | MereMemo::TagOperation operation = 20 | MereMemo::TagOperation::None) 21 | : StringTagType(value, operation) 22 | { } 23 | }; 24 | 25 | class LogPath : public MereMemo::StringTagType 26 | { 27 | public: 28 | static constexpr char key[] = "logpath"; 29 | 30 | LogPath (std::string const & value, 31 | MereMemo::TagOperation operation = 32 | MereMemo::TagOperation::None) 33 | : StringTagType(value, operation) 34 | { } 35 | }; 36 | 37 | class Request : public MereMemo::StringTagType 38 | { 39 | public: 40 | static constexpr char key[] = "request"; 41 | 42 | Request (std::string const & value, 43 | MereMemo::TagOperation operation = 44 | MereMemo::TagOperation::None) 45 | : StringTagType(value, operation) 46 | { } 47 | }; 48 | 49 | class Response : public MereMemo::StringTagType 50 | { 51 | public: 52 | static constexpr char key[] = "response"; 53 | 54 | Response (std::string const & value, 55 | MereMemo::TagOperation operation = 56 | MereMemo::TagOperation::None) 57 | : StringTagType(value, operation) 58 | { } 59 | }; 60 | 61 | } // namespace SimpleService 62 | 63 | #endif // SIMPLESERVICE_LOGTAGS_H 64 | -------------------------------------------------------------------------------- /Chapter15/SimpleService/Service.cpp: -------------------------------------------------------------------------------- 1 | #include "Service.h" 2 | 3 | #include "LogTags.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace 11 | { 12 | std::mutex & getCalcMutex () 13 | { 14 | static std::mutex m; 15 | return m; 16 | } 17 | 18 | class CalcRecord 19 | { 20 | public: 21 | CalcRecord () 22 | { } 23 | 24 | CalcRecord (CalcRecord const & src) 25 | { 26 | const std::lock_guard lock(getCalcMutex()); 27 | mComplete = src.mComplete; 28 | mProgress = src.mProgress; 29 | mResult = src.mResult; 30 | } 31 | 32 | void getData (bool & complete, int & progress, int & result) 33 | { 34 | const std::lock_guard lock(getCalcMutex()); 35 | complete = mComplete; 36 | progress = mProgress; 37 | result = mResult; 38 | } 39 | 40 | void setData (bool complete, int progress, int result) 41 | { 42 | const std::lock_guard lock(getCalcMutex()); 43 | mComplete = complete; 44 | mProgress = progress; 45 | mResult = result; 46 | } 47 | 48 | CalcRecord & 49 | operator = (CalcRecord const & rhs) = delete; 50 | 51 | private: 52 | bool mComplete {false}; 53 | int mProgress {0}; 54 | int mResult {0}; 55 | }; 56 | 57 | std::vector calculations; 58 | } 59 | 60 | void SimpleService::normalCalc ( 61 | int seed, int & progress, int & result) 62 | { 63 | progress = 100; 64 | result = seed * 10; 65 | } 66 | 67 | std::mutex SimpleService::service2Mutex; 68 | std::condition_variable SimpleService::testCalcCV; 69 | std::condition_variable SimpleService::testCV; 70 | bool SimpleService::testCalcReady {false}; 71 | bool SimpleService::testReady {false}; 72 | 73 | void SimpleService::testCalc ( 74 | int seed, int & progress, int & result) 75 | { 76 | // Wait until the test has completed the first status request. 77 | { 78 | std::unique_lock lock(service2Mutex); 79 | testCV.wait(lock, [] 80 | { 81 | return testReady; 82 | }); 83 | } 84 | 85 | progress = 100; 86 | result = seed * 8; 87 | 88 | // Notify the test that the calculation is ready. 89 | { 90 | std::lock_guard lock(service2Mutex); 91 | testCalcReady = true; 92 | } 93 | testCalcCV.notify_one(); 94 | } 95 | 96 | void SimpleService::Service::start () 97 | { 98 | MereMemo::log(info) << "Service is starting."; 99 | } 100 | 101 | SimpleService::ResponseVar SimpleService::Service::handleRequest ( 102 | std::string const & user, 103 | std::string const & path, 104 | RequestVar const & request) 105 | { 106 | ResponseVar response; 107 | if (auto const * req = std::get_if(&request)) 108 | { 109 | MereMemo::log(debug, User(user), LogPath(path)) 110 | << "Received Calculate request for: " 111 | << std::to_string(req->mSeed); 112 | 113 | calculations.emplace_back(); 114 | int calcIndex = calculations.size() - 1; 115 | int seed = req->mSeed; 116 | std::thread calcThread([this, calcIndex, seed] () 117 | { 118 | int progress {0}; 119 | int result {0}; 120 | while (true) 121 | { 122 | mCalc(seed, progress, result); 123 | if (progress == 100) 124 | { 125 | calculations[calcIndex].setData(true, progress, result); 126 | break; 127 | } 128 | else 129 | { 130 | calculations[calcIndex].setData(false, progress, result); 131 | } 132 | } 133 | }); 134 | calcThread.detach(); 135 | response = SimpleService::CalculateResponse { 136 | .mToken = std::to_string(calcIndex) 137 | }; 138 | } 139 | else if (auto const * req = std::get_if(&request)) 140 | { 141 | MereMemo::log(debug, User(user), LogPath(path)) 142 | << "Received Status request for: " 143 | << req->mToken; 144 | 145 | int calcIndex = std::stoi(req->mToken); 146 | bool complete; 147 | int progress; 148 | int result; 149 | calculations[calcIndex].getData(complete, progress, result); 150 | response = SimpleService::StatusResponse { 151 | .mComplete = complete, 152 | .mProgress = progress, 153 | .mResult = result 154 | }; 155 | } 156 | else 157 | { 158 | response = SimpleService::ErrorResponse { 159 | .mReason = "Unrecognized request." 160 | }; 161 | } 162 | 163 | return response; 164 | } 165 | -------------------------------------------------------------------------------- /Chapter15/SimpleService/Service.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLESERVICE_SERVICE_H 2 | #define SIMPLESERVICE_SERVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace SimpleService 9 | { 10 | 11 | struct CalculateRequest 12 | { 13 | int mSeed; 14 | }; 15 | 16 | struct StatusRequest 17 | { 18 | std::string mToken; 19 | }; 20 | 21 | using RequestVar = std::variant< 22 | CalculateRequest, 23 | StatusRequest 24 | >; 25 | 26 | struct ErrorResponse 27 | { 28 | std::string mReason; 29 | }; 30 | 31 | struct CalculateResponse 32 | { 33 | std::string mToken; 34 | }; 35 | 36 | struct StatusResponse 37 | { 38 | bool mComplete; 39 | int mProgress; 40 | int mResult; 41 | }; 42 | 43 | using ResponseVar = std::variant< 44 | ErrorResponse, 45 | CalculateResponse, 46 | StatusResponse 47 | >; 48 | 49 | void normalCalc (int seed, int & progress, int & result); 50 | 51 | extern std::mutex service2Mutex; 52 | extern std::condition_variable testCalcCV; 53 | extern std::condition_variable testCV; 54 | extern bool testCalcReady; 55 | extern bool testReady; 56 | void testCalc (int seed, int & progress, int & result); 57 | 58 | class Service 59 | { 60 | public: 61 | using CalcFunc = void (*) (int, int &, int &); 62 | 63 | Service (CalcFunc f = normalCalc) 64 | : mCalc(f) 65 | { } 66 | 67 | void start (); 68 | 69 | ResponseVar handleRequest (std::string const & user, 70 | std::string const & path, 71 | RequestVar const & request); 72 | 73 | private: 74 | CalcFunc mCalc; 75 | }; 76 | 77 | } // namespace SimpleService 78 | 79 | #endif // SIMPLESERVICE_SERVICE_H 80 | -------------------------------------------------------------------------------- /Chapter15/SimpleService/tests/Message.cpp: -------------------------------------------------------------------------------- 1 | #include "../Service.h" 2 | 3 | #include "SetupTeardown.h" 4 | 5 | #include 6 | 7 | using namespace MereTDD; 8 | 9 | TEST_SUITE("Calculate request can be sent", "Service 1") 10 | { 11 | std::string user = "123"; 12 | std::string path = ""; 13 | 14 | SimpleService::RequestVar request = 15 | SimpleService::CalculateRequest { 16 | .mSeed = 5 17 | }; 18 | auto const responseVar = gService1.service().handleRequest( 19 | user, path, request); 20 | auto const response = 21 | std::get_if(&responseVar); 22 | CONFIRM_TRUE(response != nullptr); 23 | } 24 | 25 | TEST_SUITE("Status request generates result", "Service 1") 26 | { 27 | std::string user = "123"; 28 | std::string path = ""; 29 | 30 | SimpleService::RequestVar calcRequest = 31 | SimpleService::CalculateRequest { 32 | .mSeed = 5 33 | }; 34 | auto responseVar = gService1.service().handleRequest( 35 | user, path, calcRequest); 36 | auto const calcResponse = 37 | std::get_if(&responseVar); 38 | CONFIRM_TRUE(calcResponse != nullptr); 39 | 40 | SimpleService::RequestVar statusRequest = 41 | SimpleService::StatusRequest { 42 | .mToken = calcResponse->mToken 43 | }; 44 | int result {0}; 45 | for (int i = 0; i < 5; ++i) 46 | { 47 | responseVar = gService1.service().handleRequest( 48 | user, path, statusRequest); 49 | auto const statusResponse = 50 | std::get_if(&responseVar); 51 | CONFIRM_TRUE(statusResponse != nullptr); 52 | 53 | if (statusResponse->mComplete) 54 | { 55 | result = statusResponse->mResult; 56 | break; 57 | } 58 | } 59 | CONFIRM_THAT(result, Equals(50)); 60 | } 61 | 62 | TEST_SUITE("Status request to test service generates result", "Service 2") 63 | { 64 | std::string user = "123"; 65 | std::string path = ""; 66 | 67 | SimpleService::RequestVar calcRequest = 68 | SimpleService::CalculateRequest { 69 | .mSeed = 5 70 | }; 71 | auto responseVar = gService2.service().handleRequest( 72 | user, path, calcRequest); 73 | auto const calcResponse = 74 | std::get_if(&responseVar); 75 | CONFIRM_TRUE(calcResponse != nullptr); 76 | 77 | // Make a status request right away before the service 78 | // is allowed to do any calculations. 79 | SimpleService::RequestVar statusRequest = 80 | SimpleService::StatusRequest { 81 | .mToken = calcResponse->mToken 82 | }; 83 | responseVar = gService2.service().handleRequest( 84 | user, path, statusRequest); 85 | auto statusResponse = 86 | std::get_if(&responseVar); 87 | CONFIRM_TRUE(statusResponse != nullptr); 88 | CONFIRM_FALSE(statusResponse->mComplete); 89 | CONFIRM_THAT(statusResponse->mProgress, Equals(0)); 90 | CONFIRM_THAT(statusResponse->mResult, Equals(0)); 91 | 92 | // Notify the service that the test has completed the first 93 | // confirmation so that the service can proceed with the 94 | // calculation. 95 | { 96 | std::lock_guard lock(SimpleService::service2Mutex); 97 | SimpleService::testReady = true; 98 | } 99 | SimpleService::testCV.notify_one(); 100 | 101 | // Now wait until the service has completed the calculation. 102 | { 103 | std::unique_lock lock(SimpleService::service2Mutex); 104 | SimpleService::testCalcCV.wait(lock, [] 105 | { 106 | return SimpleService::testCalcReady; 107 | }); 108 | } 109 | 110 | // Make another status request to get the completed result. 111 | responseVar = gService2.service().handleRequest( 112 | user, path, statusRequest); 113 | statusResponse = 114 | std::get_if(&responseVar); 115 | CONFIRM_TRUE(statusResponse != nullptr); 116 | CONFIRM_TRUE(statusResponse->mComplete); 117 | CONFIRM_THAT(statusResponse->mProgress, Equals(100)); 118 | CONFIRM_THAT(statusResponse->mResult, Equals(40)); 119 | } 120 | -------------------------------------------------------------------------------- /Chapter15/SimpleService/tests/SetupTeardown.cpp: -------------------------------------------------------------------------------- 1 | #include "SetupTeardown.h" 2 | 3 | MereTDD::TestSuiteSetupAndTeardown 4 | gService1("Calculation Service", "Service 1"); 5 | 6 | MereTDD::TestSuiteSetupAndTeardown 7 | gService2("Calculation Test Service", "Service 2"); 8 | -------------------------------------------------------------------------------- /Chapter15/SimpleService/tests/SetupTeardown.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLESERVICE_TESTS_SUITES_H 2 | #define SIMPLESERVICE_TESTS_SUITES_H 3 | 4 | #include "../Service.h" 5 | 6 | #include 7 | #include 8 | 9 | class ServiceSetup 10 | { 11 | public: 12 | void setup () 13 | { 14 | mService.start(); 15 | } 16 | 17 | void teardown () 18 | { 19 | } 20 | 21 | SimpleService::Service & service () 22 | { 23 | return mService; 24 | } 25 | 26 | private: 27 | SimpleService::Service mService; 28 | }; 29 | 30 | extern MereTDD::TestSuiteSetupAndTeardown 31 | gService1; 32 | 33 | class TestServiceSetup 34 | { 35 | public: 36 | TestServiceSetup () 37 | : mService(SimpleService::testCalc) 38 | { } 39 | 40 | void setup () 41 | { 42 | mService.start(); 43 | } 44 | 45 | void teardown () 46 | { 47 | } 48 | 49 | SimpleService::Service & service () 50 | { 51 | return mService; 52 | } 53 | 54 | private: 55 | SimpleService::Service mService; 56 | }; 57 | 58 | extern MereTDD::TestSuiteSetupAndTeardown 59 | gService2; 60 | 61 | #endif // SIMPLESERVICE_TESTS_SUITES_H 62 | -------------------------------------------------------------------------------- /Chapter15/SimpleService/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | int main () 7 | { 8 | MereMemo::FileOutput appFile("logs"); 9 | MereMemo::addLogOutput(appFile); 10 | 11 | return MereTDD::runTests(std::cout); 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Packt 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Test Driven Development with C++ 5 | 6 | Test Driven Development with C++ 7 | 8 | This is the code repository for [Test Driven Development with C++](https://www.amazon.com/Test-Driven-Development-Writing-Bug-free/dp/1803242000/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=&sr=&utm_source=github&utm_medium=repository&utm_campaign=9781804613900), published by Packt. 9 | 10 | **A simple guide to writing bug-free Agile code** 11 | 12 | ## What is this book about? 13 | Modern, standard C++ is all that is needed to create a small and practical testing framework that will improve the design of any project. This allows you to think about how the code will be used, which is the first step in designing intuitive interfaces. TDD is a modern balanced software development approach that would help create maintainable applications, provide modularity in design and write minimal code that would drastically reduce defects. With the help of this book, you’ll be able to continue adding value when designs need to change by ensuring that the changes don’t break existing tests. 14 | 15 | This book covers the following exciting features: 16 | * Understand how to develop software using TDD 17 | * Keep the code for the system as error-free as possible 18 | * Refactor and redesign code confidently 19 | * Communicate the requirements and behaviors of the code with your team 20 | * Understand the differences between unit tests and integration tests 21 | * Use TDD to create a minimal viable testing framework 22 | 23 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1803242000) today! 24 | 25 | https://www.packtpub.com/ 27 | 28 | ## Instructions and Navigations 29 | All of the code is organized into folders. For example, Chapter02. 30 | 31 | The code will look like the following: 32 | ``` 33 | TEST("Test bool confirms") 34 | { 35 | bool result = isNegative(0); 36 | CONFIRM_FALSE(result); 37 | result = isNegative(-1); 38 | CONFIRM_TRUE(result); 39 | } 40 | ``` 41 | 42 | **Following is what you need for this book:** 43 | This book is for C++ developers already familiar with and using C++ for daily tasks who want to improve their skillset. You don’t need to be an expert but you should already have some knowledge of modern C++ and how to use templates to get the most out of this book. 44 | 45 | With the following software and hardware list you can run all code files present in the book (Chapter 1-15). 46 | ### Software and Hardware List 47 | | Chapter | Software required | OS required | 48 | | -------- | ------------------------------------ | ----------------------------------- | 49 | | 1-15 | Any C++ compiler, minimally C++20 | Windows, Mac OS X, and Linux (Any) | 50 | 51 | 52 | ### Related products 53 | * C++20 STL Cookbook [[Packt]](https://www.packtpub.com/product/c20-stl-cookbook/9781803248714?utm_source=github&utm_medium=repository&utm_campaign=9781803248714) [[Amazon]](https://www.amazon.com/dp/1803248718) 54 | 55 | * Template Metaprogramming with C++ [[Packt]](https://www.packtpub.com/product/template-metaprogramming-with-c/9781803243450?utm_source=github&utm_medium=repository&utm_campaign=9781803243450) [[Amazon]](https://www.amazon.com/dp/1803243457) 56 | 57 | 58 | ## Get to Know the Author 59 | **Abdul Wahid Tanner** 60 | is the founder and an instructor at Take Up Code which teaches programmers the joys of coding through live classes, podcasts, books and more. He has authored two self-published titles, "How To Code C++ From A Simple Idea To A Game You’ll Understand!" and "The C++ Game Starter Template System". He was previously working as a software engineer at Bloomberg LP, and a Principal Software Engineer at Citrix. With over four decades of experience in the tech world, he has worked with established companies such as Dell and Microsoft in the past, and has been a speaker at CppCon. He currently resides in Texas, USA. 61 | 62 | 63 | ### Download a free PDF 64 | 65 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
66 |

https://packt.link/free-ebook/9781803242002

--------------------------------------------------------------------------------