logxpy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. logxpy/__init__.py +126 -0
  2. logxpy/_action.py +958 -0
  3. logxpy/_async.py +186 -0
  4. logxpy/_base.py +80 -0
  5. logxpy/_compat.py +71 -0
  6. logxpy/_config.py +45 -0
  7. logxpy/_dest.py +88 -0
  8. logxpy/_errors.py +58 -0
  9. logxpy/_fmt.py +68 -0
  10. logxpy/_generators.py +136 -0
  11. logxpy/_mask.py +23 -0
  12. logxpy/_message.py +195 -0
  13. logxpy/_output.py +517 -0
  14. logxpy/_pool.py +93 -0
  15. logxpy/_traceback.py +126 -0
  16. logxpy/_types.py +71 -0
  17. logxpy/_util.py +56 -0
  18. logxpy/_validation.py +486 -0
  19. logxpy/_version.py +21 -0
  20. logxpy/cli.py +61 -0
  21. logxpy/dask.py +172 -0
  22. logxpy/decorators.py +268 -0
  23. logxpy/filter.py +124 -0
  24. logxpy/journald.py +88 -0
  25. logxpy/json.py +149 -0
  26. logxpy/loggerx.py +253 -0
  27. logxpy/logwriter.py +84 -0
  28. logxpy/parse.py +191 -0
  29. logxpy/prettyprint.py +173 -0
  30. logxpy/serializers.py +36 -0
  31. logxpy/stdlib.py +23 -0
  32. logxpy/tai64n.py +45 -0
  33. logxpy/testing.py +472 -0
  34. logxpy/tests/__init__.py +9 -0
  35. logxpy/tests/common.py +36 -0
  36. logxpy/tests/strategies.py +231 -0
  37. logxpy/tests/test_action.py +1751 -0
  38. logxpy/tests/test_api.py +86 -0
  39. logxpy/tests/test_async.py +67 -0
  40. logxpy/tests/test_compat.py +13 -0
  41. logxpy/tests/test_config.py +21 -0
  42. logxpy/tests/test_coroutines.py +105 -0
  43. logxpy/tests/test_dask.py +211 -0
  44. logxpy/tests/test_decorators.py +54 -0
  45. logxpy/tests/test_filter.py +122 -0
  46. logxpy/tests/test_fmt.py +42 -0
  47. logxpy/tests/test_generators.py +292 -0
  48. logxpy/tests/test_journald.py +246 -0
  49. logxpy/tests/test_json.py +208 -0
  50. logxpy/tests/test_loggerx.py +44 -0
  51. logxpy/tests/test_logwriter.py +262 -0
  52. logxpy/tests/test_message.py +334 -0
  53. logxpy/tests/test_output.py +921 -0
  54. logxpy/tests/test_parse.py +309 -0
  55. logxpy/tests/test_pool.py +55 -0
  56. logxpy/tests/test_prettyprint.py +303 -0
  57. logxpy/tests/test_pyinstaller.py +35 -0
  58. logxpy/tests/test_serializers.py +36 -0
  59. logxpy/tests/test_stdlib.py +73 -0
  60. logxpy/tests/test_tai64n.py +66 -0
  61. logxpy/tests/test_testing.py +1051 -0
  62. logxpy/tests/test_traceback.py +251 -0
  63. logxpy/tests/test_twisted.py +814 -0
  64. logxpy/tests/test_util.py +45 -0
  65. logxpy/tests/test_validation.py +989 -0
  66. logxpy/twisted.py +265 -0
  67. logxpy-0.1.0.dist-info/METADATA +100 -0
  68. logxpy-0.1.0.dist-info/RECORD +72 -0
  69. logxpy-0.1.0.dist-info/WHEEL +5 -0
  70. logxpy-0.1.0.dist-info/entry_points.txt +2 -0
  71. logxpy-0.1.0.dist-info/licenses/LICENSE +201 -0
  72. logxpy-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1051 @@
1
+ """
2
+ Tests for L{eliot.testing}.
3
+ """
4
+
5
+ from unittest import SkipTest, TestResult, TestCase, skipUnless
6
+
7
+ try:
8
+ import numpy as np
9
+ except ImportError:
10
+ np = None
11
+
12
+ from ..testing import (
13
+ issuperset,
14
+ assertContainsFields,
15
+ LoggedAction,
16
+ LoggedMessage,
17
+ validateLogging,
18
+ UnflushedTracebacks,
19
+ assertHasMessage,
20
+ assertHasAction,
21
+ validate_logging,
22
+ capture_logging,
23
+ swap_logger,
24
+ check_for_errors,
25
+ )
26
+ from .._output import MemoryLogger
27
+ from .._action import start_action
28
+ from .._message import Message
29
+ from .._validation import ActionType, MessageType, ValidationError, Field
30
+ from .._traceback import write_traceback
31
+ from .. import add_destination, remove_destination, _output, log_message
32
+ from .common import CustomObject, CustomJSONEncoder
33
+
34
+
35
+ class IsSuperSetTests(TestCase):
36
+ """
37
+ Tests for L{issuperset}.
38
+ """
39
+
40
+ def test_equal(self):
41
+ """
42
+ Equal dictionaries are supersets of each other.
43
+ """
44
+ a = {"a": 1}
45
+ b = a.copy()
46
+ self.assertTrue(issuperset(a, b))
47
+
48
+ def test_additionalIsSuperSet(self):
49
+ """
50
+ If C{A} is C{B} plus some extra entries, C{A} is superset of C{B}.
51
+ """
52
+ a = {"a": 1, "b": 2, "c": 3}
53
+ b = {"a": 1, "c": 3}
54
+ self.assertTrue(issuperset(a, b))
55
+
56
+ def test_missingIsNotSuperSet(self):
57
+ """
58
+ If C{A} is C{B} minus some entries, C{A} is not a superset of C{B}.
59
+ """
60
+ a = {"a": 1, "c": 3}
61
+ b = {"a": 1, "b": 2, "c": 3}
62
+ self.assertFalse(issuperset(a, b))
63
+
64
+
65
+ class LoggedActionTests(TestCase):
66
+ """
67
+ Tests for L{LoggedAction}.
68
+ """
69
+
70
+ def test_values(self):
71
+ """
72
+ The values given to the L{LoggedAction} constructor are stored on it.
73
+ """
74
+ d1 = {"x": 1}
75
+ d2 = {"y": 2}
76
+ root = LoggedAction(d1, d2, [])
77
+ self.assertEqual((root.startMessage, root.endMessage), (d1, d2))
78
+
79
+ def fromMessagesIndex(self, messages, index):
80
+ """
81
+ Call L{LoggedAction.fromMessages} using action specified by index in
82
+ a list of message dictionaries.
83
+
84
+ @param messages: A C{list} of message dictionaries.
85
+
86
+ @param index: Index to the logger's messages.
87
+
88
+ @return: Result of L{LoggedAction.fromMessages}.
89
+ """
90
+ uuid = messages[index]["task_uuid"]
91
+ level = messages[index]["task_level"]
92
+ return LoggedAction.fromMessages(uuid, level, messages)
93
+
94
+ def test_fromMessagesCreatesLoggedAction(self):
95
+ """
96
+ L{LoggedAction.fromMessages} returns a L{LoggedAction}.
97
+ """
98
+ logger = MemoryLogger()
99
+ with start_action(logger, "test"):
100
+ pass
101
+ logged = self.fromMessagesIndex(logger.messages, 0)
102
+ self.assertIsInstance(logged, LoggedAction)
103
+
104
+ def test_fromMessagesStartAndSuccessfulFinish(self):
105
+ """
106
+ L{LoggedAction.fromMessages} finds the start and successful finish
107
+ messages of an action and stores them in the result.
108
+ """
109
+ logger = MemoryLogger()
110
+ Message.new(x=1).write(logger)
111
+ with start_action(logger, "test"):
112
+ Message.new(x=1).write(logger)
113
+ # Now we should have x message, start action message, another x message
114
+ # and finally finish message.
115
+ logged = self.fromMessagesIndex(logger.messages, 1)
116
+ self.assertEqual(
117
+ (logged.startMessage, logged.endMessage),
118
+ (logger.messages[1], logger.messages[3]),
119
+ )
120
+
121
+ def test_fromMessagesStartAndErrorFinish(self):
122
+ """
123
+ L{LoggedAction.fromMessages} finds the start and successful finish
124
+ messages of an action and stores them in the result.
125
+ """
126
+ logger = MemoryLogger()
127
+ try:
128
+ with start_action(logger, "test"):
129
+ raise KeyError()
130
+ except KeyError:
131
+ pass
132
+ logged = self.fromMessagesIndex(logger.messages, 0)
133
+ self.assertEqual(
134
+ (logged.startMessage, logged.endMessage),
135
+ (logger.messages[0], logger.messages[1]),
136
+ )
137
+
138
+ def test_fromMessagesStartNotFound(self):
139
+ """
140
+ L{LoggedAction.fromMessages} raises a L{ValueError} if a start message
141
+ is not found.
142
+ """
143
+ logger = MemoryLogger()
144
+ with start_action(logger, action_type="test"):
145
+ pass
146
+ self.assertRaises(ValueError, self.fromMessagesIndex, logger.messages[1:], 0)
147
+
148
+ def test_fromMessagesFinishNotFound(self):
149
+ """
150
+ L{LoggedAction.fromMessages} raises a L{ValueError} if a finish message
151
+ is not found.
152
+ """
153
+ logger = MemoryLogger()
154
+ with start_action(logger, action_type="test"):
155
+ pass
156
+ with self.assertRaises(ValueError) as cm:
157
+ self.fromMessagesIndex(logger.messages[:1], 0)
158
+ self.assertEqual(cm.exception.args[0], "Missing end message of type test")
159
+
160
+ def test_fromMessagesAddsChildMessages(self):
161
+ """
162
+ L{LoggedAction.fromMessages} adds direct child messages to the
163
+ constructed L{LoggedAction}.
164
+ """
165
+ logger = MemoryLogger()
166
+ # index 0:
167
+ Message.new(x=1).write(logger)
168
+ # index 1 - start action
169
+ with start_action(logger, "test"):
170
+ # index 2
171
+ Message.new(x=2).write(logger)
172
+ # index 3
173
+ Message.new(x=3).write(logger)
174
+ # index 4 - end action
175
+ # index 5
176
+ Message.new(x=4).write(logger)
177
+ logged = self.fromMessagesIndex(logger.messages, 1)
178
+
179
+ expectedChildren = [
180
+ LoggedMessage(logger.messages[2]),
181
+ LoggedMessage(logger.messages[3]),
182
+ ]
183
+ self.assertEqual(logged.children, expectedChildren)
184
+
185
+ def test_fromMessagesAddsChildActions(self):
186
+ """
187
+ L{LoggedAction.fromMessages} recursively adds direct child actions to
188
+ the constructed L{LoggedAction}.
189
+ """
190
+ logger = MemoryLogger()
191
+ # index 0
192
+ with start_action(logger, "test"):
193
+ # index 1:
194
+ with start_action(logger, "test2"):
195
+ # index 2
196
+ Message.new(message_type="end", x=2).write(logger)
197
+ # index 3 - end action
198
+ with start_action(logger, "test3"):
199
+ # index 4
200
+ pass
201
+ # index 5 - end action
202
+ # index 6 - end action
203
+ logged = self.fromMessagesIndex(logger.messages, 0)
204
+
205
+ self.assertEqual(logged.children[0], self.fromMessagesIndex(logger.messages, 1))
206
+ self.assertEqual(
207
+ logged.type_tree(), {"test": [{"test2": ["end"]}, {"test3": []}]}
208
+ )
209
+
210
+ def test_ofType(self):
211
+ """
212
+ L{LoggedAction.ofType} returns a list of L{LoggedAction} created by the
213
+ specified L{ActionType}.
214
+ """
215
+ ACTION = ActionType("myaction", [], [], "An action!")
216
+ logger = MemoryLogger()
217
+ # index 0
218
+ with start_action(logger, "test"):
219
+ # index 1:
220
+ with ACTION(logger):
221
+ # index 2
222
+ Message.new(x=2).write(logger)
223
+ # index 3 - end action
224
+ # index 4 - end action
225
+ # index 5
226
+ with ACTION(logger):
227
+ pass
228
+ # index 6 - end action
229
+ logged = LoggedAction.ofType(logger.messages, ACTION)
230
+ self.assertEqual(
231
+ logged,
232
+ [
233
+ self.fromMessagesIndex(logger.messages, 1),
234
+ self.fromMessagesIndex(logger.messages, 5),
235
+ ],
236
+ )
237
+
238
+ # String-variant of ofType:
239
+ logged2 = LoggedAction.ofType(logger.messages, "myaction")
240
+ self.assertEqual(logged, logged2)
241
+
242
+ def test_ofTypeNotFound(self):
243
+ """
244
+ L{LoggedAction.ofType} returns an empty list if actions of the given
245
+ type cannot be found.
246
+ """
247
+ ACTION = ActionType("myaction", [], [], "An action!")
248
+ logger = MemoryLogger()
249
+ self.assertEqual(LoggedAction.ofType(logger.messages, ACTION), [])
250
+
251
+ def test_descendants(self):
252
+ """
253
+ L{LoggedAction.descendants} returns all descendants of the
254
+ L{LoggedAction}.
255
+ """
256
+ ACTION = ActionType("myaction", [], [], "An action!")
257
+ logger = MemoryLogger()
258
+ # index 0
259
+ with ACTION(logger):
260
+ # index 1:
261
+ with start_action(logger, "test"):
262
+ # index 2
263
+ Message.new(x=2).write(logger)
264
+ # index 3 - end action
265
+ # index 4
266
+ Message.new(x=2).write(logger)
267
+ # index 5 - end action
268
+
269
+ loggedAction = LoggedAction.ofType(logger.messages, ACTION)[0]
270
+ self.assertEqual(
271
+ list(loggedAction.descendants()),
272
+ [
273
+ self.fromMessagesIndex(logger.messages, 1),
274
+ LoggedMessage(logger.messages[2]),
275
+ LoggedMessage(logger.messages[4]),
276
+ ],
277
+ )
278
+
279
+ def test_succeeded(self):
280
+ """
281
+ If the action succeeded, L{LoggedAction.succeeded} will be true.
282
+ """
283
+ logger = MemoryLogger()
284
+ with start_action(logger, "test"):
285
+ pass
286
+ logged = self.fromMessagesIndex(logger.messages, 0)
287
+ self.assertTrue(logged.succeeded)
288
+
289
+ def test_notSucceeded(self):
290
+ """
291
+ If the action failed, L{LoggedAction.succeeded} will be false.
292
+ """
293
+ logger = MemoryLogger()
294
+ try:
295
+ with start_action(logger, "test"):
296
+ raise KeyError()
297
+ except KeyError:
298
+ pass
299
+ logged = self.fromMessagesIndex(logger.messages, 0)
300
+ self.assertFalse(logged.succeeded)
301
+
302
+
303
+ class LoggedMessageTest(TestCase):
304
+ """
305
+ Tests for L{LoggedMessage}.
306
+ """
307
+
308
+ def test_values(self):
309
+ """
310
+ The values given to the L{LoggedMessage} constructor are stored on it.
311
+ """
312
+ message = {"x": 1}
313
+ logged = LoggedMessage(message)
314
+ self.assertEqual(logged.message, message)
315
+
316
+ def test_ofType(self):
317
+ """
318
+ L{LoggedMessage.ofType} returns a list of L{LoggedMessage} created by the
319
+ specified L{MessageType}.
320
+ """
321
+ MESSAGE = MessageType("mymessage", [], "A message!")
322
+ logger = MemoryLogger()
323
+ # index 0
324
+ MESSAGE().write(logger)
325
+ # index 1
326
+ Message.new(x=2).write(logger)
327
+ # index 2
328
+ MESSAGE().write(logger)
329
+ logged = LoggedMessage.ofType(logger.messages, MESSAGE)
330
+ self.assertEqual(
331
+ logged,
332
+ [LoggedMessage(logger.messages[0]), LoggedMessage(logger.messages[2])],
333
+ )
334
+
335
+ # Lookup by string type:
336
+ logged2 = LoggedMessage.ofType(logger.messages, "mymessage")
337
+ self.assertEqual(logged, logged2)
338
+
339
+ def test_ofTypeNotFound(self):
340
+ """
341
+ L{LoggedMessage.ofType} returns an empty list if messages of the given
342
+ type cannot be found.
343
+ """
344
+ MESSAGE = MessageType("mymessage", [], "A message!")
345
+ logger = MemoryLogger()
346
+ self.assertEqual(LoggedMessage.ofType(logger.messages, MESSAGE), [])
347
+
348
+
349
+ class AssertContainsFields(TestCase):
350
+ """
351
+ Tests for L{assertContainsFields}.
352
+ """
353
+
354
+ class ContainsTest(TestCase):
355
+ """
356
+ A test case that uses L{assertContainsFields}.
357
+ """
358
+
359
+ def __init__(self, message, expectedFields):
360
+ TestCase.__init__(self)
361
+ self.message = message
362
+ self.expectedFields = expectedFields
363
+
364
+ def runTest(self):
365
+ assertContainsFields(self, self.message, self.expectedFields)
366
+
367
+ def test_equal(self):
368
+ """
369
+ Equal dictionaries contain each other.
370
+ """
371
+ message = {"a": 1}
372
+ expected = message.copy()
373
+ test = self.ContainsTest(message, expected)
374
+ # No exception raised:
375
+ test.debug()
376
+
377
+ def test_additionalIsSuperSet(self):
378
+ """
379
+ If C{A} is C{B} plus some extra entries, C{A} contains the fields in
380
+ C{B}.
381
+ """
382
+ message = {"a": 1, "b": 2, "c": 3}
383
+ expected = {"a": 1, "c": 3}
384
+ test = self.ContainsTest(message, expected)
385
+ # No exception raised:
386
+ test.debug()
387
+
388
+ def test_missingFields(self):
389
+ """
390
+ If C{A} is C{B} minus some entries, C{A} does not contain the fields in
391
+ C{B}.
392
+ """
393
+ message = {"a": 1, "c": 3}
394
+ expected = {"a": 1, "b": 2, "c": 3}
395
+ test = self.ContainsTest(message, expected)
396
+ self.assertRaises(AssertionError, test.debug)
397
+
398
+ def test_differentValues(self):
399
+ """
400
+ If C{A} has a different value for a specific field than C{B}, C{A} does
401
+ not contain the fields in C{B}.
402
+ """
403
+ message = {"a": 1, "c": 3}
404
+ expected = {"a": 1, "c": 2}
405
+ test = self.ContainsTest(message, expected)
406
+ self.assertRaises(AssertionError, test.debug)
407
+
408
+
409
+ class ValidateLoggingTestsMixin(object):
410
+ """
411
+ Tests for L{validateLogging} and L{capture_logging}.
412
+ """
413
+
414
+ validate = None
415
+
416
+ def test_decoratedFunctionCalledWithMemoryLogger(self):
417
+ """
418
+ The underlying function decorated with L{validateLogging} is called with
419
+ a L{MemoryLogger} instance.
420
+ """
421
+ result = []
422
+
423
+ class MyTest(TestCase):
424
+ @self.validate(None)
425
+ def test_foo(this, logger):
426
+ result.append((this, logger.__class__))
427
+
428
+ theTest = MyTest("test_foo")
429
+ theTest.run()
430
+ self.assertEqual(result, [(theTest, MemoryLogger)])
431
+
432
+ def test_decorated_function_passthrough(self):
433
+ """
434
+ Additional arguments are passed to the underlying function.
435
+ """
436
+ result = []
437
+
438
+ def another_wrapper(f):
439
+ def g(this):
440
+ f(this, 1, 2, c=3)
441
+
442
+ return g
443
+
444
+ class MyTest(TestCase):
445
+ @another_wrapper
446
+ @self.validate(None)
447
+ def test_foo(this, a, b, logger, c=None):
448
+ result.append((a, b, c))
449
+
450
+ theTest = MyTest("test_foo")
451
+ theTest.debug()
452
+ self.assertEqual(result, [(1, 2, 3)])
453
+
454
+ def test_newMemoryLogger(self):
455
+ """
456
+ The underlying function decorated with L{validateLogging} is called with
457
+ a new L{MemoryLogger} every time the wrapper is called.
458
+ """
459
+ result = []
460
+
461
+ class MyTest(TestCase):
462
+ @self.validate(None)
463
+ def test_foo(this, logger):
464
+ result.append(logger)
465
+
466
+ theTest = MyTest("test_foo")
467
+ theTest.run()
468
+ theTest.run()
469
+ self.assertIsNot(result[0], result[1])
470
+
471
+ def test_returns(self):
472
+ """
473
+ The result of the underlying function is returned by wrapper when called.
474
+ """
475
+
476
+ class MyTest(TestCase):
477
+ @self.validate(None)
478
+ def test_foo(self, logger):
479
+ return 123
480
+
481
+ self.assertEqual(MyTest("test_foo").test_foo(), 123)
482
+
483
+ def test_raises(self):
484
+ """
485
+ The exception raised by the underlying function is passed through by the
486
+ wrapper when called.
487
+ """
488
+ exc = Exception()
489
+
490
+ class MyTest(TestCase):
491
+ @self.validate(None)
492
+ def test_foo(self, logger):
493
+ raise exc
494
+
495
+ raised = None
496
+ try:
497
+ MyTest("test_foo").debug()
498
+ except Exception as e:
499
+ raised = e
500
+ self.assertIs(exc, raised)
501
+
502
+ def test_name(self):
503
+ """
504
+ The wrapper has the same name as the wrapped function.
505
+ """
506
+
507
+ class MyTest(TestCase):
508
+ @self.validate(None)
509
+ def test_foo(self, logger):
510
+ pass
511
+
512
+ self.assertEqual(MyTest.test_foo.__name__, "test_foo")
513
+
514
+ def test_addCleanupValidate(self):
515
+ """
516
+ When a test method is decorated with L{validateLogging} it has
517
+ L{MemoryLogger.validate} registered as a test cleanup.
518
+ """
519
+ MESSAGE = MessageType("mymessage", [], "A message")
520
+
521
+ class MyTest(TestCase):
522
+ @self.validate(None)
523
+ def runTest(self, logger):
524
+ self.logger = logger
525
+ logger.write({"message_type": "wrongmessage"}, MESSAGE._serializer)
526
+
527
+ test = MyTest()
528
+ with self.assertRaises(ValidationError) as context:
529
+ test.debug()
530
+ # Some reference to the reason:
531
+ self.assertIn("wrongmessage", str(context.exception))
532
+ # Some reference to which file caused the problem:
533
+ self.assertIn("test_testing.py", str(context.exception))
534
+
535
+ def test_addCleanupTracebacks(self):
536
+ """
537
+ When a test method is decorated with L{validateLogging} it has has a
538
+ check unflushed tracebacks in the L{MemoryLogger} registered as a
539
+ test cleanup.
540
+ """
541
+
542
+ class MyTest(TestCase):
543
+ @self.validate(None)
544
+ def runTest(self, logger):
545
+ try:
546
+ 1 / 0
547
+ except ZeroDivisionError:
548
+ write_traceback(logger)
549
+
550
+ test = MyTest()
551
+ self.assertRaises(UnflushedTracebacks, test.debug)
552
+
553
+ def test_assertion(self):
554
+ """
555
+ If a callable is passed to L{validateLogging}, it is called with the
556
+ L{TestCase} instance and the L{MemoryLogger} passed to the test
557
+ method.
558
+ """
559
+ result = []
560
+
561
+ class MyTest(TestCase):
562
+ def assertLogging(self, logger):
563
+ result.append((self, logger))
564
+
565
+ @self.validate(assertLogging)
566
+ def runTest(self, logger):
567
+ self.logger = logger
568
+
569
+ test = MyTest()
570
+ test.run()
571
+ self.assertEqual(result, [(test, test.logger)])
572
+
573
+ def test_assertionArguments(self):
574
+ """
575
+ If a callable together with additional arguments and keyword arguments are
576
+ passed to L{validateLogging}, the callable is called with the additional
577
+ args and kwargs.
578
+ """
579
+ result = []
580
+
581
+ class MyTest(TestCase):
582
+ def assertLogging(self, logger, x, y):
583
+ result.append((self, logger, x, y))
584
+
585
+ @self.validate(assertLogging, 1, y=2)
586
+ def runTest(self, logger):
587
+ self.logger = logger
588
+
589
+ test = MyTest()
590
+ test.run()
591
+ self.assertEqual(result, [(test, test.logger, 1, 2)])
592
+
593
+ def test_assertionAfterTest(self):
594
+ """
595
+ If a callable is passed to L{validateLogging}, it is called with the
596
+ after the main test code has run, allowing it to make assertions
597
+ about log messages from the test.
598
+ """
599
+
600
+ class MyTest(TestCase):
601
+ def assertLogging(self, logger):
602
+ self.result.append(2)
603
+
604
+ @self.validate(assertLogging)
605
+ def runTest(self, logger):
606
+ self.result = [1]
607
+
608
+ test = MyTest()
609
+ test.run()
610
+ self.assertEqual(test.result, [1, 2])
611
+
612
+ def test_assertionBeforeTracebackCleanup(self):
613
+ """
614
+ If a callable is passed to L{validateLogging}, it is called with the
615
+ before the check for unflushed tracebacks, allowing it to flush
616
+ traceback log messages.
617
+ """
618
+
619
+ class MyTest(TestCase):
620
+ def assertLogging(self, logger):
621
+ logger.flushTracebacks(ZeroDivisionError)
622
+ self.flushed = True
623
+
624
+ @self.validate(assertLogging)
625
+ def runTest(self, logger):
626
+ self.flushed = False
627
+ try:
628
+ 1 / 0
629
+ except ZeroDivisionError:
630
+ write_traceback(logger)
631
+
632
+ test = MyTest()
633
+ test.run()
634
+ self.assertTrue(test.flushed)
635
+
636
+
637
+ class ValidateLoggingTests(ValidateLoggingTestsMixin, TestCase):
638
+ """
639
+ Tests for L{validate_logging}.
640
+ """
641
+
642
+ validate = staticmethod(validate_logging)
643
+
644
+
645
+ class CaptureLoggingTests(ValidateLoggingTestsMixin, TestCase):
646
+ """
647
+ Tests for L{capture_logging}.
648
+ """
649
+
650
+ validate = staticmethod(capture_logging)
651
+
652
+ def setUp(self):
653
+ # Since we're not always calling the test method via the TestCase
654
+ # infrastructure, sometimes cleanup methods are not called. This
655
+ # means the original default logger is not restored. So we do so
656
+ # manually. If the issue is a bug in capture_logging itself the
657
+ # tests below will catch that.
658
+ original_logger = _output._DEFAULT_LOGGER
659
+
660
+ def cleanup():
661
+ _output._DEFAULT_LOGGER = original_logger
662
+
663
+ self.addCleanup(cleanup)
664
+
665
+ def test_default_logger(self):
666
+ """
667
+ L{capture_logging} captures messages from logging that
668
+ doesn't specify a L{Logger}.
669
+ """
670
+
671
+ class MyTest(TestCase):
672
+ @capture_logging(None)
673
+ def runTest(self, logger):
674
+ Message.log(some_key=1234)
675
+ self.logger = logger
676
+
677
+ test = MyTest()
678
+ test.run()
679
+ self.assertEqual(test.logger.messages[0]["some_key"], 1234)
680
+
681
+ def test_global_cleanup(self):
682
+ """
683
+ After the function wrapped with L{capture_logging} finishes,
684
+ logging that doesn't specify a logger is logged normally.
685
+ """
686
+
687
+ class MyTest(TestCase):
688
+ @capture_logging(None)
689
+ def runTest(self, logger):
690
+ pass
691
+
692
+ test = MyTest()
693
+ test.run()
694
+ messages = []
695
+ add_destination(messages.append)
696
+ self.addCleanup(remove_destination, messages.append)
697
+ Message.log(some_key=1234)
698
+ self.assertEqual(messages[0]["some_key"], 1234)
699
+
700
+ def test_global_cleanup_exception(self):
701
+ """
702
+ If the function wrapped with L{capture_logging} throws an exception,
703
+ logging that doesn't specify a logger is logged normally.
704
+ """
705
+
706
+ class MyTest(TestCase):
707
+ @capture_logging(None)
708
+ def runTest(self, logger):
709
+ raise RuntimeError()
710
+
711
+ test = MyTest()
712
+ test.run()
713
+ messages = []
714
+ add_destination(messages.append)
715
+ self.addCleanup(remove_destination, messages.append)
716
+ Message.log(some_key=1234)
717
+ self.assertEqual(messages[0]["some_key"], 1234)
718
+
719
+ def test_validationNotRunForSkip(self):
720
+ """
721
+ If the decorated test raises L{SkipTest} then the logging validation is
722
+ also skipped.
723
+ """
724
+
725
+ class MyTest(TestCase):
726
+ recorded = False
727
+
728
+ def record(self, logger):
729
+ self.recorded = True
730
+
731
+ @validateLogging(record)
732
+ def runTest(self, logger):
733
+ raise SkipTest("Do not run this test.")
734
+
735
+ test = MyTest()
736
+ result = TestResult()
737
+ test.run(result)
738
+
739
+ # Verify that the validation function did not run and that the test was
740
+ # nevertheless marked as a skip with the correct reason.
741
+ self.assertEqual(
742
+ (test.recorded, result.skipped, result.errors, result.failures),
743
+ (False, [(test, "Do not run this test.")], [], []),
744
+ )
745
+
746
+
747
+ class JSONEncodingTests(TestCase):
748
+ """Tests for L{capture_logging} JSON encoder support."""
749
+
750
+ @skipUnless(np, "NumPy is not installed.")
751
+ @capture_logging(None)
752
+ def test_default_JSON_encoder(self, logger):
753
+ """
754
+ L{capture_logging} validates using L{EliotJSONEncoder} by default.
755
+ """
756
+ # Default JSON encoder can't handle NumPy:
757
+ log_message(message_type="hello", number=np.uint32(12))
758
+
759
+ @capture_logging(None, encoder_=CustomJSONEncoder)
760
+ def test_custom_JSON_encoder(self, logger):
761
+ """
762
+ L{capture_logging} can be called with a custom JSON encoder, which is then
763
+ used for validation.
764
+ """
765
+ # Default JSON encoder can't handle this custom object:
766
+ log_message(message_type="hello", object=CustomObject())
767
+
768
+
769
+ MESSAGE1 = MessageType(
770
+ "message1", [Field.forTypes("x", [int], "A number")], "A message for testing."
771
+ )
772
+ MESSAGE2 = MessageType("message2", [], "A message for testing.")
773
+
774
+
775
+ class AssertHasMessageTests(TestCase):
776
+ """
777
+ Tests for L{assertHasMessage}.
778
+ """
779
+
780
+ class UnitTest(TestCase):
781
+ """
782
+ Test case that can be instantiated.
783
+ """
784
+
785
+ def runTest(self):
786
+ pass
787
+
788
+ def test_failIfNoMessagesOfType(self):
789
+ """
790
+ L{assertHasMessage} raises L{AssertionError} if the given L{MemoryLogger}
791
+ has no messages of the given L{MessageType}.
792
+ """
793
+ test = self.UnitTest()
794
+ logger = MemoryLogger()
795
+ MESSAGE1(x=123).write(logger)
796
+ self.assertRaises(AssertionError, assertHasMessage, test, logger, MESSAGE2)
797
+
798
+ def test_returnsIfMessagesOfType(self):
799
+ """
800
+ L{assertHasMessage} returns the first message of the given L{MessageType}.
801
+ """
802
+ test = self.UnitTest()
803
+ logger = MemoryLogger()
804
+ MESSAGE1(x=123).write(logger)
805
+ self.assertEqual(
806
+ assertHasMessage(test, logger, MESSAGE1),
807
+ LoggedMessage.ofType(logger.messages, MESSAGE1)[0],
808
+ )
809
+
810
+ def test_failIfNotSubset(self):
811
+ """
812
+ L{assertHasMessage} raises L{AssertionError} if the found message doesn't
813
+ contain the given fields.
814
+ """
815
+ test = self.UnitTest()
816
+ logger = MemoryLogger()
817
+ MESSAGE1(x=123).write(logger)
818
+ self.assertRaises(
819
+ AssertionError, assertHasMessage, test, logger, MESSAGE1, {"x": 24}
820
+ )
821
+
822
+ def test_returnsIfSubset(self):
823
+ """
824
+ L{assertHasMessage} returns the first message of the given L{MessageType} if
825
+ it contains the given fields.
826
+ """
827
+ test = self.UnitTest()
828
+ logger = MemoryLogger()
829
+ MESSAGE1(x=123).write(logger)
830
+ self.assertEqual(
831
+ assertHasMessage(test, logger, MESSAGE1, {"x": 123}),
832
+ LoggedMessage.ofType(logger.messages, MESSAGE1)[0],
833
+ )
834
+
835
+
836
+ ACTION1 = ActionType(
837
+ "action1",
838
+ [Field.forTypes("x", [int], "A number")],
839
+ [Field.forTypes("result", [int], "A number")],
840
+ "A action for testing.",
841
+ )
842
+ ACTION2 = ActionType("action2", [], [], "A action for testing.")
843
+
844
+
845
+ class AssertHasActionTests(TestCase):
846
+ """
847
+ Tests for L{assertHasAction}.
848
+ """
849
+
850
+ class UnitTest(TestCase):
851
+ """
852
+ Test case that can be instantiated.
853
+ """
854
+
855
+ def runTest(self):
856
+ pass
857
+
858
+ def test_failIfNoActionsOfType(self):
859
+ """
860
+ L{assertHasAction} raises L{AssertionError} if the given L{MemoryLogger}
861
+ has no actions of the given L{ActionType}.
862
+ """
863
+ test = self.UnitTest()
864
+ logger = MemoryLogger()
865
+ with ACTION1(logger, x=123):
866
+ pass
867
+ self.assertRaises(AssertionError, assertHasAction, test, logger, ACTION2, True)
868
+
869
+ def test_failIfWrongSuccessStatus(self):
870
+ """
871
+ L{assertHasAction} raises L{AssertionError} if the given success status does
872
+ not match that of the found actions.
873
+ """
874
+ test = self.UnitTest()
875
+ logger = MemoryLogger()
876
+ with ACTION1(logger, x=123):
877
+ pass
878
+ try:
879
+ with ACTION2(logger):
880
+ 1 / 0
881
+ except ZeroDivisionError:
882
+ pass
883
+ self.assertRaises(AssertionError, assertHasAction, test, logger, ACTION1, False)
884
+ self.assertRaises(AssertionError, assertHasAction, test, logger, ACTION2, True)
885
+
886
+ def test_returnsIfMessagesOfType(self):
887
+ """
888
+ A successful L{assertHasAction} returns the first message of the given
889
+ L{ActionType}.
890
+ """
891
+ test = self.UnitTest()
892
+ logger = MemoryLogger()
893
+ with ACTION1(logger, x=123):
894
+ pass
895
+ self.assertEqual(
896
+ assertHasAction(test, logger, ACTION1, True),
897
+ LoggedAction.ofType(logger.messages, ACTION1)[0],
898
+ )
899
+
900
+ def test_failIfNotStartSubset(self):
901
+ """
902
+ L{assertHasAction} raises L{AssertionError} if the found action doesn't
903
+ contain the given start fields.
904
+ """
905
+ test = self.UnitTest()
906
+ logger = MemoryLogger()
907
+ with ACTION1(logger, x=123):
908
+ pass
909
+ self.assertRaises(
910
+ AssertionError, assertHasAction, test, logger, ACTION1, True, {"x": 24}
911
+ )
912
+
913
+ def test_failIfNotEndSubset(self):
914
+ """
915
+ L{assertHasAction} raises L{AssertionError} if the found action doesn't
916
+ contain the given end fields.
917
+ """
918
+ test = self.UnitTest()
919
+ logger = MemoryLogger()
920
+ with ACTION1(logger, x=123) as act:
921
+ act.addSuccessFields(result=5)
922
+ self.assertRaises(
923
+ AssertionError,
924
+ assertHasAction,
925
+ test,
926
+ logger,
927
+ ACTION1,
928
+ True,
929
+ startFields={"x": 123},
930
+ endFields={"result": 24},
931
+ )
932
+
933
+ def test_returns(self):
934
+ """
935
+ A successful L{assertHasAction} returns the first message of the given
936
+ L{ActionType} after doing all validation.
937
+ """
938
+ test = self.UnitTest()
939
+ logger = MemoryLogger()
940
+ with ACTION1(logger, x=123) as act:
941
+ act.addSuccessFields(result=5)
942
+ self.assertEqual(
943
+ assertHasAction(test, logger, ACTION1, True, {"x": 123}, {"result": 5}),
944
+ LoggedAction.ofType(logger.messages, ACTION1)[0],
945
+ )
946
+
947
+
948
+ class PEP8Tests(TestCase):
949
+ """
950
+ Tests for PEP 8 method compatibility.
951
+ """
952
+
953
+ def test_LoggedAction_from_messages(self):
954
+ """
955
+ L{LoggedAction.from_messages} is the same as
956
+ L{LoggedAction.fromMessages}.
957
+ """
958
+ self.assertEqual(LoggedAction.from_messages, LoggedAction.fromMessages)
959
+
960
+ def test_LoggedAction_of_type(self):
961
+ """
962
+ L{LoggedAction.of_type} is the same as
963
+ L{LoggedAction.ofType}.
964
+ """
965
+ self.assertEqual(LoggedAction.of_type, LoggedAction.ofType)
966
+
967
+ def test_LoggedAction_end_message(self):
968
+ """
969
+ L{LoggedAction.end_message} is the same as L{LoggedAction.endMessage}.
970
+ """
971
+ action = LoggedAction({1: 2}, {3: 4}, [])
972
+ self.assertEqual(action.end_message, action.endMessage)
973
+
974
+ def test_LoggedAction_start_message(self):
975
+ """
976
+ L{LoggedAction.start_message} is the same as
977
+ L{LoggedAction.startMessage}.
978
+ """
979
+ action = LoggedAction({1: 2}, {3: 4}, [])
980
+ self.assertEqual(action.start_message, action.startMessage)
981
+
982
+ def test_LoggedMessage_of_type(self):
983
+ """
984
+ L{LoggedMessage.of_type} is the same as
985
+ L{LoggedMessage.ofType}.
986
+ """
987
+ self.assertEqual(LoggedMessage.of_type, LoggedMessage.ofType)
988
+
989
+ def test_validate_logging(self):
990
+ """
991
+ L{validate_logging} is the same as L{validateLogging}.
992
+ """
993
+ self.assertEqual(validate_logging, validateLogging)
994
+
995
+
996
+ class LowLevelTestingHooks(TestCase):
997
+ """Tests for lower-level APIs for setting up MemoryLogger."""
998
+
999
+ @capture_logging(None)
1000
+ def test_swap_logger(self, logger):
1001
+ """C{swap_logger} swaps out the current logger."""
1002
+ new_logger = MemoryLogger()
1003
+ old_logger = swap_logger(new_logger)
1004
+ Message.log(message_type="hello")
1005
+
1006
+ # We swapped out old logger for new:
1007
+ self.assertIs(old_logger, logger)
1008
+ self.assertEqual(new_logger.messages[0]["message_type"], "hello")
1009
+
1010
+ # Now restore old logger:
1011
+ intermediate_logger = swap_logger(old_logger)
1012
+ Message.log(message_type="goodbye")
1013
+ self.assertIs(intermediate_logger, new_logger)
1014
+ self.assertEqual(logger.messages[0]["message_type"], "goodbye")
1015
+
1016
+ def test_check_for_errors_unflushed_tracebacks(self):
1017
+ """C{check_for_errors} raises on unflushed tracebacks."""
1018
+ logger = MemoryLogger()
1019
+
1020
+ # No errors initially:
1021
+ check_for_errors(logger)
1022
+
1023
+ try:
1024
+ 1 / 0
1025
+ except ZeroDivisionError:
1026
+ write_traceback(logger)
1027
+ logger.flush_tracebacks(ZeroDivisionError)
1028
+
1029
+ # Flushed tracebacks don't count:
1030
+ check_for_errors(logger)
1031
+
1032
+ # But unflushed tracebacks do:
1033
+ try:
1034
+ raise RuntimeError
1035
+ except RuntimeError:
1036
+ write_traceback(logger)
1037
+ with self.assertRaises(UnflushedTracebacks):
1038
+ check_for_errors(logger)
1039
+
1040
+ def test_check_for_errors_validation(self):
1041
+ """C{check_for_errors} raises on validation errors."""
1042
+ logger = MemoryLogger()
1043
+ logger.write({"x": 1, "message_type": "mem"})
1044
+
1045
+ # No errors:
1046
+ check_for_errors(logger)
1047
+
1048
+ # Now long something unserializable to JSON:
1049
+ logger.write({"message_type": object()})
1050
+ with self.assertRaises(TypeError):
1051
+ check_for_errors(logger)