1 /** 2 * An SLF4D provider that is intended to be used for testing. The provider 3 * class, `TestingLoggingProvider`, includes some convenience methods to 4 * inspect the log messages generated while it's active. 5 */ 6 module slf4d.testing_provider; 7 8 import slf4d.factory; 9 import slf4d.provider; 10 import slf4d.level; 11 import slf4d.logger; 12 import slf4d.handler : CachingLogHandler; 13 14 /** 15 * A convenient logging provider to use in unit tests, which uses a factory 16 * that creates Loggers that send messages to a `CachingLogHandler` for 17 * inspection. 18 */ 19 shared class TestingLoggingProvider : LoggingProvider { 20 /** 21 * The logger factory that this provider uses. 22 */ 23 public TestingLoggerFactory factory; 24 25 public this() { 26 this.factory = new TestingLoggerFactory(); 27 } 28 29 public TestingLoggerFactory getLoggerFactory() shared { 30 return this.factory; 31 } 32 33 /** 34 * Convenience method to get the list of log messages that have been logged 35 * to this provider since the last time it was reset. 36 * Returns: The messages that have been logged to this provider. 37 */ 38 public LogMessage[] messages() { 39 return this.factory.handler.getMessages(); 40 } 41 42 /** 43 * Convenience method to clear this provider's cached list of messages. 44 */ 45 public void reset() { 46 this.factory.handler.reset(); 47 } 48 49 /** 50 * Gets the number of messages that have been logged. 51 * Returns: The number of messages that have been logged. 52 */ 53 public size_t messageCount() { 54 return this.factory.handler.messageCount; 55 } 56 57 /** 58 * Gets the number of messages that have been logged at a given level. 59 * Params: 60 * levelFilter = The level to filter by. 61 * Returns: The number of messages that have been logged at the given level. 62 */ 63 public size_t messageCount(Level levelFilter) { 64 import std.algorithm : count; 65 return cast(size_t) this.messages().count!(m => m.level == levelFilter); 66 } 67 68 /** 69 * Asserts that this provider has exactly an expected amount of messages. 70 * Params: 71 * expected = The expected message count. 72 */ 73 public void assertMessageCount(size_t expected) { 74 import std.format : format; 75 size_t actual = this.messageCount(); 76 assert(actual == expected, format!"Actual message count %d does not match expected %d."(actual, expected)); 77 } 78 79 /** 80 * Asserts that this provider has exactly an expected amount of messages 81 * at the given logging level. 82 * Params: 83 * level = The level to filter by. 84 * expected = The expected message count. 85 */ 86 public void assertMessageCount(Level level, size_t expected) { 87 import std.format : format; 88 size_t actual = this.messageCount(level); 89 assert( 90 actual == expected, 91 format!"Actual message count %d for level %s does not match expected %d."(actual, level.name, expected) 92 ); 93 } 94 95 /** 96 * Asserts that this provider has no cached messages. 97 */ 98 public void assertNoMessages() { 99 this.assertMessageCount(0); 100 } 101 102 /** 103 * Asserts that this provider has no cached messages at the given logging 104 * level. 105 * Params: 106 * level = The level to filter by. 107 */ 108 public void assertNoMessages(Level level) { 109 this.assertMessageCount(level, 0); 110 } 111 112 /** 113 * Asserts that this provider has a cached log message that satisfies the 114 * given boolean delegate function. 115 * Params: 116 * dg = A delegate function that takes a log message, and returns true if 117 * the message matches, or false otherwise. 118 * message = The message to show if no matching log messages are found. 119 */ 120 public void assertHasMessage( 121 bool delegate(LogMessage) dg, 122 string message = "No matching log message for delegate function." 123 ) { 124 import std.algorithm : any; 125 assert(any!(m => dg(m))(this.messages()), message); 126 } 127 128 /** 129 * Asserts that this provider has a cached log message with the given 130 * string message. 131 * Params: 132 * expected = The expected string message. 133 * caseSensitive = Whether to do a case-sensitive search. True by default. 134 */ 135 public void assertHasMessage(string expected, bool caseSensitive = true) { 136 import std.format : format; 137 import std.string : toLower; 138 this.assertHasMessage( 139 (m) { 140 if (!caseSensitive) { 141 return toLower(m.message) == toLower(expected); 142 } 143 return m.message == expected; 144 }, 145 format!"Cached log messages do not contain expected message \"%s\"."(expected) 146 ); 147 } 148 149 /** 150 * Asserts that this provider has a cached log message with the given level. 151 * Params: 152 * level = The logging level to look for. 153 */ 154 public void assertHasMessage(Level level) { 155 import std.format : format; 156 this.assertHasMessage( 157 m => m.level == level, 158 format!"No cached log message with level %s."(level.name) 159 ); 160 } 161 } 162 163 /** 164 * A convenient LoggerFactory implementation for testing logging in an isolated 165 * manner. 166 */ 167 shared class TestingLoggerFactory : LoggerFactory { 168 public CachingLogHandler handler; 169 public Level logLevel = Levels.TRACE; 170 171 public this() { 172 this.handler = new CachingLogHandler(); 173 } 174 175 Logger getLogger(string name = __MODULE__) shared { 176 return Logger(this.handler, this.logLevel, name); 177 } 178 } 179 180 unittest { 181 import slf4d; 182 183 auto p = new TestingLoggingProvider(); 184 assert(p.messages.length == 0); 185 p.assertMessageCount(0); 186 p.assertNoMessages(); 187 188 auto log = p.getLoggerFactory().getLogger(); 189 log.info("Testing"); 190 assert(p.messages.length == 1); 191 p.assertMessageCount(1); 192 p.assertMessageCount(Levels.INFO, 1); 193 p.assertMessageCount(Levels.WARN, 0); 194 p.assertNoMessages(Levels.ERROR); 195 p.assertHasMessage("Testing"); 196 p.assertHasMessage("testing", false); 197 p.reset(); 198 assert(p.messages.length == 0); 199 }