11module main
22
33import time
4+ import term
45
56// Provide a Teamcity implementation of the TestRunner interface.
67// Used in Teamcity and JetBrains IDEs for nice test reporting.
2021 fn_passes u64
2122 fn_fails u64
2223
24+ assertion_info VAssertMetaInfo
2325 total_assert_passes u64
2426 total_assert_fails u64
2527}
@@ -58,25 +60,94 @@ fn (mut runner TeamcityTestRunner) fn_start() bool {
5860 runner.fn_assert_passes = 0
5961 runner.start_time = time.now ()
6062 runner.fname = normalise_fname (runner.fn_test_info.name)
61- println ("Start '${runner.fname} '" )
62- eprintln ("##teamcity[testStarted name='${runner.fname} ' locationHint='v_qn://${runner.file_test_info.file} :${runner.fname} ']" )
63+
64+ msg := "Start '${runner.fname} ' test"
65+ println (term.gray (msg))
66+
67+ runner.print_service ("
68+ |##teamcity[
69+ |testStarted name='${runner.fname} '
70+ |locationHint='v_qn://${runner.file_test_info.file} :${runner.fname} '
71+ |]" .strip_margin ())
6372 return true
6473}
6574
6675fn (mut runner TeamcityTestRunner) fn_pass () {
6776 runner.fn_passes++
6877 duration := runner.test_duration ()
6978 eprintln ("##teamcity[testFinished name='${runner.fname} ' duration='${duration} ']" )
79+ end_msg := "Finish '${runner.fname} ' test"
80+ println (term.gray (end_msg))
81+ msg := "Test '${runner.fname} ' passed"
82+ println (term.green (msg))
7083 println ('\n ' )
7184}
7285
7386fn (mut runner TeamcityTestRunner) fn_fail () {
7487 runner.fn_fails++
7588 duration := runner.test_duration ()
76- eprintln ("##teamcity[testFailed name='${runner.fname} ' duration='${duration} ' message='assertion failed']" )
89+ assertion := runner.assertion_info
90+
91+ mut actual := runner.prepare_value (assertion.lvalue)
92+ mut expected := runner.prepare_value (assertion.rvalue)
93+
94+ message := if assertion.has_msg {
95+ 'Assertion "${assertion.message} " failed'
96+ } else {
97+ op := if assertion.op == '' { '' } else { assertion.op }
98+
99+ if op == 'in' {
100+ parts := assertion.src.split ('in' )
101+ if parts.len == 2 {
102+ left := parts[0 ].trim (' ' )
103+ right := parts[1 ].trim (' ' )
104+ 'Assertion that "${left} " in "${right} " failed'
105+ } else {
106+ 'Assertion "${assertion.src} " failed'
107+ }
108+ } else if op == 'is' {
109+ 'Assertion that left and right type are equal failed'
110+ } else if op == 'call' {
111+ actual = 'false'
112+ expected = 'true'
113+ 'Assertion that function call "${assertion.src} " returns true failed'
114+ } else {
115+ lines := assertion.src.split_into_lines ()
116+ if lines.len == 1 {
117+ 'Assertion "${lines.first()} " failed'
118+ } else {
119+ 'Assertion failed'
120+ }
121+ }
122+ }
123+
124+ details := 'See failed assertion: ${assertion.fpath} :${assertion.line_nr + 1} '
125+
126+ runner.print_service ("
127+ |##teamcity[
128+ |testFailed name='${runner.fname} '
129+ |duration='${duration} '
130+ |details='${details} '
131+ |type='comparisonFailure'
132+ |actual='${actual} '
133+ |expected='${expected} '
134+ |message='${message} '
135+ |]" .strip_margin ())
77136 println ('\n ' )
78137}
79138
139+ // prepare_value escapes the value for Teamcity output.
140+ // For example, it replaces `\n` with `|n`, otherwise Teamcity
141+ // will not correctly parse the output.
142+ fn (mut _ TeamcityTestRunner) prepare_value (raw string ) string {
143+ return raw
144+ .replace ('\n ' , '|n' )
145+ .replace ('\r ' , '|r' )
146+ .replace ('[' , '|[' )
147+ .replace (']' , '|]' )
148+ .replace ("'" , "|'" )
149+ }
150+
80151fn (mut runner TeamcityTestRunner) fn_error (line_nr int , file string , mod string , fn_name string , errmsg string ) {
81152 eprintln ('>>> TeamcityTestRunner fn_error ${runner.fname} , line_nr: ${line_nr} , file: ${file} , mod: ${mod} , fn_name: ${fn_name} , errmsg: ${errmsg} ' )
82153}
@@ -85,52 +156,29 @@ fn (mut runner TeamcityTestRunner) test_duration() i64 {
85156 return time.now ().unix_time_milli () - runner.start_time.unix_time_milli ()
86157}
87158
159+ // print_service prepare and prints a Teamcity service message.
160+ fn (mut runner TeamcityTestRunner) print_service (msg string ) {
161+ without_new_lines := msg
162+ .trim ('\n\r ' )
163+ .replace ('\r ' , '' )
164+ .replace ('\n ' , ' ' )
165+ eprintln (without_new_lines)
166+ }
167+
88168//
89169
90170fn (mut runner TeamcityTestRunner) assert_pass (i & VAssertMetaInfo) {
91171 runner.total_assert_passes++
92172 runner.fn_assert_passes++
93173
94174 filepath := i.fpath.clone ()
95- println ('>>> assert_pass ${filepath} :${i.line_nr + 1} ' )
175+ msg := '>>> assertion passed ${filepath} :${i.line_nr + 1} '
176+ println (term.green (msg))
96177
97178 unsafe { i.free () }
98179}
99180
100181fn (mut runner TeamcityTestRunner) assert_fail (i & VAssertMetaInfo) {
101182 runner.total_assert_fails++
102-
103- filepath := i.fpath.clone ()
104- mut final_filepath := filepath + ':${i.line_nr + 1} :'
105- mut final_funcname := 'fn ' + i.fn_name.replace ('main.' , '' ).replace ('__' , '.' )
106- final_src := 'assert ' + i.src
107- eprintln ('${final_filepath} ${final_funcname} ' )
108- if i.op.len > 0 && i.op != 'call' {
109- mut lvtitle := ' Left value:'
110- mut rvtitle := ' Right value:'
111- mut slvalue := '${i.lvalue} '
112- mut srvalue := '${i.rvalue} '
113- cutoff_limit := 30
114- if slvalue.len > cutoff_limit || srvalue.len > cutoff_limit {
115- eprintln (' > ${final_src} ' )
116- eprintln (lvtitle)
117- eprintln (' ${slvalue} ' )
118- eprintln (rvtitle)
119- eprintln (' ${srvalue} ' )
120- } else {
121- eprintln (' > ${final_src} ' )
122- eprintln (' ${lvtitle} ${slvalue} ' )
123- eprintln ('${rvtitle} ${srvalue} ' )
124- }
125- } else {
126- eprintln (' ${final_src} ' )
127- }
128- if i.has_msg {
129- mut mtitle := ' Message:'
130- mut mvalue := '${i.message} '
131- eprintln ('${mtitle} ${mvalue} ' )
132- }
133- eprintln ('' )
134-
135- unsafe { i.free () }
183+ runner.assertion_info = * i
136184}
0 commit comments