1
1
const path = require ( 'path' )
2
2
const chalk = require ( 'chalk' )
3
3
const Libhoney = require ( 'libhoney' )
4
+ const { v4 : uuidv4 } = require ( 'uuid' )
4
5
5
- const pkg = require ( '@packages/root' )
6
6
const ciProvider = require ( '@packages/server/lib/util/ci_provider' )
7
7
const { commitInfo } = require ( '@cypress/commit-info' )
8
+ const { getNextVersionForPath } = require ( '../../scripts/get-next-version' )
8
9
9
- class StatsdReporter {
10
+ const honey = new Libhoney ( {
11
+ dataset : 'systemtest-performance' ,
12
+ writeKey : process . env . HONEYCOMB_API_KEY ,
13
+ } )
14
+
15
+ // This event is created here independently every time the reporter
16
+ // is imported (in each parallel instance of the system-tests
17
+ // in circleci) so that we can use it as the parent,
18
+ // but ../scripts/send-root-honeycomb-event.js
19
+ // is only invoked once at the start of the build,
20
+ // and is responsible for sending it to honeycomb.
21
+ const spanId = process . env . CIRCLE_WORKFLOW_ID || uuidv4 ( )
22
+ const circleCiRootEvent = honey . newEvent ( )
23
+
24
+ circleCiRootEvent . timestamp = Date . now ( )
25
+ circleCiRootEvent . add ( {
26
+ buildUrl : process . env . CIRCLE_BUILD_URL ,
27
+ platform : process . platform ,
28
+ arch : process . arch ,
29
+ name : 'ci_run' ,
30
+
31
+ spanId,
32
+ traceId : spanId ,
33
+ } )
34
+
35
+ // Mocha events ('test', 'test end', etc) have no way to wait
36
+ // for async callbacks, so we can't guarantee we have this
37
+ // data ready by the time any of the reporter's events are emitted.
38
+
39
+ // Therefore, we have each honeycomb event await this promise
40
+ // before sending itself.
41
+ let asyncInfo = Promise . all ( [ getNextVersionForPath ( path . resolve ( __dirname , '../../packages' ) ) , commitInfo ( ) ] )
42
+ . then ( ( [ nextVersion , commitInformation ] ) => {
43
+ const ciInformation = ciProvider . commitParams ( ) || { }
44
+
45
+ return {
46
+ nextVersion,
47
+ branch : commitInformation . branch || ciInformation . branch ,
48
+ commitSha : commitInformation . sha || ciInformation . sha ,
49
+ }
50
+ } )
51
+
52
+ function addAsyncInfoAndSend ( honeycombEvent ) {
53
+ return asyncInfo . then ( ( info ) => {
54
+ honeycombEvent . add ( info )
55
+ honeycombEvent . send ( )
56
+ } )
57
+ }
58
+
59
+ class HoneycombReporter {
10
60
constructor ( runner ) {
11
61
if ( ! process . env . HONEYCOMB_API_KEY ) {
12
62
return
13
63
}
14
64
15
65
console . log ( chalk . green ( 'Reporting to honeycomb' ) )
16
66
17
- let branch
18
- let commitSha
67
+ runner . on ( 'suite' , ( suite ) => {
68
+ if ( ! suite . title ) {
69
+ return
70
+ }
19
71
20
- this . honey = new Libhoney ( {
21
- dataset : 'systemtest-performance' ,
22
- writeKey : process . env . HONEYCOMB_API_KEY ,
23
- } )
72
+ const parent = suite . parent && suite . parent . honeycombEvent ? suite . parent . honeycombEvent : circleCiRootEvent
24
73
25
- commitInfo ( ) . then ( ( commitInformation ) => {
26
- const ciInformation = ciProvider . commitParams ( ) || { }
74
+ suite . honeycombEvent = honey . newEvent ( )
75
+ suite . honeycombEvent . timestamp = Date . now ( )
76
+ suite . honeycombEvent . add ( {
77
+ ...parent . data ,
78
+ suite : suite . title ,
79
+ specFile : suite . file && path . basename ( suite . file ) ,
80
+ name : 'spec_execution' ,
27
81
28
- branch = commitInformation . branch || ciInformation . branch
29
- commitSha = commitInformation . sha || ciInformation . sha
82
+ spanId : uuidv4 ( ) ,
83
+ parentId : parent . data . spanId ,
84
+ } )
30
85
} )
31
86
32
87
runner . on ( 'test' , ( test ) => {
33
- test . wallclockStart = Date . now ( )
88
+ const path = test . titlePath ( )
89
+ // This regex pulls apart a string like `failing1 [electron]`
90
+ // into `failing1` and `electron`, letting us use the same
91
+ // test name for all browsers, with the browser as a separate field.
92
+ // The browser capture group is optional because some tests aren't browser specific,
93
+ // in which case it will be undefined and not passed as a field to honeycomb.
94
+ const [ , testTitle , browser ] = path [ path . length - 1 ] . match ( / ( .+ ?) (?: \[ ( [ a - z ] + ) \] ) ? $ / )
95
+
96
+ test . honeycombEvent = honey . newEvent ( )
97
+ test . honeycombEvent . timestamp = Date . now ( )
98
+ test . honeycombEvent . add ( {
99
+ ...test . parent . honeycombEvent . data ,
100
+ test : testTitle ,
101
+ browser,
102
+ name : 'test_execution' ,
103
+
104
+ spanId : uuidv4 ( ) ,
105
+ parentId : test . parent . honeycombEvent . data . spanId ,
106
+ } )
34
107
} )
35
108
36
109
runner . on ( 'test end' , ( test ) => {
@@ -39,44 +112,42 @@ class StatsdReporter {
39
112
return
40
113
}
41
114
42
- const title = test . titlePath ( ) . join ( ' / ' )
43
- // This regex pulls apart a string like `e2e async timeouts / failing1 [electron]`
44
- // into `e2e async timeouts / failing1` and `electron`, letting us use the same
45
- // test name for all browsers, with the browser as a separate field.
46
- // The browser capture group is optional because some tests aren't browser specific,
47
- // in which case it will be undefined and not passed as a field to honeycomb.
48
- const [ , testTitle , browser ] = title . match ( / ( .+ ?) (?: \[ ( [ a - z ] + ) \] ) ? $ / )
49
-
50
- const honeycombEvent = this . honey . newEvent ( )
51
-
52
- honeycombEvent . timestamp = test . wallclockStart
53
- honeycombEvent . add ( {
54
- test : testTitle ,
55
- specFile : path . basename ( test . file ) ,
56
- browser,
115
+ test . honeycombEvent . add ( {
57
116
state : test . state ,
58
117
err : test . err && test . err . message ,
59
118
errStack : test . err && test . err . stack ,
60
- durationMs : Date . now ( ) - test . wallclockStart ,
61
- mochaDurationMs : test . duration ,
62
- branch,
63
- commitSha,
64
- buildUrl : process . env . CIRCLE_BUILD_URL ,
65
- platform : process . platform ,
66
- arch : process . arch ,
67
- version : pkg . version ,
119
+ durationMs : Date . now ( ) - test . honeycombEvent . timestamp ,
68
120
} )
69
121
70
- honeycombEvent . send ( )
122
+ addAsyncInfoAndSend ( test . honeycombEvent )
123
+ } )
124
+
125
+ runner . on ( 'suite end' , ( suite ) => {
126
+ if ( ! suite . honeycombEvent ) {
127
+ return
128
+ }
129
+
130
+ suite . honeycombEvent . add ( {
131
+ durationMs : Date . now ( ) - suite . honeycombEvent . timestamp ,
132
+ } )
133
+
134
+ addAsyncInfoAndSend ( suite . honeycombEvent )
71
135
} )
72
136
}
73
137
74
- // If there is no done callback, then mocha-multi-reporter will kill the process without waiting for our honeycomb post to complete.
138
+ // If there is no done method, then mocha-multi-reporter will kill the process
139
+ // without waiting for our honeycomb posts to complete.
75
140
done ( failures , callback ) {
76
- if ( this . honey ) {
77
- this . honey . flush ( ) . then ( callback )
78
- }
141
+ // Await the asyncInfo promise one last time, to ensure all events have
142
+ // added the data and sent themselves before we flush honeycomb's queue and exit.
143
+ asyncInfo
144
+ . then ( ( ) => honey . flush ( ) )
145
+ . then ( callback )
79
146
}
80
147
}
81
148
82
- module . exports = StatsdReporter
149
+ module . exports = HoneycombReporter
150
+
151
+ HoneycombReporter . honey = honey
152
+ HoneycombReporter . circleCiRootEvent = circleCiRootEvent
153
+ HoneycombReporter . addAsyncInfoAndSend = addAsyncInfoAndSend
0 commit comments