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,86 @@
1
+ """
2
+ Tests for the public API exposed by L{eliot}.
3
+ """
4
+
5
+ from unittest import TestCase
6
+
7
+ from .._output import Logger
8
+ import logxpy
9
+
10
+
11
+ class PublicAPITests(TestCase):
12
+ """
13
+ Tests for the public API.
14
+ """
15
+
16
+ def test_addDestination(self):
17
+ """
18
+ L{eliot.addDestination} adds destinations to the L{Destinations}
19
+ attached to L{Logger}.
20
+ """
21
+ o = object()
22
+ eliot.addDestination(o)
23
+ self.addCleanup(eliot.removeDestination, o)
24
+ self.assertIn(o, Logger._destinations._destinations)
25
+
26
+ def test_removeDestination(self):
27
+ """
28
+ L{eliot.addDestination} removes destinations from the L{Destinations}
29
+ attached to L{Logger}.
30
+ """
31
+ self.assertEqual(eliot.removeDestination, Logger._destinations.remove)
32
+
33
+ def test_addGlobalFields(self):
34
+ """
35
+ L{eliot.addGlobalFields} calls the corresponding method on the
36
+ L{Destinations} attached to L{Logger}.
37
+ """
38
+ self.assertEqual(eliot.addGlobalFields, Logger._destinations.addGlobalFields)
39
+
40
+
41
+ class PEP8Tests(TestCase):
42
+ """
43
+ Tests for the PEP 8 variant of the the public API.
44
+ """
45
+
46
+ def test_add_destination(self):
47
+ """
48
+ L{eliot.addDestionation} is the same as L{eliot.add_destination}.
49
+ """
50
+ self.assertIs(eliot.add_destination, eliot.addDestination)
51
+
52
+ def test_remove_destination(self):
53
+ """
54
+ L{eliot.removeDestionation} is the same as L{eliot.remove_destination}.
55
+ """
56
+ self.assertIs(eliot.remove_destination, eliot.removeDestination)
57
+
58
+ def test_add_global_fields(self):
59
+ """
60
+ L{eliot.add_global_fields} is the same as L{eliot.addGlobalFields}.
61
+ """
62
+ self.assertIs(eliot.add_global_fields, eliot.addGlobalFields)
63
+
64
+ def test_write_traceback(self):
65
+ """
66
+ L{eliot.writeTraceback} is the same as L{eliot.write_traceback}.
67
+ """
68
+ self.assertIs(eliot.write_traceback, eliot.writeTraceback)
69
+
70
+ def test_write_failure(self):
71
+ """
72
+ L{eliot.writeFailure} is the same as L{eliot.write_failure}.
73
+ """
74
+ self.assertIs(eliot.write_failure, eliot.writeFailure)
75
+
76
+ def test_start_task(self):
77
+ """
78
+ L{eliot.startTask} is the same as L{eliot.start_task}.
79
+ """
80
+ self.assertIs(eliot.start_task, eliot.startTask)
81
+
82
+ def test_start_action(self):
83
+ """
84
+ L{eliot.startAction} is the same as L{eliot.start_action}.
85
+ """
86
+ self.assertIs(eliot.start_action, eliot.startAction)
@@ -0,0 +1,67 @@
1
+ """Tests for eliot._async."""
2
+ from unittest import TestCase
3
+ import asyncio
4
+ from logxpy._async import action, aaction, scope, _emit_handlers
5
+
6
+ class AsyncActionTests(TestCase):
7
+ def test_sync_action(self):
8
+ """Sync action context manager works."""
9
+ records = []
10
+ def handler(r): records.append(r)
11
+ _emit_handlers.append(handler)
12
+ self.addCleanup(_emit_handlers.remove, handler)
13
+
14
+ with action("test_action", x=1):
15
+ pass
16
+
17
+ self.assertEqual(len(records), 2)
18
+ start, end = records
19
+ self.assertEqual(start.action_status, "started")
20
+ self.assertEqual(end.action_status, "succeeded")
21
+ self.assertEqual(start.task_uuid, end.task_uuid)
22
+
23
+ def test_async_action(self):
24
+ """Async action context manager works."""
25
+ records = []
26
+ def handler(r): records.append(r)
27
+ _emit_handlers.append(handler)
28
+ self.addCleanup(_emit_handlers.remove, handler)
29
+
30
+ async def run():
31
+ async with aaction("test_async", y=2):
32
+ pass
33
+
34
+ asyncio.run(run())
35
+
36
+ self.assertEqual(len(records), 2)
37
+ start, end = records
38
+ self.assertEqual(start.action_type, "test_async")
39
+ self.assertEqual(end.action_status, "succeeded")
40
+
41
+ def test_scope(self):
42
+ """Scope context manager merges context."""
43
+ records = []
44
+ def handler(r): records.append(r)
45
+ _emit_handlers.append(handler)
46
+ self.addCleanup(_emit_handlers.remove, handler)
47
+
48
+ with scope(a=1):
49
+ with action("scoped"):
50
+ pass
51
+
52
+ self.assertEqual(records[0].context.get("a"), 1)
53
+
54
+ def test_async_scope(self):
55
+ """Scope works in async context."""
56
+ records = []
57
+ def handler(r): records.append(r)
58
+ _emit_handlers.append(handler)
59
+ self.addCleanup(_emit_handlers.remove, handler)
60
+
61
+ async def run():
62
+ with scope(b=2):
63
+ async with aaction("async_scoped"):
64
+ pass
65
+
66
+ asyncio.run(run())
67
+ self.assertEqual(records[0].context.get("b"), 2)
@@ -0,0 +1,13 @@
1
+ """Tests for eliot._compat."""
2
+ from unittest import TestCase
3
+ import warnings
4
+ from logxpy import log_message
5
+
6
+ class CompatTests(TestCase):
7
+ def test_deprecations(self):
8
+ """Old APIs emit warnings."""
9
+ with warnings.catch_warnings(record=True) as w:
10
+ warnings.simplefilter("always")
11
+ log_message("test_msg")
12
+ self.assertTrue(len(w) > 0)
13
+ self.assertIn("deprecated", str(w[0].message))
@@ -0,0 +1,21 @@
1
+ """Tests for eliot._config."""
2
+ from unittest import TestCase
3
+ import os
4
+ from logxpy._config import Config
5
+
6
+ class ConfigTests(TestCase):
7
+ def test_defaults(self):
8
+ """Default config is sane."""
9
+ cfg = Config()
10
+ self.assertEqual(cfg.format, "rich")
11
+ self.assertEqual(cfg.destinations, ["console"])
12
+
13
+ def test_env_override(self):
14
+ """Environment variables override defaults."""
15
+ os.environ["LOGGERX_LEVEL"] = "ERROR"
16
+ try:
17
+ cfg = Config.load()
18
+ from eliot._types import Level
19
+ self.assertEqual(cfg.level, Level.ERROR)
20
+ finally:
21
+ del os.environ["LOGGERX_LEVEL"]
@@ -0,0 +1,105 @@
1
+ """
2
+ Tests for coroutines.
3
+
4
+ Imported into test_coroutine.py when running tests under Python 3.5 or later;
5
+ in earlier versions of Python this code is a syntax error.
6
+ """
7
+
8
+ import asyncio
9
+ from unittest import TestCase
10
+
11
+ from ..testing import capture_logging
12
+ from ..parse import Parser
13
+ from .. import start_action
14
+
15
+
16
+ async def standalone_coro():
17
+ """
18
+ Log a message inside a new coroutine.
19
+ """
20
+ await asyncio.sleep(0.1)
21
+ with start_action(action_type="standalone"):
22
+ pass
23
+
24
+
25
+ async def calling_coro():
26
+ """
27
+ Log an action inside a coroutine, and call another coroutine.
28
+ """
29
+ with start_action(action_type="calling"):
30
+ await standalone_coro()
31
+
32
+
33
+ def run_coroutines(*async_functions):
34
+ """
35
+ Run a coroutine until it finishes.
36
+ """
37
+ loop = asyncio.get_event_loop()
38
+ futures = [asyncio.ensure_future(f()) for f in async_functions]
39
+
40
+ async def wait_for_futures():
41
+ for future in futures:
42
+ await future
43
+
44
+ loop.run_until_complete(wait_for_futures())
45
+
46
+
47
+ class CoroutineTests(TestCase):
48
+ """
49
+ Tests for coroutines.
50
+ """
51
+
52
+ @capture_logging(None)
53
+ def test_multiple_coroutines_contexts(self, logger):
54
+ """
55
+ Each top-level coroutine has its own Eliot logging context.
56
+ """
57
+
58
+ async def waiting_coro():
59
+ with start_action(action_type="waiting"):
60
+ await asyncio.sleep(0.5)
61
+
62
+ run_coroutines(waiting_coro, standalone_coro)
63
+ trees = Parser.parse_stream(logger.messages)
64
+ self.assertEqual(
65
+ sorted([(t.root().action_type, t.root().children) for t in trees]),
66
+ [("standalone", []), ("waiting", [])],
67
+ )
68
+
69
+ @capture_logging(None)
70
+ def test_await_inherits_coroutine_contexts(self, logger):
71
+ """
72
+ awaited coroutines inherit the logging context.
73
+ """
74
+ run_coroutines(calling_coro)
75
+ [tree] = Parser.parse_stream(logger.messages)
76
+ root = tree.root()
77
+ [child] = root.children
78
+ self.assertEqual(
79
+ (root.action_type, child.action_type, child.children),
80
+ ("calling", "standalone", []),
81
+ )
82
+
83
+ @capture_logging(None)
84
+ def test_interleaved_coroutines(self, logger):
85
+ """
86
+ start_action() started in one coroutine doesn't impact another in a
87
+ different coroutine.
88
+ """
89
+
90
+ async def coro_sleep(delay, action_type):
91
+ with start_action(action_type=action_type):
92
+ await asyncio.sleep(delay)
93
+
94
+ async def main():
95
+ with start_action(action_type="main"):
96
+ f1 = asyncio.ensure_future(coro_sleep(1, "a"))
97
+ f2 = asyncio.ensure_future(coro_sleep(0.5, "b"))
98
+ await f1
99
+ await f2
100
+
101
+ run_coroutines(main)
102
+ [tree] = list(Parser.parse_stream(logger.messages))
103
+ root = tree.root()
104
+ self.assertEqual(root.action_type, "main")
105
+ self.assertEqual(sorted([c.action_type for c in root.children]), ["a", "b"])
@@ -0,0 +1,211 @@
1
+ """Tests for eliot.dask."""
2
+
3
+ from unittest import TestCase, skipUnless
4
+
5
+ from ..testing import capture_logging, LoggedAction, LoggedMessage
6
+ from .. import start_action, log_message
7
+
8
+ try:
9
+ import dask
10
+ from dask.bag import from_sequence
11
+ from dask.distributed import Client
12
+ import dask.dataframe as dd
13
+ import pandas as pd
14
+ except ImportError:
15
+ dask = None
16
+ else:
17
+ from ..dask import (
18
+ compute_with_trace,
19
+ _RunWithEliotContext,
20
+ _add_logging,
21
+ persist_with_trace,
22
+ )
23
+
24
+
25
+ @skipUnless(dask, "Dask not available.")
26
+ class DaskTests(TestCase):
27
+ """Tests for end-to-end functionality."""
28
+
29
+ def setUp(self):
30
+ dask.config.set(scheduler="threading")
31
+
32
+ def test_compute(self):
33
+ """compute_with_trace() runs the same logic as compute()."""
34
+ bag = from_sequence([1, 2, 3])
35
+ bag = bag.map(lambda x: x * 7).map(lambda x: x * 4)
36
+ bag = bag.fold(lambda x, y: x + y)
37
+ self.assertEqual(dask.compute(bag), compute_with_trace(bag))
38
+
39
+ def test_future(self):
40
+ """compute_with_trace() can handle Futures."""
41
+ client = Client(processes=False)
42
+ self.addCleanup(client.shutdown)
43
+ [bag] = dask.persist(from_sequence([1, 2, 3]))
44
+ bag = bag.map(lambda x: x * 5)
45
+ result = dask.compute(bag)
46
+ self.assertEqual(result, ([5, 10, 15],))
47
+ self.assertEqual(result, compute_with_trace(bag))
48
+
49
+ def test_persist_result(self):
50
+ """persist_with_trace() runs the same logic as process()."""
51
+ client = Client(processes=False)
52
+ self.addCleanup(client.shutdown)
53
+ bag = from_sequence([1, 2, 3])
54
+ bag = bag.map(lambda x: x * 7)
55
+ self.assertEqual(
56
+ [b.compute() for b in dask.persist(bag)],
57
+ [b.compute() for b in persist_with_trace(bag)],
58
+ )
59
+
60
+ def test_persist_pandas(self):
61
+ """persist_with_trace() with a Pandas dataframe.
62
+
63
+ This ensures we don't blow up, which used to be the case.
64
+ """
65
+ df = pd.DataFrame()
66
+ df = dd.from_pandas(df, npartitions=1)
67
+ persist_with_trace(df)
68
+
69
+ @capture_logging(None)
70
+ def test_persist_logging(self, logger):
71
+ """persist_with_trace() preserves Eliot context."""
72
+
73
+ def persister(bag):
74
+ [bag] = persist_with_trace(bag)
75
+ return dask.compute(bag)
76
+
77
+ self.assert_logging(logger, persister, "dask:persist")
78
+
79
+ @capture_logging(None)
80
+ def test_compute_logging(self, logger):
81
+ """compute_with_trace() preserves Eliot context."""
82
+ self.assert_logging(logger, compute_with_trace, "dask:compute")
83
+
84
+ def assert_logging(self, logger, run_with_trace, top_action_name):
85
+ """Utility function for _with_trace() logging tests."""
86
+
87
+ def mult(x):
88
+ log_message(message_type="mult")
89
+ return x * 4
90
+
91
+ def summer(x, y):
92
+ log_message(message_type="finally")
93
+ return x + y
94
+
95
+ bag = from_sequence([1, 2])
96
+ bag = bag.map(mult).fold(summer)
97
+ with start_action(action_type="act1"):
98
+ run_with_trace(bag)
99
+
100
+ [logged_action] = LoggedAction.ofType(logger.messages, "act1")
101
+ self.assertEqual(
102
+ logged_action.type_tree(),
103
+ {
104
+ "act1": [
105
+ {
106
+ top_action_name: [
107
+ {"eliot:remote_task": ["dask:task", "mult"]},
108
+ {"eliot:remote_task": ["dask:task", "mult"]},
109
+ {"eliot:remote_task": ["dask:task", "finally"]},
110
+ ]
111
+ }
112
+ ]
113
+ },
114
+ )
115
+
116
+ # Make sure dependencies are tracked:
117
+ (
118
+ mult1_msg,
119
+ mult2_msg,
120
+ final_msg,
121
+ ) = LoggedMessage.ofType(logger.messages, "dask:task")
122
+ self.assertEqual(
123
+ sorted(final_msg.message["dependencies"]),
124
+ sorted([mult1_msg.message["key"], mult2_msg.message["key"]]),
125
+ )
126
+
127
+ # Make sure dependencies are logically earlier in the logs:
128
+ self.assertTrue(
129
+ mult1_msg.message["task_level"] < final_msg.message["task_level"]
130
+ )
131
+ self.assertTrue(
132
+ mult2_msg.message["task_level"] < final_msg.message["task_level"]
133
+ )
134
+
135
+
136
+ @skipUnless(dask, "Dask not available.")
137
+ class AddLoggingTests(TestCase):
138
+ """Tests for _add_logging()."""
139
+
140
+ maxDiff = None
141
+
142
+ def test_add_logging_to_full_graph(self):
143
+ """_add_logging() recreates Dask graph with wrappers."""
144
+ bag = from_sequence([1, 2, 3])
145
+ bag = bag.map(lambda x: x * 7).map(lambda x: x * 4)
146
+ bag = bag.fold(lambda x, y: x + y)
147
+ graph = bag.__dask_graph__()
148
+
149
+ # Add logging:
150
+ with start_action(action_type="bleh"):
151
+ logging_added = _add_logging(graph)
152
+
153
+ # Ensure resulting graph hasn't changed substantively:
154
+ logging_removed = {}
155
+ for key, value in logging_added.items():
156
+ if callable(value[0]):
157
+ func, args = value[0], value[1:]
158
+ self.assertIsInstance(func, _RunWithEliotContext)
159
+ value = (func.func,) + args
160
+ logging_removed[key] = value
161
+
162
+ self.assertEqual(logging_removed, graph)
163
+
164
+ def test_add_logging_explicit(self):
165
+ """_add_logging() on more edge cases of the graph."""
166
+
167
+ def add(s):
168
+ return s + "s"
169
+
170
+ def add2(s):
171
+ return s + "s"
172
+
173
+ # b runs first, then d, then a and c.
174
+ graph = {
175
+ "a": "d",
176
+ "d": [1, 2, (add, "b")],
177
+ ("b", 0): 1,
178
+ "c": (add2, "d"),
179
+ }
180
+
181
+ with start_action(action_type="bleh") as action:
182
+ task_id = action.task_uuid
183
+ self.assertEqual(
184
+ _add_logging(graph),
185
+ {
186
+ "d": [
187
+ 1,
188
+ 2,
189
+ (
190
+ _RunWithEliotContext(
191
+ task_id=task_id + "@/2",
192
+ func=add,
193
+ key="d",
194
+ dependencies=["b"],
195
+ ),
196
+ "b",
197
+ ),
198
+ ],
199
+ "a": "d",
200
+ ("b", 0): 1,
201
+ "c": (
202
+ _RunWithEliotContext(
203
+ task_id=task_id + "@/3",
204
+ func=add2,
205
+ key="c",
206
+ dependencies=["d"],
207
+ ),
208
+ "d",
209
+ ),
210
+ },
211
+ )
@@ -0,0 +1,54 @@
1
+ """Tests for eliot.decorators."""
2
+ from unittest import TestCase
3
+ import asyncio
4
+ from logxpy.decorators import logged, timed, retry
5
+ from logxpy._async import _emit_handlers
6
+
7
+ class DecoratorTests(TestCase):
8
+ def setUp(self):
9
+ self.records = []
10
+ _emit_handlers.append(self.records.append)
11
+
12
+ def tearDown(self):
13
+ _emit_handlers.remove(self.records.append)
14
+
15
+ def test_logged_sync(self):
16
+ """@logged works on sync functions."""
17
+ @logged
18
+ def func(x): return x + 1
19
+
20
+ func(1)
21
+ self.assertEqual(len(self.records), 2) # start, end
22
+ self.assertEqual(self.records[0].fields["x"], 1)
23
+ self.assertEqual(self.records[1].fields["result"], 2)
24
+
25
+ def test_logged_async(self):
26
+ """@logged works on async functions."""
27
+ @logged
28
+ async def func(x): return x + 1
29
+
30
+ asyncio.run(func(1))
31
+ self.assertEqual(len(self.records), 2)
32
+ self.assertEqual(self.records[1].fields["result"], 2)
33
+
34
+ def test_timed(self):
35
+ """@timed emits metric."""
36
+ @timed("my_metric")
37
+ def func(): pass
38
+
39
+ func()
40
+ self.assertEqual(self.records[0].fields["metric"], "my_metric")
41
+ self.assertIn("duration_ms", self.records[0].fields)
42
+
43
+ def test_retry(self):
44
+ """@retry retries on failure."""
45
+ count = 0
46
+ @retry(attempts=2, delay=0)
47
+ def func():
48
+ nonlocal count
49
+ count += 1
50
+ if count < 2: raise ValueError("fail")
51
+ return "ok"
52
+
53
+ self.assertEqual(func(), "ok")
54
+ self.assertEqual(count, 2)
@@ -0,0 +1,122 @@
1
+ """
2
+ Tests for L{eliot.filter}.
3
+ """
4
+
5
+ import sys
6
+
7
+ from unittest import TestCase
8
+ from datetime import datetime
9
+ from io import StringIO
10
+ import inspect
11
+ import json
12
+
13
+ from .common import FakeSys
14
+ from ..filter import EliotFilter, main, USAGE
15
+
16
+
17
+ class EliotFilterTests(TestCase):
18
+ """
19
+ Tests for L{EliotFilter}.
20
+ """
21
+
22
+ def test_expression(self):
23
+ """
24
+ For each item in the incoming sequence L{EliotFilter.run} calls
25
+ L{EliotFilter._evaluate} with the item decoded from JSON, and writes the
26
+ result to the output file as JSON.
27
+ """
28
+ f = StringIO()
29
+ efilter = EliotFilter("J", [b'"abcd"', b"[1, 2]"], f)
30
+ efilter._evaluate = lambda expr: {"x": len(expr), "orig": expr}
31
+ self.assertEqual(f.getvalue(), "")
32
+ efilter.run()
33
+ self.assertEqual(
34
+ f.getvalue(),
35
+ json.dumps({"x": 4, "orig": "abcd"})
36
+ + "\n"
37
+ + json.dumps({"x": 2, "orig": [1, 2]})
38
+ + "\n",
39
+ )
40
+
41
+ def evaluateExpression(self, expr, message):
42
+ """
43
+ Render a single message with the given expression using
44
+ L{EliotFilter._evaluate}.
45
+ """
46
+ efilter = EliotFilter(expr, [], StringIO())
47
+ return efilter._evaluate(message)
48
+
49
+ def test_J(self):
50
+ """
51
+ The expression has access to the decoded JSON message as C{J} in its
52
+ locals.
53
+ """
54
+ result = self.evaluateExpression("J['a']", {"a": 123})
55
+ self.assertEqual(result, 123)
56
+
57
+ def test_otherLocals(self):
58
+ """
59
+ The expression has access to L{datetime} and L{timedelta} in its
60
+ built-ins.
61
+ """
62
+ result = self.evaluateExpression(
63
+ "isinstance(datetime.utcnow() - datetime.utcnow(), timedelta)", {}
64
+ )
65
+ self.assertEqual(result, True)
66
+
67
+ def test_datetimeSerialization(self):
68
+ """
69
+ Any L{datetime} in results will be serialized using L{datetime.isoformat}.
70
+ """
71
+ dt = datetime(2012, 12, 31)
72
+ f = StringIO()
73
+ EliotFilter("datetime(2012, 12, 31)", ["{}"], f).run()
74
+ expected = json.dumps(dt.isoformat()) + "\n"
75
+ self.assertEqual(f.getvalue(), expected)
76
+
77
+ def test_SKIP(self):
78
+ """
79
+ A result of C{SKIP} indicates nothing should be output.
80
+ """
81
+ f = StringIO()
82
+ EliotFilter("SKIP", [b'{"a": 123}'], f).run()
83
+ self.assertEqual(f.getvalue(), "")
84
+
85
+
86
+ class MainTests(TestCase):
87
+ """
88
+ Test cases for L{main}.
89
+ """
90
+
91
+ def test_default(self):
92
+ """
93
+ By default L{main} uses information from L{sys}.
94
+ """
95
+ self.assertEqual(inspect.getfullargspec(main).defaults, (sys,))
96
+
97
+ def test_stdinOut(self):
98
+ """
99
+ L{main} reads from the C{stdin} attribute of the given C{sys} equivalent,
100
+ and writes rendered expressions to the C{stdout} attribute.
101
+ """
102
+ sys = FakeSys(["eliotfilter", "J[0]"], "[1, 2]\n[4, 5]\n")
103
+ main(sys)
104
+ self.assertEqual(sys.stdout.getvalue(), "1\n4\n")
105
+
106
+ def test_success(self):
107
+ """
108
+ A successful run returns C{0}.
109
+ """
110
+ sys = FakeSys(["eliotfilter", "J[0]"], "[1, 2]\n[4, 5]\n")
111
+ result = main(sys)
112
+ self.assertEqual(result, 0)
113
+
114
+ def test_noArguments(self):
115
+ """
116
+ If given no arguments, usage documentation is printed to stderr and C{1}
117
+ is returned.
118
+ """
119
+ sys = FakeSys(["eliotfilter"], "")
120
+ result = main(sys)
121
+ self.assertEqual(sys.stderr.getvalue(), USAGE)
122
+ self.assertEqual(result, 1)