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 }