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,989 @@
1
+ """
2
+ Tests for L{eliot._validation}.
3
+ """
4
+
5
+ from unittest import TestCase
6
+
7
+ from .._validation import (
8
+ Field,
9
+ MessageType,
10
+ ActionType,
11
+ ValidationError,
12
+ fields,
13
+ _MessageSerializer,
14
+ )
15
+ from .._action import start_action, startTask
16
+ from .._output import MemoryLogger
17
+ from ..serializers import identity
18
+ from .. import add_destination, remove_destination
19
+
20
+
21
+ class TypedFieldTests(TestCase):
22
+ """
23
+ Tests for L{Field.forTypes}.
24
+ """
25
+
26
+ def test_validateCorrectType(self):
27
+ """
28
+ L{Field.validate} will not raise an exception if the given value is in
29
+ the list of supported classes.
30
+ """
31
+ field = Field.forTypes("path", [str, int], "A path!")
32
+ field.validate(123)
33
+ field.validate("hello")
34
+
35
+ def test_validateNone(self):
36
+ """
37
+ When given a "class" of C{None}, L{Field.validate} will support
38
+ validating C{None}.
39
+ """
40
+ field = Field.forTypes("None", [None], "Nothing!")
41
+ field.validate(None)
42
+
43
+ def test_validateWrongType(self):
44
+ """
45
+ L{Field.validate} will raise a L{ValidationError} exception if the
46
+ given value's type is not in the list of supported classes.
47
+ """
48
+ field = Field.forTypes("key", [int], "An integer key")
49
+ self.assertRaises(ValidationError, field.validate, "lala")
50
+ self.assertRaises(ValidationError, field.validate, None)
51
+ self.assertRaises(ValidationError, field.validate, object())
52
+
53
+ def test_extraValidatorPasses(self):
54
+ """
55
+ L{Field.validate} will not raise an exception if the extra validator
56
+ does not raise an exception.
57
+ """
58
+
59
+ def validate(i):
60
+ if i > 10:
61
+ return
62
+ else:
63
+ raise ValidationError("too small")
64
+
65
+ field = Field.forTypes("key", [int], "An integer key", validate)
66
+ field.validate(11)
67
+
68
+ def test_extraValidatorFails(self):
69
+ """
70
+ L{Field.validate} will raise a L{ValidationError} exception if the
71
+ extra validator raises one.
72
+ """
73
+
74
+ def validate(i):
75
+ if i > 10:
76
+ return
77
+ else:
78
+ raise ValidationError("too small")
79
+
80
+ field = Field.forTypes("key", [int], "An int", validate)
81
+ self.assertRaises(ValidationError, field.validate, 10)
82
+
83
+ def test_onlyValidTypes(self):
84
+ """
85
+ Only JSON supported types can be passed to L{Field.forTypes}.
86
+ """
87
+ self.assertRaises(TypeError, Field.forTypes, "key", [complex], "Oops")
88
+
89
+ def test_listIsValidType(self):
90
+ """
91
+ A C{list} is a valid type for L{Field.forTypes}.
92
+ """
93
+ Field.forTypes("key", [list], "Oops")
94
+
95
+ def test_dictIsValidType(self):
96
+ """
97
+ A C{dict} is a valid type for L{Field.forTypes}.
98
+ """
99
+ Field.forTypes("key", [dict], "Oops")
100
+
101
+
102
+ class FieldTests(TestCase):
103
+ """
104
+ Tests for L{Field}.
105
+ """
106
+
107
+ def test_description(self):
108
+ """
109
+ L{Field.description} stores the passed in description.
110
+ """
111
+ field = Field("path", identity, "A path!")
112
+ self.assertEqual(field.description, "A path!")
113
+
114
+ def test_optionalDescription(self):
115
+ """
116
+ L{Field} can be constructed with no description.
117
+ """
118
+ field = Field("path", identity)
119
+ self.assertEqual(field.description, "")
120
+
121
+ def test_key(self):
122
+ """
123
+ L{Field.key} stores the passed in field key.
124
+ """
125
+ field = Field("path", identity, "A path!")
126
+ self.assertEqual(field.key, "path")
127
+
128
+ def test_serialize(self):
129
+ """
130
+ L{Field.serialize} calls the given serializer function.
131
+ """
132
+ result = []
133
+ Field("key", result.append, "field").serialize(123)
134
+ self.assertEqual(result, [123])
135
+
136
+ def test_serializeResult(self):
137
+ """
138
+ L{Field.serialize} returns the result of the given serializer function.
139
+ """
140
+ result = Field("key", lambda obj: 456, "field").serialize(None)
141
+ self.assertEqual(result, 456)
142
+
143
+ def test_serializeCallsValidate(self):
144
+ """
145
+ L{Field.validate} calls the serializer, in case that raises an
146
+ exception for the given input.
147
+ """
148
+
149
+ class MyException(Exception):
150
+ pass
151
+
152
+ def serialize(obj):
153
+ raise MyException()
154
+
155
+ field = Field("key", serialize, "")
156
+ self.assertRaises(MyException, field.validate, 123)
157
+
158
+ def test_noExtraValidator(self):
159
+ """
160
+ L{Field.validate} doesn't break if there is no extra validator.
161
+ """
162
+ field = Field("key", identity, "")
163
+ field.validate(123)
164
+
165
+ def test_extraValidatorPasses(self):
166
+ """
167
+ L{Field.validate} will not raise an exception if the extra validator
168
+ does not raise an exception.
169
+ """
170
+
171
+ def validate(i):
172
+ if i > 10:
173
+ return
174
+ else:
175
+ raise ValidationError("too small")
176
+
177
+ field = Field("path", identity, "A path!", validate)
178
+ field.validate(11)
179
+
180
+ def test_extraValidatorFails(self):
181
+ """
182
+ L{Field.validate} will raise a L{ValidationError} exception if the
183
+ extra validator raises one.
184
+ """
185
+
186
+ def validate(i):
187
+ if i > 10:
188
+ return
189
+ else:
190
+ raise ValidationError("too small")
191
+
192
+ field = Field("path", identity, "A path!", validate)
193
+ self.assertRaises(ValidationError, field.validate, 10)
194
+
195
+
196
+ class FieldForValueTests(TestCase):
197
+ """
198
+ Tests for L{Field.forValue}.
199
+ """
200
+
201
+ def test_forValue(self):
202
+ """
203
+ L{Field.forValue} creates a L{Field} with the given key and description.
204
+ """
205
+ field = Field.forValue("key", None, "description")
206
+ self.assertEqual(field.key, "key")
207
+ self.assertEqual(field.description, "description")
208
+
209
+ def test_forValueGoodValue(self):
210
+ """
211
+ The L{Field.forValue}-created L{Field} validates the value it was
212
+ constructed with.
213
+ """
214
+ field = Field.forValue("key", 1234, "description")
215
+ field.validate(1234)
216
+
217
+ def test_valueFieldWrongValue(self):
218
+ """
219
+ The L{Field.forValue}-created L{Field} raises a L{ValidationError} for
220
+ different values.
221
+ """
222
+ field = Field.forValue("key", 1234, "description")
223
+ self.assertRaises(ValidationError, field.validate, 5678)
224
+
225
+ def test_serialize(self):
226
+ """
227
+ The L{Field.forValue}-created L{Field} returns the given object when
228
+ serializing, regardless of input.
229
+
230
+ If the caller is buggy, no need to log garbage if we know what needs
231
+ logging. These bugs will be caught by unit tests, anyway, if author of
232
+ code is doing things correctly.
233
+ """
234
+ field = Field.forValue("key", 1234, "description")
235
+ self.assertEqual(field.serialize(None), 1234)
236
+
237
+
238
+ class FieldsTests(TestCase):
239
+ """
240
+ Tests for L{fields}.
241
+ """
242
+
243
+ def test_positional(self):
244
+ """
245
+ L{fields} accepts positional arguments of L{Field} instances and
246
+ combines them with fields specied as keyword arguments.
247
+ """
248
+ a_field = Field("akey", identity)
249
+ l = fields(a_field, another=str)
250
+ self.assertIn(a_field, l)
251
+ self.assertEqual(
252
+ {(type(field), field.key) for field in l},
253
+ {(Field, "akey"), (Field, "another")},
254
+ )
255
+
256
+ def test_keys(self):
257
+ """
258
+ L{fields} creates L{Field} instances with the given keys.
259
+ """
260
+ l = fields(key=int, status=str)
261
+ self.assertEqual(
262
+ {(type(field), field.key) for field in l},
263
+ {(Field, "key"), (Field, "status")},
264
+ )
265
+
266
+ def test_validTypes(self):
267
+ """
268
+ The L{Field} instances constructed by L{fields} validate the specified
269
+ types.
270
+ """
271
+ (field,) = fields(key=int)
272
+ self.assertRaises(ValidationError, field.validate, "abc")
273
+
274
+ def test_noSerialization(self):
275
+ """
276
+ The L{Field} instances constructed by L{fields} do no special
277
+ serialization.
278
+ """
279
+ (field,) = fields(key=int)
280
+ self.assertEqual(field.serialize("abc"), "abc")
281
+
282
+
283
+ class MessageSerializerTests(TestCase):
284
+ """
285
+ Tests for L{_MessageSerializer}.
286
+ """
287
+
288
+ def test_noMultipleFields(self):
289
+ """
290
+ L{_MessageSerializer.__init__} will raise a L{ValueError} exception if
291
+ constructed with more than object per field name.
292
+ """
293
+ self.assertRaises(
294
+ ValueError,
295
+ _MessageSerializer,
296
+ [
297
+ Field("akey", identity, ""),
298
+ Field("akey", identity, ""),
299
+ Field("message_type", identity, ""),
300
+ ],
301
+ )
302
+
303
+ def test_noBothTypeFields(self):
304
+ """
305
+ L{_MessageSerializer.__init__} will raise a L{ValueError} exception if
306
+ constructed with both a C{"message_type"} and C{"action_type"} field.
307
+ """
308
+ self.assertRaises(
309
+ ValueError,
310
+ _MessageSerializer,
311
+ [Field("message_type", identity, ""), Field("action_type", identity, "")],
312
+ )
313
+
314
+ def test_missingTypeField(self):
315
+ """
316
+ L{_MessageSerializer.__init__} will raise a L{ValueError} if there is
317
+ neither a C{"message_type"} nor a C{"action_type"} field.
318
+ """
319
+ self.assertRaises(ValueError, _MessageSerializer, [])
320
+
321
+ def test_noTaskLevel(self):
322
+ """
323
+ L{_MessageSerializer.__init__} will raise a L{ValueError} if there is
324
+ a C{"task_level"} field included.
325
+ """
326
+ self.assertRaises(
327
+ ValueError,
328
+ _MessageSerializer,
329
+ [Field("message_type", identity, ""), Field("task_level", identity, "")],
330
+ )
331
+
332
+ def test_noTaskUuid(self):
333
+ """
334
+ L{_MessageSerializer.__init__} will raise a L{ValueError} if there is
335
+ a C{"task_uuid"} field included.
336
+ """
337
+ self.assertRaises(
338
+ ValueError,
339
+ _MessageSerializer,
340
+ [Field("message_type", identity, ""), Field("task_uuid", identity, "")],
341
+ )
342
+
343
+ def test_noTimestamp(self):
344
+ """
345
+ L{_MessageSerializer.__init__} will raise a L{ValueError} if there is
346
+ a C{"timestamp"} field included.
347
+ """
348
+ self.assertRaises(
349
+ ValueError,
350
+ _MessageSerializer,
351
+ [Field("message_type", identity, ""), Field("timestamp", identity, "")],
352
+ )
353
+
354
+ def test_noUnderscoreStart(self):
355
+ """
356
+ L{_MessageSerializer.__init__} will raise a L{ValueError} if there is
357
+ a field included whose name starts with C{"_"}.
358
+ """
359
+ self.assertRaises(
360
+ ValueError,
361
+ _MessageSerializer,
362
+ [Field("message_type", identity, ""), Field("_key", identity, "")],
363
+ )
364
+
365
+ def test_serialize(self):
366
+ """
367
+ L{_MessageSerializer.serialize} will serialize all values in the given
368
+ dictionary using the respective L{Field}.
369
+ """
370
+ serializer = _MessageSerializer(
371
+ [
372
+ Field.forValue("message_type", "mymessage", "The type"),
373
+ Field("length", len, "The length of a thing"),
374
+ ]
375
+ )
376
+ message = {"message_type": "mymessage", "length": "thething"}
377
+ serializer.serialize(message)
378
+ self.assertEqual(message, {"message_type": "mymessage", "length": 8})
379
+
380
+ def test_missingSerializer(self):
381
+ """
382
+ If a value in the dictionary passed to L{_MessageSerializer.serialize}
383
+ has no respective field, it is unchanged.
384
+
385
+ Logging attempts to capture everything, with minimal work; with any
386
+ luck this value is JSON-encodable. Unit tests should catch such bugs, in any case.
387
+ """
388
+ serializer = _MessageSerializer(
389
+ [
390
+ Field.forValue("message_type", "mymessage", "The type"),
391
+ Field("length", len, "The length of a thing"),
392
+ ]
393
+ )
394
+ message = {"message_type": "mymessage", "length": "thething", "extra": 123}
395
+ serializer.serialize(message)
396
+ self.assertEqual(
397
+ message, {"message_type": "mymessage", "length": 8, "extra": 123}
398
+ )
399
+
400
+ def test_fieldInstances(self):
401
+ """
402
+ Fields to L{_MessageSerializer.__init__} should be instances of
403
+ L{Field}.
404
+ """
405
+ a_field = Field("a_key", identity)
406
+ arg = object()
407
+ with self.assertRaises(TypeError) as cm:
408
+ _MessageSerializer([a_field, arg])
409
+ self.assertEqual(("Expected a Field instance but got", arg), cm.exception.args)
410
+
411
+
412
+ class MessageTypeTests(TestCase):
413
+ """
414
+ Tests for L{MessageType}.
415
+ """
416
+
417
+ def messageType(self):
418
+ """
419
+ Return a L{MessageType} suitable for unit tests.
420
+ """
421
+ return MessageType(
422
+ "myapp:mysystem",
423
+ [Field.forTypes("key", [int], ""), Field.forTypes("value", [int], "")],
424
+ "A message type",
425
+ )
426
+
427
+ def test_validateMissingType(self):
428
+ """
429
+ L{MessageType._serializer.validate} raises a L{ValidationError} exception if the
430
+ given dictionary has no C{"message_type"} field.
431
+ """
432
+ messageType = self.messageType()
433
+ self.assertRaises(
434
+ ValidationError, messageType._serializer.validate, {"key": 1, "value": 2}
435
+ )
436
+
437
+ def test_validateWrongType(self):
438
+ """
439
+ L{MessageType._serializer.validate} raises a L{ValidationError}
440
+ exception if the given dictionary has the wrong value for the
441
+ C{"message_type"} field.
442
+ """
443
+ messageType = self.messageType()
444
+ self.assertRaises(
445
+ ValidationError,
446
+ messageType._serializer.validate,
447
+ {"key": 1, "value": 2, "message_type": "wrong"},
448
+ )
449
+
450
+ def test_validateExtraField(self):
451
+ """
452
+ L{MessageType._serializer.validate} raises a L{ValidationError}
453
+ exception if the given dictionary has an extra unknown field.
454
+ """
455
+ messageType = self.messageType()
456
+ self.assertRaises(
457
+ ValidationError,
458
+ messageType._serializer.validate,
459
+ {"key": 1, "value": 2, "message_type": "myapp:mysystem", "extra": "hello"},
460
+ )
461
+
462
+ def test_validateMissingField(self):
463
+ """
464
+ L{MessageType._serializer.validate} raises a L{ValidationError}
465
+ exception if the given dictionary has a missing field.
466
+ """
467
+ messageType = self.messageType()
468
+ self.assertRaises(
469
+ ValidationError,
470
+ messageType._serializer.validate,
471
+ {"key": 1, "message_type": "myapp:mysystem"},
472
+ )
473
+
474
+ def test_validateFieldValidation(self):
475
+ """
476
+ L{MessageType._serializer.validate} raises a L{ValidationError}
477
+ exception if the one of the field values fails field-specific
478
+ validation.
479
+ """
480
+ messageType = self.messageType()
481
+ self.assertRaises(
482
+ ValidationError,
483
+ messageType._serializer.validate,
484
+ {"key": 1, "value": None, "message_type": "myapp:mysystem"},
485
+ )
486
+
487
+ def test_validateStandardFields(self):
488
+ """
489
+ L{MessageType._serializer.validate} does not raise an exception if the
490
+ dictionary has the standard fields that are added to all messages.
491
+ """
492
+ messageType = self.messageType()
493
+ messageType._serializer.validate(
494
+ {
495
+ "key": 1,
496
+ "value": 2,
497
+ "message_type": "myapp:mysystem",
498
+ "task_level": "/",
499
+ "task_uuid": "123",
500
+ "timestamp": "xxx",
501
+ }
502
+ )
503
+
504
+ def test_call(self):
505
+ """
506
+ L{MessageType.__call__} creates a new L{Message} with correct
507
+ C{message_type} field value added.
508
+ """
509
+ messageType = self.messageType()
510
+ message = messageType()
511
+ self.assertEqual(message._contents, {"message_type": messageType.message_type})
512
+
513
+ def test_callSerializer(self):
514
+ """
515
+ L{MessageType.__call__} creates a new L{Message} with the
516
+ L{MessageType._serializer} as its serializer.
517
+ """
518
+ messageType = self.messageType()
519
+ message = messageType()
520
+ self.assertIs(message._serializer, messageType._serializer)
521
+
522
+ def test_callWithFields(self):
523
+ """
524
+ L{MessageType.__call__} creates a new L{Message} with the additional
525
+ given fields.
526
+ """
527
+ messageType = self.messageType()
528
+ message = messageType(key=2, value=3)
529
+ self.assertEqual(
530
+ message._contents,
531
+ {"message_type": messageType.message_type, "key": 2, "value": 3},
532
+ )
533
+
534
+ def test_logCallsDefaultLoggerWrite(self):
535
+ """
536
+ L{MessageType.log} calls the given logger's C{write} method with a
537
+ dictionary that is superset of the L{Message} contents.
538
+ """
539
+ messages = []
540
+ add_destination(messages.append)
541
+ self.addCleanup(remove_destination, messages.append)
542
+ message_type = self.messageType()
543
+ message_type.log(key=1234, value=3)
544
+ self.assertEqual(messages[0]["key"], 1234)
545
+ self.assertEqual(messages[0]["value"], 3)
546
+ self.assertEqual(messages[0]["message_type"], message_type.message_type)
547
+
548
+ def test_description(self):
549
+ """
550
+ L{MessageType.description} stores the passed in description.
551
+ """
552
+ messageType = self.messageType()
553
+ self.assertEqual(messageType.description, "A message type")
554
+
555
+ def test_optionalDescription(self):
556
+ """
557
+ L{MessageType} can be constructed without a description.
558
+ """
559
+ messageType = MessageType("name", [])
560
+ self.assertEqual(messageType.description, "")
561
+
562
+
563
+ class ActionTypeTestsMixin(object):
564
+ """
565
+ Mixin for tests for the three L{ActionType} message variants.
566
+ """
567
+
568
+ def getValidMessage(self):
569
+ """
570
+ Return a dictionary of a message that is of the action status being
571
+ tested.
572
+ """
573
+ raise NotImplementedError("Override in subclasses")
574
+
575
+ def getSerializer(self, actionType):
576
+ """
577
+ Given a L{ActionType}, return the L{_MessageSerializer} for this
578
+ variant.
579
+ """
580
+ raise NotImplementedError("Override in subclasses")
581
+
582
+ def actionType(self):
583
+ """
584
+ Return a L{ActionType} suitable for unit tests.
585
+ """
586
+ return ActionType(
587
+ "myapp:mysystem:myaction",
588
+ [Field.forTypes("key", [int], "")], # start fields
589
+ [Field.forTypes("value", [int], "")], # success fields
590
+ "A action type",
591
+ )
592
+
593
+ def test_validateMissingType(self):
594
+ """
595
+ L{ActionType.validate} raises a L{ValidationError} exception if the
596
+ given dictionary has no C{"action_type"} field.
597
+ """
598
+ actionType = self.actionType()
599
+ message = self.getValidMessage()
600
+ del message["action_type"]
601
+ self.assertRaises(
602
+ ValidationError, self.getSerializer(actionType).validate, message
603
+ )
604
+
605
+ def test_validateWrongType(self):
606
+ """
607
+ L{ActionType.validate} raises a L{ValidationError} exception if the
608
+ given dictionary has the wrong value for the C{"action_type"} field.
609
+ """
610
+ actionType = self.actionType()
611
+ message = self.getValidMessage()
612
+ message["action_type"] = "xxx"
613
+ self.assertRaises(
614
+ ValidationError, self.getSerializer(actionType).validate, message
615
+ )
616
+
617
+ def test_validateExtraField(self):
618
+ """
619
+ L{ActionType.validate} raises a L{ValidationError} exception if the
620
+ given dictionary has an extra unknown field.
621
+ """
622
+ actionType = self.actionType()
623
+ message = self.getValidMessage()
624
+ message["extra"] = "ono"
625
+ self.assertRaises(
626
+ ValidationError, self.getSerializer(actionType).validate, message
627
+ )
628
+
629
+ def test_validateMissingField(self):
630
+ """
631
+ L{ActionType.validate} raises a L{ValidationError} exception if the
632
+ given dictionary has a missing field.
633
+ """
634
+ actionType = self.actionType()
635
+ message = self.getValidMessage()
636
+ for key in message:
637
+ if key != "action_type":
638
+ del message[key]
639
+ break
640
+ self.assertRaises(
641
+ ValidationError, self.getSerializer(actionType).validate, message
642
+ )
643
+
644
+ def test_validateFieldValidation(self):
645
+ """
646
+ L{ActionType.validate} raises a L{ValidationError} exception if the
647
+ one of the field values fails field-specific validation.
648
+ """
649
+ actionType = self.actionType()
650
+ message = self.getValidMessage()
651
+ for key in message:
652
+ if key != "action_type":
653
+ message[key] = object()
654
+ break
655
+ self.assertRaises(
656
+ ValidationError, self.getSerializer(actionType).validate, message
657
+ )
658
+
659
+ def test_validateStandardFields(self):
660
+ """
661
+ L{ActionType.validate} does not raise an exception if the dictionary
662
+ has the standard fields that are added to all messages.
663
+ """
664
+ actionType = self.actionType()
665
+ message = self.getValidMessage()
666
+ message.update({"task_level": "/", "task_uuid": "123", "timestamp": "xxx"})
667
+ self.getSerializer(actionType).validate(message)
668
+
669
+
670
+ class ActionTypeStartMessage(TestCase, ActionTypeTestsMixin):
671
+ """
672
+ Tests for L{ActionType} validation of action start messages.
673
+ """
674
+
675
+ def getValidMessage(self):
676
+ """
677
+ Return a dictionary of a valid action start message.
678
+ """
679
+ return {
680
+ "action_type": "myapp:mysystem:myaction",
681
+ "action_status": "started",
682
+ "key": 1,
683
+ }
684
+
685
+ def getSerializer(self, actionType):
686
+ return actionType._serializers.start
687
+
688
+
689
+ class ActionTypeSuccessMessage(TestCase, ActionTypeTestsMixin):
690
+ """
691
+ Tests for L{ActionType} validation of action success messages.
692
+ """
693
+
694
+ def getValidMessage(self):
695
+ """
696
+ Return a dictionary of a valid action success message.
697
+ """
698
+ return {
699
+ "action_type": "myapp:mysystem:myaction",
700
+ "action_status": "succeeded",
701
+ "value": 2,
702
+ }
703
+
704
+ def getSerializer(self, actionType):
705
+ return actionType._serializers.success
706
+
707
+
708
+ class ActionTypeFailureMessage(TestCase, ActionTypeTestsMixin):
709
+ """
710
+ Tests for L{ActionType} validation of action failure messages.
711
+ """
712
+
713
+ def getValidMessage(self):
714
+ """
715
+ Return a dictionary of a valid action failure message.
716
+ """
717
+ return {
718
+ "action_type": "myapp:mysystem:myaction",
719
+ "action_status": "failed",
720
+ "exception": "exceptions.RuntimeError",
721
+ "reason": "because",
722
+ }
723
+
724
+ def getSerializer(self, actionType):
725
+ return actionType._serializers.failure
726
+
727
+ def test_validateExtraField(self):
728
+ """
729
+ Additional fields (which can be added by exception extraction) don't
730
+ cause a validation failure for failed action messages.
731
+ """
732
+ actionType = self.actionType()
733
+ message = self.getValidMessage()
734
+ message.update({"task_level": "/", "task_uuid": "123", "timestamp": "xxx"})
735
+ message.update({"extra_field": "hello"})
736
+ self.getSerializer(actionType).validate(message)
737
+
738
+
739
+ class ChildActionTypeStartMessage(TestCase):
740
+ """
741
+ Tests for validation of child actions created with L{ActionType}.
742
+ """
743
+
744
+ def test_childActionUsesChildValidator(self):
745
+ """
746
+ Validation of child actions uses the child's validator.
747
+ """
748
+ A = ActionType("myapp:foo", [Field.forTypes("a", [int], "")], [], "")
749
+ B = ActionType("myapp:bar", [Field.forTypes("b", [int], "")], [], "")
750
+
751
+ logger = MemoryLogger()
752
+
753
+ with A(logger, a=1):
754
+ with B(logger, b=2):
755
+ pass
756
+ # If wrong serializers/validators were used, this will fail:
757
+ logger.validate()
758
+
759
+
760
+ class ActionTypeTests(TestCase):
761
+ """
762
+ General tests for L{ActionType}.
763
+ """
764
+
765
+ def actionType(self):
766
+ """
767
+ Return a L{ActionType} suitable for unit tests.
768
+ """
769
+ return ActionType("myapp:mysystem:myaction", [], [], "An action type")
770
+
771
+ def test_call(self):
772
+ """
773
+ L{ActionType.__call__} returns the result of calling
774
+ C{self._start_action}.
775
+ """
776
+ actionType = self.actionType()
777
+ actionType._start_action = lambda *args, **kwargs: 1234
778
+ result = actionType(object())
779
+ self.assertEqual(result, 1234)
780
+
781
+ def test_callArguments(self):
782
+ """
783
+ L{ActionType.__call__} calls C{self._start_action} with the logger,
784
+ action type, serializers and passed in fields.
785
+ """
786
+ called = []
787
+ actionType = self.actionType()
788
+ actionType._start_action = lambda *args, **kwargs: called.append((args, kwargs))
789
+ logger = object()
790
+ actionType(logger, key=5)
791
+ self.assertEqual(
792
+ called,
793
+ [
794
+ (
795
+ (logger, "myapp:mysystem:myaction", actionType._serializers),
796
+ {"key": 5},
797
+ )
798
+ ],
799
+ )
800
+
801
+ def test_defaultStartAction(self):
802
+ """
803
+ L{ActionType._start_action} is L{eliot.start_action} by default.
804
+ """
805
+ self.assertEqual(ActionType._start_action, start_action)
806
+
807
+ def test_as_task(self):
808
+ """
809
+ L{ActionType.as_task} returns the result of calling C{self._startTask}.
810
+ """
811
+ actionType = self.actionType()
812
+ actionType._startTask = lambda *args, **kwargs: 1234
813
+ result = actionType.as_task(object())
814
+ self.assertEqual(result, 1234)
815
+
816
+ def test_as_taskArguments(self):
817
+ """
818
+ L{ActionType.as_task} calls C{self._startTask} with the logger,
819
+ action type and passed in fields.
820
+ """
821
+ called = []
822
+ actionType = self.actionType()
823
+ actionType._startTask = lambda *args, **kwargs: called.append((args, kwargs))
824
+ logger = object()
825
+ actionType.as_task(logger, key=5)
826
+ self.assertEqual(
827
+ called,
828
+ [
829
+ (
830
+ (logger, "myapp:mysystem:myaction", actionType._serializers),
831
+ {"key": 5},
832
+ )
833
+ ],
834
+ )
835
+
836
+ def test_defaultStartTask(self):
837
+ """
838
+ L{ActionType._startTask} is L{eliot.startTask} by default.
839
+ """
840
+ self.assertEqual(ActionType._startTask, startTask)
841
+
842
+ def test_description(self):
843
+ """
844
+ L{ActionType.description} stores the passed in description.
845
+ """
846
+ actionType = self.actionType()
847
+ self.assertEqual(actionType.description, "An action type")
848
+
849
+ def test_optionalDescription(self):
850
+ """
851
+ L{ActionType} can be constructed without a description.
852
+ """
853
+ actionType = ActionType("name", [], [])
854
+ self.assertEqual(actionType.description, "")
855
+
856
+ def test_as_taskDefaultLogger(self):
857
+ """
858
+ L{ActionType.as_task} doesn't require passing in a logger.
859
+ """
860
+ actionType = self.actionType()
861
+ actionType.as_task(key=5)
862
+
863
+
864
+ class EndToEndValidationTests(TestCase):
865
+ """
866
+ Test validation of messages created using L{MessageType} and
867
+ L{ActionType}.
868
+ """
869
+
870
+ MESSAGE = MessageType(
871
+ "myapp:mymessage",
872
+ [Field.forTypes("key", [int], "The key")],
873
+ "A message for testing.",
874
+ )
875
+ ACTION = ActionType(
876
+ "myapp:myaction",
877
+ [Field.forTypes("key", [int], "The key")],
878
+ [Field.forTypes("result", [str], "The result")],
879
+ "An action for testing.",
880
+ )
881
+
882
+ def test_correctFromMessageType(self):
883
+ """
884
+ A correct message created using L{MessageType} will be logged to a
885
+ L{MemoryLogger}.
886
+ """
887
+ logger = MemoryLogger()
888
+ msg = self.MESSAGE().bind(key=123)
889
+ msg.write(logger)
890
+ self.assertEqual(logger.messages[0]["key"], 123)
891
+
892
+ def test_incorrectFromMessageType(self):
893
+ """
894
+ An incorrect message created using L{MessageType} will raise a
895
+ L{ValidationError} in L{MemoryLogger.validate}.
896
+ """
897
+ logger = MemoryLogger()
898
+ msg = self.MESSAGE().bind(key="123")
899
+ msg.write(logger)
900
+ self.assertRaises(ValidationError, logger.validate)
901
+
902
+ def test_correctStartFromActionType(self):
903
+ """
904
+ A correct start message created using a L{ActionType} will be logged
905
+ to a L{MemoryLogger}.
906
+ """
907
+ logger = MemoryLogger()
908
+ with self.ACTION(logger, key=123) as action:
909
+ action.addSuccessFields(result="foo")
910
+ self.assertEqual(logger.messages[0]["key"], 123)
911
+
912
+ def test_omitLoggerFromActionType(self):
913
+ """
914
+ If no logger is given to the L{ActionType} the default logger is used.
915
+ """
916
+ messages = []
917
+ add_destination(messages.append)
918
+ self.addCleanup(remove_destination, messages.append)
919
+ with self.ACTION(key=123) as action:
920
+ action.add_success_fields(result="foo")
921
+ self.assertEqual(messages[0]["key"], 123)
922
+
923
+ def test_incorrectStartFromActionType(self):
924
+ """
925
+ An incorrect start message created using a L{ActionType} will raise a
926
+ L{ValidationError}.
927
+ """
928
+ logger = MemoryLogger()
929
+ with self.ACTION(logger, key="123") as action:
930
+ action.addSuccessFields(result="foo")
931
+ self.assertRaises(ValidationError, logger.validate)
932
+
933
+ def test_correctSuccessFromActionType(self):
934
+ """
935
+ A correct success message created using a L{ActionType} will be logged
936
+ to a L{MemoryLogger}.
937
+ """
938
+ logger = MemoryLogger()
939
+ with self.ACTION(logger, key=123) as action:
940
+ action.addSuccessFields(result="foo")
941
+ self.assertEqual(logger.messages[1]["result"], "foo")
942
+
943
+ def test_incorrectSuccessFromActionType(self):
944
+ """
945
+ An incorrect success message created using a L{ActionType} will raise a
946
+ L{ValidationError}.
947
+ """
948
+ logger = MemoryLogger()
949
+ with self.ACTION(logger, key=123) as action:
950
+ action.addSuccessFields(result=-1)
951
+ self.assertRaises(ValidationError, logger.validate)
952
+
953
+ def test_correctFailureFromActionType(self):
954
+ """
955
+ A correct failure message created using a L{ActionType} will be logged
956
+ to a L{MemoryLogger}.
957
+ """
958
+ logger = MemoryLogger()
959
+
960
+ def run():
961
+ with self.ACTION(logger, key=123):
962
+ raise RuntimeError("hello")
963
+
964
+ self.assertRaises(RuntimeError, run)
965
+ self.assertEqual(logger.messages[1]["reason"], "hello")
966
+
967
+
968
+ class PEP8Tests(TestCase):
969
+ """
970
+ Tests for PEP 8 method compatibility.
971
+ """
972
+
973
+ def test_for_value(self):
974
+ """
975
+ L{Field.for_value} is the same as L{Field.forValue}.
976
+ """
977
+ self.assertEqual(Field.for_value, Field.forValue)
978
+
979
+ def test_for_types(self):
980
+ """
981
+ L{Field.for_types} is the same as L{Field.forTypes}.
982
+ """
983
+ self.assertEqual(Field.for_types, Field.forTypes)
984
+
985
+ def test_as_task(self):
986
+ """
987
+ L{ActionType.as_task} is the same as L{ActionType.asTask}.
988
+ """
989
+ self.assertEqual(ActionType.as_task, ActionType.asTask)