jat.py 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #!/usr/bin/python2
  2. import yaml
  3. class ParsingError(ValueError):
  4. pass
  5. class _EventGroup(object):
  6. def __init__(self, events):
  7. if not events:
  8. raise ValueError("no events in event group")
  9. self._events = events
  10. @property
  11. def all_events(self):
  12. return self._events
  13. @property
  14. def asserts(self):
  15. return [e for e in self._events if e.is_assert]
  16. @property
  17. def fails(self):
  18. return [e for e in self._events if e.etype == 'FAIL']
  19. @property
  20. def passes(self):
  21. return [e for e in self._events if e.etype == 'PASS']
  22. @property
  23. def events(self):
  24. return self._events
  25. @property
  26. def lints(self):
  27. lints = []
  28. for e in self._events:
  29. lints += e.lints
  30. return lints
  31. class Event(object):
  32. is_assert = False
  33. def __str__(self):
  34. return str(self._rawdata)
  35. def __init__(self, rawdata):
  36. self._rawdata = rawdata
  37. self.origin = rawdata['origin']
  38. self.stamp = rawdata['stamp']
  39. self.hint = rawdata['hint']
  40. self.beids = [BeId(b) for b in rawdata['beids']]
  41. self.edata = rawdata['data']
  42. self.etype = rawdata['etype']
  43. self.phaseid = rawdata['phase']['id']
  44. self.phasename = rawdata['phase']['name']
  45. self.phasetype = rawdata['phase']['type']
  46. @staticmethod
  47. def from_data(data):
  48. etype = data['etype']
  49. if etype in ['PASS', 'FAIL']:
  50. cls = AssertEvent
  51. elif etype == 'SINFO':
  52. cls = StructureInfoEvent
  53. elif etype == 'TEST_ERROR':
  54. cls = TestErrorEvent
  55. elif etype == 'PROMISE':
  56. cls = PromiseEvent
  57. else:
  58. raise ParsingError("unknown event type: %s" % etype)
  59. return cls(data)
  60. @property
  61. def lints(self):
  62. return []
  63. class StrayEvent(Event):
  64. pass
  65. class TestErrorEvent(Event):
  66. def lints(self):
  67. return [Lint("test error", self._rawdata)]
  68. class PromiseEvent(Event):
  69. pass
  70. class StructureInfoEvent(Event):
  71. pass
  72. class AssertEvent(Event):
  73. is_assert = True
  74. @property
  75. def lints(self):
  76. lints = []
  77. if self.phaseid == '0000000000-000000000':
  78. lints.append(Lint('assert outside phase', self))
  79. return lints
  80. class BeId(object):
  81. def __init__(self, data):
  82. self._data = data
  83. class Phase(_EventGroup):
  84. @property
  85. def name(self):
  86. return self.all_events[0].phasename
  87. @property
  88. def type(self):
  89. return self.all_events[0].phasetype
  90. class EventLog(_EventGroup):
  91. """
  92. Session event log (all events from session)
  93. """
  94. @classmethod
  95. def from_data(cls, data):
  96. return cls([Event.from_data(e) for e in data])
  97. class Lint(object):
  98. """
  99. Lint-like warning coming from log parsing (eg. unclosed phase)
  100. """
  101. def __init__(self, msg, data):
  102. self.msg = msg
  103. self._data = data
  104. class Session(object):
  105. def __init__(self, doc):
  106. self._doc = doc
  107. self._format = doc['format']
  108. if not self._format.startswith('jat/0.'):
  109. raise ParsingError('unsupported log format: %s' % self._format)
  110. self.jat_version = doc['jat_version']
  111. self.start = doc['start']
  112. self.sessid = 'noid'
  113. self.finalized = doc.get('finalized', False)
  114. self.end = doc.get('end')
  115. self.lints = []
  116. self.eventlog = EventLog.from_data(doc['events'])
  117. self.lints += self.eventlog.lints
  118. self.phases, lints = self._es2ps(self.eventlog)
  119. self.lints += lints
  120. if not self.finalized:
  121. self.lints.append(Lint("session not finalized", self.sessid))
  122. class _PhaseBuff:
  123. def __init__(self):
  124. self.phaseid = None
  125. self.events = []
  126. def is_anon(self):
  127. return self.phaseid == '0000000000.000000000'
  128. def dump2phase(self):
  129. phase = Phase(self.events)
  130. self.phaseid = None
  131. self.events = []
  132. return phase
  133. def match(self, event):
  134. if self.phaseid is None:
  135. return True
  136. else:
  137. return self.phaseid == event.phaseid
  138. def append(self, event):
  139. self.phaseid = event.phaseid
  140. self.events.append(event)
  141. def __nonzero__(self):
  142. return bool(self.events)
  143. def __bool__(self):
  144. return bool(self.events)
  145. def __len__(self):
  146. return len(self.events)
  147. @staticmethod
  148. def _es2ps(eventlog):
  149. def addlint(msg):
  150. lints.append(Lint(msg, event))
  151. phases = []
  152. lints = []
  153. this_phaseid = None
  154. pbuff = Session._PhaseBuff()
  155. for event in eventlog.all_events:
  156. if event.origin == 'jat__sinit':
  157. continue
  158. if event.origin == 'jat__sfinish':
  159. if pbuff:
  160. phases.append(pbuff.dump2phase())
  161. addlint('unfinished phase: %s' % pbuff.phaseid)
  162. continue
  163. if event.origin == 'jat__pend':
  164. #
  165. # Phase end
  166. #
  167. if pbuff:
  168. phases.append(pbuff.dump2phase())
  169. else:
  170. addlint('dangling phase end')
  171. elif event.origin == '__jat__pstart':
  172. #
  173. # Phase start
  174. #
  175. if pbuff:
  176. phases.append(pbuff.dump2phase())
  177. if not pbuff.is_anon():
  178. addlint('phase starts before previous ends')
  179. elif pbuff:
  180. #
  181. # Second or later event
  182. #
  183. if not pbuff.match(event):
  184. phases.append(pbuff.dump2phase())
  185. addlint('phase mismatch: %s =! %s'\
  186. % (event.phaseid, pbuff.phaseid))
  187. pbuff.append(event)
  188. else:
  189. #
  190. # First event
  191. #
  192. pbuff.append(event)
  193. return phases, lints
  194. def load(fpath):
  195. with open(fpath) as ylog:
  196. sessions = [Session(doc) for doc in yaml.load_all(ylog)]
  197. return sessions