@@ -33,19 +33,20 @@ function json_repr(io::IO, val::AbstractVector; indent::Int=0)
3333 print (io, ' [' )
3434 for i in eachindex (val)
3535 print (io, ' \n ' , ' ' ^ (indent + 2 ))
36- json_repr (io, val[i]; indent= indent+ 2 )
36+ json_repr (io, val[i]; indent= indent + 2 )
3737 i == lastindex (val) || print (io, ' ,' )
3838 end
3939 print (io, ' \n ' , ' ' ^ indent, ' ]' )
4040end
4141function json_repr (io:: IO , val:: Dict ; indent:: Int = 0 )
4242 print (io, ' {' )
43+ len = length (val)
4344 for (i, (k, v)) in enumerate (pairs (val))
4445 print (io, ' \n ' , ' ' ^ (indent + 2 ))
4546 json_repr (io, string (k))
4647 print (io, " : " )
47- json_repr (io, v; indent= indent+ 2 )
48- i === length (val) || print (io, ' ,' )
48+ json_repr (io, v; indent= indent + 2 )
49+ i == len || print (io, ' ,' )
4950 end
5051 print (io, ' \n ' , ' ' ^ indent, ' }' )
5152end
@@ -54,81 +55,105 @@ json_repr(io::IO, val::Any; indent::Int=0) = json_repr(io, string(val))
5455# Test result processing
5556
5657function result_dict (testset:: Test.DefaultTestSet , prefix:: String = " " )
57- Dict {String, Any} (
58+ scope = if isempty (prefix)
59+ testset. description == " Overall" ? " " : testset. description
60+ else
61+ join ((prefix, testset. description), ' /' )
62+ end
63+ data = Dict {String,Any} (
5864 " id" => Base. UUID (rand (UInt128)),
59- " scope" => join ((prefix, testset. description), ' /' ),
65+ " scope" => scope,
66+ " tags" => Dict {String,String} (
67+ " job_label" => get (ENV , " BUILDKITE_LABEL" , " unknown" ),
68+ " job_id" => get (ENV , " BUILDKITE_JOB_ID" , " unknown" ),
69+ " job_group" => get (ENV , " BUILDKITE_GROUP_LABEL" , " unknown" ),
70+ " os" => string (Sys. KERNEL),
71+ " arch" => string (Sys. ARCH),
72+ " julia_version" => string (VERSION ),
73+ " testset" => testset. description,
74+ ),
6075 " history" => if ! isnothing (testset. time_end)
61- Dict {String, Any} (
76+ Dict {String,Any} (
6277 " start_at" => testset. time_start,
6378 " end_at" => testset. time_end,
6479 " duration" => testset. time_end - testset. time_start)
6580 else
66- Dict {String, Any} (" start_at" => testset. time_start, " duration" => 0.0 )
81+ Dict {String,Any} (" start_at" => testset. time_start, " duration" => 0.0 )
6782 end )
83+ return data
6884end
6985
70- function result_dict (result:: Test.Result )
71- file, line = if ! hasproperty (result, :source ) || isnothing (result. source)
72- " unknown" , 0
73- else
74- something (result. source. file, " unknown" ), result. source. line
75- end
76- status = if result isa Test. Pass && result. test_type === :skipped
86+ # Test paths on runners are often in deep directories, so just make them contain enough information
87+ # to be able to identify the file. Also convert Windows-style paths to Unix-style paths so tests can
88+ # be grouped by file.
89+ function generalize_file_paths (path:: AbstractString )
90+ pathsep = Sys. iswindows () ? ' \\ ' : ' /'
91+ path = replace (path,
92+ string (Sys. STDLIB, pathsep) => " " ,
93+ string (normpath (Sys. BUILD_ROOT_PATH), pathsep) => " " ,
94+ string (dirname (Sys. BINDIR), pathsep) => " "
95+ )
96+ return Sys. iswindows () ? replace (path, " \\ " => " /" ) : path
97+ end
98+
99+ # passed, failed, skipped, or unknown
100+ function get_status (result)
101+ if result isa Test. Pass && result. test_type === :skipped
77102 " skipped"
103+ elseif result isa Test. Broken
104+ " skipped" # buildkite don't have a "broken" status
78105 elseif result isa Test. Pass
79106 " passed"
80107 elseif result isa Test. Fail || result isa Test. Error
81108 " failed"
82109 else
83110 " unknown"
84111 end
85- data = Dict {String, Any} (
86- " name" => " $(result. test_type) : $(result. orig_expr) " ,
112+ end
113+
114+ function result_dict (result:: Test.Result )
115+ file, line = if ! hasproperty (result, :source ) || isnothing (result. source)
116+ " unknown" , 0
117+ else
118+ something (result. source. file, " unknown" ), result. source. line
119+ end
120+ file = generalize_file_paths (string (file))
121+
122+ status = get_status (result)
123+
124+ result_show = sprint (show, result; context= :color => false )
125+ firstline = split (result_show, ' \n ' )[1 ]
126+ primary_reason = split (firstline, " at " )[1 ]
127+
128+ data = Dict {String,Any} (
129+ " name" => " $(primary_reason) . Expression: $(result. orig_expr) " ,
87130 " location" => string (file, ' :' , line),
88131 " file_name" => file,
89132 " result" => status)
90- add_failure_info! (data, result)
91- end
92133
93- function add_failure_info! (data:: Dict{String, Any} , result:: Test.Result )
94- if result isa Test. Fail
95- data[" failure_reason" ] = if result. test_type === :test && ! isnothing (result. data)
96- " Evaluated: $(result. data) "
97- elseif result. test_type === :test_throws_nothing
98- " No exception thrown"
99- elseif result. test_type === :test_throws_wrong
100- " Wrong exception type thrown"
101- else
102- " unknown"
103- end
104- elseif result isa Test. Error
105- data[" failure_reason" ] = if result. test_type === :test_error
106- if occursin (" \n Stacktrace:\n " , result. backtrace)
107- err, trace = split (result. backtrace, " \n Stacktrace:\n " , limit= 2 )
108- data[" failure_expanded" ] =
109- [Dict {String,Any} (" expanded" => split (err, ' \n ' ),
110- " backtrace" => split (trace, ' \n ' ))]
111- end
112- " Exception (unexpectedly) thrown during test"
113- elseif result. test_type === :test_nonbool
114- " Expected the expression to evaluate to a Bool, not a $(typeof (result. data)) "
115- elseif result. test_type === :test_unbroken
116- " Expected this test to be broken, but it passed"
134+ job_label = replace (get (ENV , " BUILDKITE_LABEL" , " job label not found" ), r" :\w +:\s *" => " " )
135+ if result isa Test. Fail || result isa Test. Error
136+ data[" failure_reason" ] = generalize_file_paths (firstline) * " | $job_label "
137+ err_trace = split (result_show, " \n Stacktrace:\n " , limit= 2 )
138+ if length (err_trace) == 2
139+ err, trace = err_trace
140+ data[" failure_expanded" ] = [Dict {String,Any} (" expanded" => split (err, ' \n ' ), " backtrace" => split (trace, ' \n ' ))]
117141 else
118- " unknown "
142+ data[ " failure_expanded " ] = [ Dict {String,Any} ( " expanded " => split (result_show, ' \n ' ), " backtrace " => [])]
119143 end
120144 end
121- data
145+ return data
122146end
123147
124- function collect_results! (results:: Vector{Dict{String, Any}} , testset:: Test.DefaultTestSet , prefix:: String = " " )
148+ function collect_results! (results:: Vector{Dict{String,Any}} , testset:: Test.DefaultTestSet , prefix:: String = " " )
125149 common_data = result_dict (testset, prefix)
126150 result_offset = length (results) + 1
127- result_counts = Dict {Tuple{String, String}, Int} ()
151+ result_counts = Dict {Tuple{String,String},Int} ()
152+ get_rid (rdata) = (rdata[" location" ], rdata[" result" ])
128153 for (i, result) in enumerate (testset. results)
129154 if result isa Test. Result
130155 rdata = result_dict (result)
131- rid = (rdata[ " location " ], rdata[ " result " ] )
156+ rid = get_rid (rdata)
132157 if haskey (result_counts, rid)
133158 result_counts[rid] += 1
134159 else
@@ -142,17 +167,17 @@ function collect_results!(results::Vector{Dict{String, Any}}, testset::Test.Defa
142167 # Modify names to hold `result_counts`
143168 for i in result_offset: length (results)
144169 result = results[i]
145- rid = (result[ " location " ], result[ " result " ] )
170+ rid = get_rid (result)
146171 if get (result_counts, rid, 0 ) > 1
147172 result[" name" ] = replace (result[" name" ], r" ^([^:]):" =>
148173 SubstitutionString (" \\ 1 (x$(result_counts[rid]) ):" ))
149174 end
150175 end
151- results
176+ return results
152177end
153178
154179function write_testset_json_files (dir:: String , testset:: Test.DefaultTestSet )
155- data = Dict{String, Any}[]
180+ data = Dict{String,Any}[]
156181 collect_results! (data, testset)
157182 files = String[]
158183 # Buildkite is limited to 5000 results per file https://buildkite.com/docs/test-analytics/importing-json
0 commit comments