-
-
Notifications
You must be signed in to change notification settings - Fork 424
Expand file tree
/
Copy pathHDTestReport.class.st
More file actions
474 lines (392 loc) · 13 KB
/
HDTestReport.class.st
File metadata and controls
474 lines (392 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
"
Hudson report for test results
"
Class {
#name : 'HDTestReport',
#superclass : 'HDReport',
#instVars : [
'suite',
'stream',
'suitePosition',
'suiteTime',
'suiteFailures',
'suiteErrors',
'nodeName',
'stageName',
'progressFileName',
'progressStream',
'shouldSerializeError'
],
#classVars : [
'CurrentStageName',
'ShuffleSeed'
],
#category : 'SUnit-Basic-CLI',
#package : 'SUnit-Basic-CLI'
}
{ #category : 'running' }
HDTestReport class >> currentStageName [
^CurrentStageName ifNil: [ '' ]
]
{ #category : 'running' }
HDTestReport class >> currentStageName: aStageName [
CurrentStageName := aStageName
]
{ #category : 'running' }
HDTestReport class >> runClasses: aCollectionOfClasses named: packageName [
| suite classes result |
suite := TestSuite named: packageName.
ShuffleSeed ifNotNil: [ suite shuffleSeed: ShuffleSeed asInteger ].
classes := (aCollectionOfClasses select: [ :class | class isTestCase and: [ class isAbstract not ] ]) asSortedCollection: [ :a :b | a name <= b name ].
classes ifEmpty: [ ^ nil ].
classes do: [ :class | suite addTests: class buildSuite tests ].
result := self runSuite: suite.
^ result
]
{ #category : 'running' }
HDTestReport class >> runPackage: aString [
^ self runClasses: (self packageOrganizer packageNamed: aString) definedClasses named: aString
]
{ #category : 'running' }
HDTestReport class >> runSuite: aTestSuite [
^ self new
suite: aTestSuite;
run
]
{ #category : 'running' }
HDTestReport class >> shuffleSeed: aSeed [
ShuffleSeed := aSeed
]
{ #category : 'private' }
HDTestReport >> calculateNodeName [
| environmentClass name bitString |
bitString := Smalltalk vm is32bit
ifTrue: [ '32' ]
ifFalse: [ '64' ].
name := Smalltalk os family , bitString , '.'.
"The Stage name has kernel in the name when running the tests in the small image.
We need to keep record of that"
(self stageName includesSubstring: 'Kernel')
ifTrue: [name := name , '.Kernel' ].
" If the environment class is not available, because it needs FFI, I use the node name,
because for sure I am in the Bootstrap process."
environmentClass := Smalltalk at: #OSEnvironment ifAbsent: [ ^ name ].
^ environmentClass current
at: 'JENKINS_HOME'
ifPresent: [ :value | name ]
ifAbsent: [ '' ]
]
{ #category : 'private' }
HDTestReport >> generateTestName: aTestCase [
^ nodeName , (aTestCase class package name copyReplaceAll: '-' with: '.')
]
{ #category : 'testing' }
HDTestReport >> hasErrors [
^ suiteErrors ~= 0
]
{ #category : 'testing' }
HDTestReport >> hasFailures [
^ suiteFailures ~= 0
]
{ #category : 'testing' }
HDTestReport >> hasFailuresOrErrors [
^ self hasFailures or: [ self hasErrors ]
]
{ #category : 'initialization' }
HDTestReport >> initialize [
super initialize.
shouldSerializeError := true.
suiteTime := 0.
suitePosition := suiteFailures := suiteErrors := 0.
progressFileName := 'progress.log'.
stageName := self class currentStageName.
nodeName := self calculateNodeName.
]
{ #category : 'private' }
HDTestReport >> newLogDuring: aBlock [
| currentStream tempStream |
currentStream := stream.
stream := tempStream := String new writeStream.
aBlock ensure: [ stream := currentStream ].
^tempStream contents
]
{ #category : 'initialization' }
HDTestReport >> openProgressStream [
| aFile |
aFile := File named: progressFileName.
aFile delete.
progressStream := ZnCharacterWriteStream
on: (aFile writeStream setToEnd; yourself)
encoding: 'utf8'
]
{ #category : 'accessing' }
HDTestReport >> progressFileName [
^ progressFileName
]
{ #category : 'accessing' }
HDTestReport >> progressFileName: anObject [
progressFileName := anObject
]
{ #category : 'running' }
HDTestReport >> recordError: anError duringTest: aTestCase [
aTestCase shouldPass ifFalse: [ ^self ].
suiteErrors := suiteErrors + 1.
self writeError: anError of: aTestCase.
self serializeError: anError of: aTestCase.
]
{ #category : 'running' }
HDTestReport >> recordFailure: aTestFailure duringTest: aTestCase [
aTestCase shouldPass ifFalse: [ ^self ].
suiteFailures := suiteFailures + 1.
self writeFailure: aTestFailure of: aTestCase.
self serializeError: aTestFailure of: aTestCase.
]
{ #category : 'running' }
HDTestReport >> recordPassOf: aTestCase [
]
{ #category : 'running' }
HDTestReport >> recordSkip: aTestSkip duringTest: aTestCase [
]
{ #category : 'running' }
HDTestReport >> recordUnexpectedPassOf: aTestCase [
suiteFailures := suiteFailures + 1.
self
writeException: (TestFailure new messageText: 'Unexpected pass (test is marked as expected failure)')
asNode: 'failure'
using: [ ]
]
{ #category : 'private' }
HDTestReport >> reportTestCase: aTestCase runBlock: aBlock [
| time testLog |
progressStream
nextPutAll: 'starting testcase: ';
nextPutAll: aTestCase class name;
nextPutAll: '>>';
nextPutAll: aTestCase nameForReport;
nextPutAll: ' ... ';
flush.
"The test element must be written after test completion to include a time information about run.
Therefore we can't report any error during test directly into the main xml stream.
Otherwise any new xml element would be written out of the test node.
To allow reporting during test we set a temp log stream
which we append to the main stream at the end of test as part of test element"
testLog := self newLogDuring: [ time := aBlock millisecondsToRun ].
stream
tab; nextPutAll: '<testcase classname="';
nextPutAll: (self encode: (self generateTestName: aTestCase));
nextPut: $.;
nextPutAll: (self encode: aTestCase class name);
nextPutAll: '" name="';
nextPutAll: (self encode: aTestCase nameForReport);
nextPutAll: '" time="';
print: time / 1000.0;
nextPutAll: '">';
lf.
stream nextPutAll: testLog.
stream tab; nextPutAll: '</testcase>'; lf.
progressStream
nextPutAll: 'finished in ';
nextPutAll: (Duration milliSeconds: time) humanReadablePrintString;
crlf;
flush
]
{ #category : 'running' }
HDTestReport >> run [
[
| time |
self setUp.
time := DateAndTime now.
CurrentExecutionEnvironment runTestsBy: [ suite tests do: [ :each | each run: self ] ].
suiteTime := DateAndTime now - time ] ensure: [ self tearDown ]
]
{ #category : 'running' }
HDTestReport >> runCase: aTestCase [
self
reportTestCase: aTestCase
runBlock: [
[aTestCase runCaseManaged.
aTestCase shouldPass
ifTrue: [ self recordPassOf: aTestCase ]
ifFalse: [ self recordUnexpectedPassOf: aTestCase ]
] on: Exception do: [ :exc |
exc recordResultOf: aTestCase inHDTestReport: self ]
]
]
{ #category : 'running' }
HDTestReport >> serializeError: error of: aTestCase [
"We got an error from a test, let's serialize it so we can properly debug it later on..."
| fuelFileName |
self shouldSerializeError ifFalse: [ ^ self ].
self class environment
at: #FLDebuggerStackSerializer
ifPresent: [ :fuelOutStackDebugAction |
| context testCaseMethodContext |
"we use signalContext and findMethodContextSuchThat: to find the method even with clean blocks in the middle"
context := error signalContext.
testCaseMethodContext := context findMethodContextSuchThat: [ :ctx |
ctx receiver == aTestCase and: [
ctx selector == #performTest ] ].
context := context copyTo: testCaseMethodContext.
fuelFileName := self suiteFileNameWithoutExtension , ('-' , aTestCase class name asString , '-', aTestCase selector asString , '.fuel').
[ fuelOutStackDebugAction
serializeStackFromContext: context sender
toFileNamed: fuelFileName ]
on: Error
do: [ :err | Stdio stderr << err messageText; crlf; flush ] ]
]
{ #category : 'running' }
HDTestReport >> setUp [
| aFile logString |
self openProgressStream.
logString := String streamContents: [ :aStream |
aStream
nextPutAll: 'Beginning to run tests of ';
nextPutAll: suite name;
nextPutAll: ' with random seed ';
print: suite shuffleSeed;
nextPutAll: OSPlatform current lineEnding ].
"We print in the progress file but also in the stdout for the CI infos."
logString trace.
progressStream
nextPutAll: logString;
flush.
aFile := File named: self suiteFileNameWithoutExtension , '.xml' .
aFile delete.
stream := ZnCharacterWriteStream
on: (aFile writeStream setToEnd; yourself)
encoding: 'utf8'.
stream nextPutAll: '<?xml version="1.0" encoding="UTF-8"?>'; lf.
stream
nextPutAll: '<testsuite ';
nextPutAll: 'name="'; nextPutAll: (self encode: suite name); nextPutAll: '" ';
nextPutAll: 'tests="'; print: suite tests size; nextPutAll: '" ';
nextPutAll: 'timestamp="'; print: Time now; nextPutAll: '" ';
nextPutAll: 'seed="'; print: suite shuffleSeed; nextPutAll: '" ';
nextPutAll: '>'.
"Now this is ugly. We want to update the time and the number of failures and errors, but still at the same time stream a valid XML. So remember this position and add some whitespace, that we can fill later."
suitePosition := stream wrappedStream position - 1.
stream nextPutAll: (String new: 100 withAll: $ ); lf.
"Initialize the test resources."
suite resources do: [ :each |
each isAvailable
ifFalse: [ each signalInitializationError ] ]
]
{ #category : 'accessing' }
HDTestReport >> shouldSerializeError [
^ shouldSerializeError
]
{ #category : 'accessing' }
HDTestReport >> shouldSerializeError: aBoolean [
shouldSerializeError := aBoolean.
]
{ #category : 'private' }
HDTestReport >> stackTraceString: err of: aTestCase [
^ self newLogDuring: [ self writeExceptionStack: err of: aTestCase ]
]
{ #category : 'accessing' }
HDTestReport >> stageName [
"The stage name is used by the CI to name the report files"
^ stageName
]
{ #category : 'accessing' }
HDTestReport >> stageName: anObject [
stageName := anObject
]
{ #category : 'initialization' }
HDTestReport >> suite: aTestSuite [
suite := aTestSuite
]
{ #category : 'accessing' }
HDTestReport >> suiteErrors [
^ suiteErrors
]
{ #category : 'accessing' }
HDTestReport >> suiteFailures [
^ suiteFailures
]
{ #category : 'accessing' }
HDTestReport >> suiteFileNameWithoutExtension [
| fileName |
fileName := stageName isEmpty
ifTrue: [ '' ]
ifFalse: [ stageName , '-' ].
^ fileName , suite name , '-Test'
]
{ #category : 'accessing' }
HDTestReport >> suitePassing [
^ self suiteTotal - self suiteFailures - self suiteErrors
]
{ #category : 'accessing' }
HDTestReport >> suiteTotal [
^ suite
ifNotNil: [ suite tests size ]
ifNil: [ 0 ]
]
{ #category : 'running' }
HDTestReport >> tearDown [
| logString |
suite resources
do: [ :each | each reset ].
stream tab; nextPutAll: '<system-out><![CDATA[]]></system-out>'; lf.
stream tab; nextPutAll: '<system-err><![CDATA[]]></system-err>'; lf.
stream nextPutAll: '</testsuite>'.
stream wrappedStream position: suitePosition.
stream
nextPutAll: ' failures="'; print: suiteFailures;
nextPutAll: '" errors="'; print: suiteErrors;
nextPutAll: '" time="'; print: suiteTime asMilliSeconds / 1000.0;
nextPutAll: '">'.
stream close.
logString := String streamContents: [ :aStream |
aStream
nextPutAll: 'Finished to run tests of ';
nextPutAll: suite name;
nextPutAll: ' in '.
suiteTime printHumanReadableOn: aStream ]. "We print in the progress file but also in the stdout for the CI infos."
logString traceCr.
progressStream
nextPutAll: logString;
close
]
{ #category : 'private' }
HDTestReport >> writeError: anError of: aTestCase [
self writeException: anError of: aTestCase asNode: 'error'
]
{ #category : 'private' }
HDTestReport >> writeException: anException asNode: errorNodeName using: writeBlock [
| encodedErrorName encodedErrorDescription |
stream tab; tab; nextPutAll: '<' ; nextPutAll: errorNodeName; nextPutAll: ' type="'.
encodedErrorName := self encode: anException class name.
encodedErrorDescription := self
encode: (anException messageText ifNil: [ anException description ]).
stream
nextPutAll: encodedErrorName;
nextPutAll: '" message="'; nextPutAll: encodedErrorDescription; nextPutAll: '">';
lf; nextPutAll: encodedErrorName; lf.
encodedErrorDescription ifNotEmpty: [
stream nextPutAll: encodedErrorDescription;
lf ].
writeBlock value.
stream tab; tab; nextPutAll: '</'; nextPutAll: errorNodeName; nextPutAll: '>'; lf
]
{ #category : 'private' }
HDTestReport >> writeException: anException of: aTestCase asNode: errorNodeName [
self
writeException: anException
asNode: errorNodeName
using: [ self writeExceptionStack: anException of: aTestCase ]
]
{ #category : 'private' }
HDTestReport >> writeExceptionStack: anException of: aTestCase [
| context |
context := anException signalerContext.
[ context isNil or: [ context receiver == aTestCase and: [ context selector == #runCase ]]]
whileFalse: [
[ stream nextPutAll: (self encode: context printString); lf ]
onErrorDo: [ stream nextPutAll: 'PRINTING ERROR'; lf].
context := context sender ]
]
{ #category : 'private' }
HDTestReport >> writeFailure: aTestFailure of: aTestCase [
self writeException: aTestFailure of: aTestCase asNode: 'failure'
]