test_flatten.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Test cases for L{twisted.logger._format}.
  6. """
  7. from itertools import count
  8. import json
  9. try:
  10. from time import tzset
  11. # We should upgrade to a version of pyflakes that does not require this.
  12. tzset
  13. except ImportError:
  14. tzset = None
  15. from twisted.trial import unittest
  16. from .._format import formatEvent
  17. from .._flatten import (
  18. flattenEvent, extractField, KeyFlattener, aFormatter
  19. )
  20. class FlatFormattingTests(unittest.TestCase):
  21. """
  22. Tests for flattened event formatting functions.
  23. """
  24. def test_formatFlatEvent(self):
  25. """
  26. L{flattenEvent} will "flatten" an event so that, if scrubbed of all but
  27. serializable objects, it will preserve all necessary data to be
  28. formatted once serialized. When presented with an event thusly
  29. flattened, L{formatEvent} will produce the same output.
  30. """
  31. counter = count()
  32. class Ephemeral(object):
  33. attribute = "value"
  34. event1 = dict(
  35. log_format=(
  36. "callable: {callme()} "
  37. "attribute: {object.attribute} "
  38. "numrepr: {number!r} "
  39. "numstr: {number!s} "
  40. "strrepr: {string!r} "
  41. "unistr: {unistr!s}"
  42. ),
  43. callme=lambda: next(counter), object=Ephemeral(),
  44. number=7, string="hello", unistr=u"ö"
  45. )
  46. flattenEvent(event1)
  47. event2 = dict(event1)
  48. del event2["callme"]
  49. del event2["object"]
  50. event3 = json.loads(json.dumps(event2))
  51. self.assertEqual(
  52. formatEvent(event3),
  53. (
  54. u"callable: 0 "
  55. "attribute: value "
  56. "numrepr: 7 "
  57. "numstr: 7 "
  58. "strrepr: 'hello' "
  59. u"unistr: ö"
  60. )
  61. )
  62. def test_formatFlatEventBadFormat(self):
  63. """
  64. If the format string is invalid, an error is produced.
  65. """
  66. event1 = dict(
  67. log_format=(
  68. "strrepr: {string!X}"
  69. ),
  70. string="hello",
  71. )
  72. flattenEvent(event1)
  73. event2 = json.loads(json.dumps(event1))
  74. self.assertTrue(
  75. formatEvent(event2).startswith(u"Unable to format event")
  76. )
  77. def test_formatFlatEventWithMutatedFields(self):
  78. """
  79. L{formatEvent} will prefer the stored C{str()} or C{repr()} value for
  80. an object, in case the other version.
  81. """
  82. class Unpersistable(object):
  83. """
  84. Unpersitable object.
  85. """
  86. destructed = False
  87. def selfDestruct(self):
  88. """
  89. Self destruct.
  90. """
  91. self.destructed = True
  92. def __repr__(self):
  93. if self.destructed:
  94. return "post-serialization garbage"
  95. else:
  96. return "un-persistable"
  97. up = Unpersistable()
  98. event1 = dict(
  99. log_format="unpersistable: {unpersistable}", unpersistable=up
  100. )
  101. flattenEvent(event1)
  102. up.selfDestruct()
  103. self.assertEqual(formatEvent(event1), "unpersistable: un-persistable")
  104. def test_keyFlattening(self):
  105. """
  106. Test that L{KeyFlattener.flatKey} returns the expected keys for format
  107. fields.
  108. """
  109. def keyFromFormat(format):
  110. for (
  111. literalText,
  112. fieldName,
  113. formatSpec,
  114. conversion,
  115. ) in aFormatter.parse(format):
  116. return KeyFlattener().flatKey(
  117. fieldName, formatSpec, conversion
  118. )
  119. # No name
  120. try:
  121. self.assertEqual(keyFromFormat("{}"), "!:")
  122. except ValueError:
  123. # In python 2.6, an empty field name causes Formatter.parse to
  124. # raise ValueError.
  125. # In Python 2.7, it's allowed, so this exception is unexpected.
  126. raise
  127. # Just a name
  128. self.assertEqual(keyFromFormat("{foo}"), "foo!:")
  129. # Add conversion
  130. self.assertEqual(keyFromFormat("{foo!s}"), "foo!s:")
  131. self.assertEqual(keyFromFormat("{foo!r}"), "foo!r:")
  132. # Add format spec
  133. self.assertEqual(keyFromFormat("{foo:%s}"), "foo!:%s")
  134. self.assertEqual(keyFromFormat("{foo:!}"), "foo!:!")
  135. self.assertEqual(keyFromFormat("{foo::}"), "foo!::")
  136. # Both
  137. self.assertEqual(keyFromFormat("{foo!s:%s}"), "foo!s:%s")
  138. self.assertEqual(keyFromFormat("{foo!s:!}"), "foo!s:!")
  139. self.assertEqual(keyFromFormat("{foo!s::}"), "foo!s::")
  140. [keyPlusLiteral] = aFormatter.parse("{x}")
  141. key = keyPlusLiteral[1:]
  142. sameFlattener = KeyFlattener()
  143. self.assertEqual(sameFlattener.flatKey(*key), "x!:")
  144. self.assertEqual(sameFlattener.flatKey(*key), "x!:/2")
  145. def _test_formatFlatEvent_fieldNamesSame(self, event=None):
  146. """
  147. The same format field used twice in one event is rendered twice.
  148. @param event: An event to flatten. If L{None}, create a new event.
  149. @return: C{event} or the event created.
  150. """
  151. if event is None:
  152. counter = count()
  153. class CountStr(object):
  154. """
  155. Hack
  156. """
  157. def __str__(self):
  158. return str(next(counter))
  159. event = dict(
  160. log_format="{x} {x}",
  161. x=CountStr(),
  162. )
  163. flattenEvent(event)
  164. self.assertEqual(formatEvent(event), u"0 1")
  165. return event
  166. def test_formatFlatEventFieldNamesSame(self):
  167. """
  168. The same format field used twice in one event is rendered twice.
  169. """
  170. self._test_formatFlatEvent_fieldNamesSame()
  171. def test_formatFlatEventFieldNamesSameAgain(self):
  172. """
  173. The same event flattened twice gives the same (already rendered)
  174. result.
  175. """
  176. event = self._test_formatFlatEvent_fieldNamesSame()
  177. self._test_formatFlatEvent_fieldNamesSame(event)
  178. def test_formatEventFlatTrailingText(self):
  179. """
  180. L{formatEvent} will handle a flattened event with tailing text after
  181. a replacement field.
  182. """
  183. event = dict(
  184. log_format="test {x} trailing",
  185. x='value',
  186. )
  187. flattenEvent(event)
  188. result = formatEvent(event)
  189. self.assertEqual(result, u"test value trailing")
  190. def test_extractField(self, flattenFirst=lambda x: x):
  191. """
  192. L{extractField} will extract a field used in the format string.
  193. @param flattenFirst: callable to flatten an event
  194. """
  195. class ObjectWithRepr(object):
  196. def __repr__(self):
  197. return "repr"
  198. class Something(object):
  199. def __init__(self):
  200. self.number = 7
  201. self.object = ObjectWithRepr()
  202. def __getstate__(self):
  203. raise NotImplementedError("Just in case.")
  204. event = dict(
  205. log_format="{something.number} {something.object}",
  206. something=Something(),
  207. )
  208. flattened = flattenFirst(event)
  209. def extract(field):
  210. return extractField(field, flattened)
  211. self.assertEqual(extract("something.number"), 7)
  212. self.assertEqual(extract("something.number!s"), "7")
  213. self.assertEqual(extract("something.object!s"), "repr")
  214. def test_extractFieldFlattenFirst(self):
  215. """
  216. L{extractField} behaves identically if the event is explicitly
  217. flattened first.
  218. """
  219. def flattened(evt):
  220. flattenEvent(evt)
  221. return evt
  222. self.test_extractField(flattened)
  223. def test_flattenEventWithoutFormat(self):
  224. """
  225. L{flattenEvent} will do nothing to an event with no format string.
  226. """
  227. inputEvent = {'a': 'b', 'c': 1}
  228. flattenEvent(inputEvent)
  229. self.assertEqual(inputEvent, {'a': 'b', 'c': 1})
  230. def test_flattenEventWithInertFormat(self):
  231. """
  232. L{flattenEvent} will do nothing to an event with a format string that
  233. contains no format fields.
  234. """
  235. inputEvent = {'a': 'b', 'c': 1, 'log_format': 'simple message'}
  236. flattenEvent(inputEvent)
  237. self.assertEqual(
  238. inputEvent,
  239. {
  240. 'a': 'b',
  241. 'c': 1,
  242. 'log_format': 'simple message',
  243. }
  244. )
  245. def test_flattenEventWithNoneFormat(self):
  246. """
  247. L{flattenEvent} will do nothing to an event with log_format set to
  248. None.
  249. """
  250. inputEvent = {'a': 'b', 'c': 1, 'log_format': None}
  251. flattenEvent(inputEvent)
  252. self.assertEqual(
  253. inputEvent,
  254. {
  255. 'a': 'b',
  256. 'c': 1,
  257. 'log_format': None,
  258. }
  259. )