|
@@ -1,6 +1,7 @@
|
1
|
1
|
#!/usr/bin/python2
|
2
|
2
|
|
3
|
3
|
import yaml
|
|
4
|
+import sys
|
4
|
5
|
|
5
|
6
|
|
6
|
7
|
class ParsingError(ValueError):
|
|
@@ -24,11 +25,11 @@ class _EventGroup(object):
|
24
|
25
|
|
25
|
26
|
@property
|
26
|
27
|
def fails(self):
|
27
|
|
- return [e for e in self._events if e.etype == 'FAIL']
|
|
28
|
+ return [e for e in self._events if e.etype == 'ASSERT.FAIL']
|
28
|
29
|
|
29
|
30
|
@property
|
30
|
31
|
def passes(self):
|
31
|
|
- return [e for e in self._events if e.etype == 'PASS']
|
|
32
|
+ return [e for e in self._events if e.etype == 'ASSERT.PASS']
|
32
|
33
|
|
33
|
34
|
@property
|
34
|
35
|
def events(self):
|
|
@@ -42,35 +43,108 @@ class _EventGroup(object):
|
42
|
43
|
return lints
|
43
|
44
|
|
44
|
45
|
|
|
46
|
+class EventTree(object):
|
|
47
|
+
|
|
48
|
+ def __init__(self, events, head=None):
|
|
49
|
+ self.children = []
|
|
50
|
+ self.rest = [e for e in events]
|
|
51
|
+ self.head = head
|
|
52
|
+ while self.rest:
|
|
53
|
+ if self.peek().startswith('SESSION.'):
|
|
54
|
+ self.pop()
|
|
55
|
+ elif self.peek() == 'PHASE.START':
|
|
56
|
+ phead = self.pop()
|
|
57
|
+ pbody = self.pull_branch('PHASE.END')
|
|
58
|
+ self.children.append(Phase(pbody, phead))
|
|
59
|
+ elif self.peek().startswith('MESSAGE.'):
|
|
60
|
+ self.children.append(self.pop())
|
|
61
|
+ elif self.peek().startswith('ASSERT.'):
|
|
62
|
+ self.children.append(self.pop())
|
|
63
|
+
|
|
64
|
+ def peek(self):
|
|
65
|
+ return self.rest[0].etype
|
|
66
|
+
|
|
67
|
+ def pop(self):
|
|
68
|
+ return self.rest.pop(0)
|
|
69
|
+
|
|
70
|
+ def pull_branch(self, final_etype):
|
|
71
|
+ out = []
|
|
72
|
+ while self.rest:
|
|
73
|
+ if self.peek() == final_etype:
|
|
74
|
+ self.pop()
|
|
75
|
+ return out
|
|
76
|
+ else:
|
|
77
|
+ out.append(self.pop())
|
|
78
|
+ self.lints += [Lint('could not find: %s' % final_etype, out)]
|
|
79
|
+ return out
|
|
80
|
+
|
|
81
|
+ @property
|
|
82
|
+ def lints(self):
|
|
83
|
+ lints = []
|
|
84
|
+ for e in self.children:
|
|
85
|
+ lints += e.lints
|
|
86
|
+ return lints
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+class Phase(EventTree):
|
|
90
|
+
|
|
91
|
+ is_phase = True
|
|
92
|
+
|
|
93
|
+ @property
|
|
94
|
+ def id(self):
|
|
95
|
+ return self.head._rawdata['phase']['id']
|
|
96
|
+
|
|
97
|
+ @property
|
|
98
|
+ def name(self):
|
|
99
|
+ return self.head._rawdata['phase']['name']
|
|
100
|
+
|
|
101
|
+ @property
|
|
102
|
+ def type(self):
|
|
103
|
+ return self.head._rawdata['phase']['type']
|
|
104
|
+
|
|
105
|
+ @property
|
|
106
|
+ def verdict(self):
|
|
107
|
+ out = 'UNKNOWN'
|
|
108
|
+ for e in self.children:
|
|
109
|
+ if e.is_assert:
|
|
110
|
+ if e.verdict == 'PASS':
|
|
111
|
+ out = 'PASS'
|
|
112
|
+ elif e.verdict == 'FAIL':
|
|
113
|
+ return 'FAIL'
|
|
114
|
+ return out
|
|
115
|
+
|
|
116
|
+
|
45
|
117
|
class Event(object):
|
46
|
118
|
|
47
|
119
|
is_assert = False
|
|
120
|
+ is_message = False
|
|
121
|
+ is_phase = False
|
48
|
122
|
|
49
|
123
|
def __str__(self):
|
50
|
124
|
return str(self._rawdata)
|
51
|
125
|
|
52
|
126
|
def __init__(self, rawdata):
|
53
|
127
|
self._rawdata = rawdata
|
54
|
|
- self.origin = rawdata['origin']
|
55
|
|
- self.stamp = rawdata['stamp']
|
56
|
|
- self.hint = rawdata['hint']
|
57
|
|
- self.beids = [BeId(b) for b in rawdata['beids']]
|
58
|
|
- self.edata = rawdata['data']
|
59
|
128
|
self.etype = rawdata['etype']
|
60
|
|
- self.phaseid = rawdata['phase']['id']
|
61
|
|
- self.phasename = rawdata['phase']['name']
|
62
|
|
- self.phasetype = rawdata['phase']['type']
|
|
129
|
+ self.stamp = rawdata['stamp']
|
|
130
|
+ self.data = rawdata.get('data', dict())
|
63
|
131
|
|
64
|
132
|
@staticmethod
|
65
|
133
|
def from_data(data):
|
66
|
134
|
etype = data['etype']
|
67
|
|
- if etype in ['PASS', 'FAIL']:
|
|
135
|
+ if etype in ['ASSERT.PASS', 'ASSERT.FAIL']:
|
68
|
136
|
cls = AssertEvent
|
69
|
|
- elif etype == 'SINFO':
|
|
137
|
+ elif etype in ['PHASE.START', 'PHASE.END']:
|
70
|
138
|
cls = StructureInfoEvent
|
71
|
|
- elif etype == 'TEST_ERROR':
|
72
|
|
- cls = TestErrorEvent
|
73
|
|
- elif etype == 'PROMISE':
|
|
139
|
+ elif etype in ['SESSION.START', 'SESSION.RELOAD', 'SESSION.END']:
|
|
140
|
+ cls = StructureInfoEvent
|
|
141
|
+ elif etype == 'MESSAGE.INFO':
|
|
142
|
+ cls = MessageEvent
|
|
143
|
+ elif etype == 'MESSAGE.WARNING':
|
|
144
|
+ cls = MessageEvent
|
|
145
|
+ elif etype == 'MESSAGE.ERROR':
|
|
146
|
+ cls = MessageEvent
|
|
147
|
+ elif etype == 'MESSAGE.PROMISE':
|
74
|
148
|
cls = PromiseEvent
|
75
|
149
|
else:
|
76
|
150
|
raise ParsingError("unknown event type: %s" % etype)
|
|
@@ -81,17 +155,33 @@ class Event(object):
|
81
|
155
|
return []
|
82
|
156
|
|
83
|
157
|
|
84
|
|
-class StrayEvent(Event):
|
85
|
|
- pass
|
|
158
|
+class MessageEvent(Event):
|
86
|
159
|
|
|
160
|
+ is_message = True
|
|
161
|
+
|
|
162
|
+ @property
|
|
163
|
+ def message(self):
|
|
164
|
+ return self._rawdata['message']
|
87
|
165
|
|
88
|
|
-class TestErrorEvent(Event):
|
|
166
|
+ @property
|
|
167
|
+ def severity(self):
|
|
168
|
+ return self.etype.split('.')[1]
|
89
|
169
|
|
|
170
|
+ @property
|
90
|
171
|
def lints(self):
|
91
|
|
- return [Lint("test error", self._rawdata)]
|
|
172
|
+ if self.severity == 'WARNING':
|
|
173
|
+ return [Lint("test warning", self._rawdata)]
|
|
174
|
+ elif self.severity == 'ERROR':
|
|
175
|
+ return [Lint("test error", self._rawdata)]
|
|
176
|
+ else:
|
|
177
|
+ return []
|
|
178
|
+
|
92
|
179
|
|
93
|
180
|
class PromiseEvent(Event):
|
94
|
|
- pass
|
|
181
|
+
|
|
182
|
+ @property
|
|
183
|
+ def lints(self):
|
|
184
|
+ return [Lint("promises are not supported (yet)", self._rawdata)]
|
95
|
185
|
|
96
|
186
|
|
97
|
187
|
class StructureInfoEvent(Event):
|
|
@@ -103,28 +193,16 @@ class AssertEvent(Event):
|
103
|
193
|
is_assert = True
|
104
|
194
|
|
105
|
195
|
@property
|
106
|
|
- def lints(self):
|
107
|
|
- lints = []
|
108
|
|
- if self.phaseid == '0000000000-000000000':
|
109
|
|
- lints.append(Lint('assert outside phase', self))
|
110
|
|
- return lints
|
111
|
|
-
|
112
|
|
-
|
113
|
|
-class BeId(object):
|
114
|
|
-
|
115
|
|
- def __init__(self, data):
|
116
|
|
- self._data = data
|
117
|
|
-
|
118
|
|
-
|
119
|
|
-class Phase(_EventGroup):
|
|
196
|
+ def caseid(self):
|
|
197
|
+ return self._rawdata['caseid']
|
120
|
198
|
|
121
|
199
|
@property
|
122
|
|
- def name(self):
|
123
|
|
- return self.all_events[0].phasename
|
|
200
|
+ def hint(self):
|
|
201
|
+ return self._rawdata['hint']
|
124
|
202
|
|
125
|
203
|
@property
|
126
|
|
- def type(self):
|
127
|
|
- return self.all_events[0].phasetype
|
|
204
|
+ def verdict(self):
|
|
205
|
+ return self.etype.split('.')[1]
|
128
|
206
|
|
129
|
207
|
|
130
|
208
|
class EventLog(_EventGroup):
|
|
@@ -156,102 +234,17 @@ class Session(object):
|
156
|
234
|
raise ParsingError('unsupported log format: %s' % self._format)
|
157
|
235
|
self.jat_version = doc['jat_version']
|
158
|
236
|
self.start = doc['start']
|
159
|
|
- self.sessid = 'noid'
|
|
237
|
+ self.sessid = doc.get('sessid', 'noid')
|
160
|
238
|
self.finalized = doc.get('finalized', False)
|
161
|
239
|
self.end = doc.get('end')
|
162
|
240
|
self.lints = []
|
163
|
241
|
self.eventlog = EventLog.from_data(doc['events'])
|
164
|
242
|
self.lints += self.eventlog.lints
|
165
|
|
- self.phases, lints = self._es2ps(self.eventlog)
|
166
|
|
- self.lints += lints
|
|
243
|
+ self.eventtree = EventTree(self.eventlog._events)
|
|
244
|
+ self.lints += self.eventtree.lints
|
167
|
245
|
if not self.finalized:
|
168
|
246
|
self.lints.append(Lint("session not finalized", self.sessid))
|
169
|
247
|
|
170
|
|
- class _PhaseBuff:
|
171
|
|
-
|
172
|
|
- def __init__(self):
|
173
|
|
- self.phaseid = None
|
174
|
|
- self.events = []
|
175
|
|
-
|
176
|
|
- def is_anon(self):
|
177
|
|
- return self.phaseid == '0000000000.000000000'
|
178
|
|
-
|
179
|
|
- def dump2phase(self):
|
180
|
|
- phase = Phase(self.events)
|
181
|
|
- self.phaseid = None
|
182
|
|
- self.events = []
|
183
|
|
- return phase
|
184
|
|
-
|
185
|
|
- def match(self, event):
|
186
|
|
- if self.phaseid is None:
|
187
|
|
- return True
|
188
|
|
- else:
|
189
|
|
- return self.phaseid == event.phaseid
|
190
|
|
-
|
191
|
|
- def append(self, event):
|
192
|
|
- self.phaseid = event.phaseid
|
193
|
|
- self.events.append(event)
|
194
|
|
-
|
195
|
|
- def __nonzero__(self):
|
196
|
|
- return bool(self.events)
|
197
|
|
-
|
198
|
|
- def __bool__(self):
|
199
|
|
- return bool(self.events)
|
200
|
|
-
|
201
|
|
- def __len__(self):
|
202
|
|
- return len(self.events)
|
203
|
|
-
|
204
|
|
- @staticmethod
|
205
|
|
- def _es2ps(eventlog):
|
206
|
|
-
|
207
|
|
- def addlint(msg):
|
208
|
|
- lints.append(Lint(msg, event))
|
209
|
|
-
|
210
|
|
- phases = []
|
211
|
|
- lints = []
|
212
|
|
- this_phaseid = None
|
213
|
|
- pbuff = Session._PhaseBuff()
|
214
|
|
- for event in eventlog.all_events:
|
215
|
|
- if event.origin == 'jat__sinit':
|
216
|
|
- continue
|
217
|
|
- if event.origin == 'jat__sfinish':
|
218
|
|
- if pbuff:
|
219
|
|
- phases.append(pbuff.dump2phase())
|
220
|
|
- addlint('unfinished phase: %s' % pbuff.phaseid)
|
221
|
|
- continue
|
222
|
|
- if event.origin == 'jat__pend':
|
223
|
|
- #
|
224
|
|
- # Phase end
|
225
|
|
- #
|
226
|
|
- if pbuff:
|
227
|
|
- phases.append(pbuff.dump2phase())
|
228
|
|
- else:
|
229
|
|
- addlint('dangling phase end')
|
230
|
|
- elif event.origin == '__jat__pstart':
|
231
|
|
- #
|
232
|
|
- # Phase start
|
233
|
|
- #
|
234
|
|
- if pbuff:
|
235
|
|
- phases.append(pbuff.dump2phase())
|
236
|
|
- if not pbuff.is_anon():
|
237
|
|
- addlint('phase starts before previous ends')
|
238
|
|
- elif pbuff:
|
239
|
|
- #
|
240
|
|
- # Second or later event
|
241
|
|
- #
|
242
|
|
- if not pbuff.match(event):
|
243
|
|
- phases.append(pbuff.dump2phase())
|
244
|
|
- addlint('phase mismatch: %s =! %s'\
|
245
|
|
- % (event.phaseid, pbuff.phaseid))
|
246
|
|
- pbuff.append(event)
|
247
|
|
- else:
|
248
|
|
- #
|
249
|
|
- # First event
|
250
|
|
- #
|
251
|
|
- pbuff.append(event)
|
252
|
|
- return phases, lints
|
253
|
|
-
|
254
|
|
-
|
255
|
248
|
def load(fpath):
|
256
|
249
|
with open(fpath) as ylog:
|
257
|
250
|
sessions = [Session(doc) for doc in yaml.load_all(ylog)]
|