v2 / vlib / v / preludes / test_runner_teamcity.v
185 lines · 154 sloc · 4.54 KB · 8cc4a81aa626fb2c39dd4d9d4a67ec2693d0e38f
Raw
1module main
2
3import time
4import term
5
6// Provide a Teamcity implementation of the TestRunner interface.
7// Used in Teamcity and JetBrains IDEs for nice test reporting.
8
9fn vtest_init() {
10 change_test_runner(&TestRunner(TeamcityTestRunner{}))
11}
12
13struct TeamcityTestRunner {
14mut:
15 fname string
16 start_time time.Time
17
18 file_test_info VTestFileMetaInfo
19 fn_test_info VTestFnMetaInfo
20 fn_assert_passes u64
21 fn_passes u64
22 fn_fails u64
23
24 assertion_info VAssertMetaInfo
25 total_assert_passes u64
26 total_assert_fails u64
27}
28
29fn (mut runner TeamcityTestRunner) free() {
30 unsafe {
31 runner.fname.free()
32 runner.fn_test_info.free()
33 runner.file_test_info.free()
34 }
35}
36
37fn normalise_fname(name string) string {
38 return name.replace('__', '.').replace('main.', '')
39}
40
41fn (mut runner TeamcityTestRunner) start(ntests int) {
42}
43
44fn (mut runner TeamcityTestRunner) finish() {
45}
46
47fn (mut runner TeamcityTestRunner) exit_code() int {
48 if runner.fn_fails > 0 {
49 return 1
50 }
51 if runner.total_assert_fails > 0 {
52 return 1
53 }
54 return 0
55}
56
57//
58
59fn (mut runner TeamcityTestRunner) fn_start() bool {
60 runner.fn_assert_passes = 0
61 runner.start_time = time.now()
62 runner.fname = normalise_fname(runner.fn_test_info.name)
63
64 msg := "Start '${runner.fname}' test"
65 println(term.gray(msg))
66
67 runner.print_service("
68 |##teamcity[testStarted
69 |name='${runner.fname}'
70 |locationHint='v_qn://${runner.file_test_info.file}:${runner.fname}'
71 |]".strip_margin())
72 return true
73}
74
75fn (mut runner TeamcityTestRunner) fn_pass() {
76 runner.fn_passes++
77 duration := runner.test_duration()
78 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))
83 println('\n')
84}
85
86fn (mut runner TeamcityTestRunner) fn_fail() {
87 runner.fn_fails++
88 duration := runner.test_duration()
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[testFailed
128 |name='${runner.fname}'
129 |duration='${duration}'
130 |details='${details}'
131 |type='comparisonFailure'
132 |actual='${actual}'
133 |expected='${expected}'
134 |message='${runner.prepare_value(message)}'
135 |]".strip_margin())
136 println('\n')
137}
138
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.
142fn (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
151fn (mut runner TeamcityTestRunner) fn_error(line_nr int, file string, mod string, fn_name string, errmsg string) {
152 eprintln('>>> TeamcityTestRunner fn_error ${runner.fname}, line_nr: ${line_nr}, file: ${file}, mod: ${mod}, fn_name: ${fn_name}, errmsg: ${errmsg}')
153}
154
155fn (mut runner TeamcityTestRunner) test_duration() i64 {
156 return time.now().unix_milli() - runner.start_time.unix_milli()
157}
158
159// print_service prepare and prints a Teamcity service message.
160fn (mut runner TeamcityTestRunner) print_service(msg string) {
161 without_new_lines := msg
162 .trim('\n\r ')
163 .replace('\r', '')
164 .replace('\n]', ']')
165 .replace('\n', ' ')
166 eprintln(without_new_lines)
167}
168
169//
170
171fn (mut runner TeamcityTestRunner) assert_pass(i &VAssertMetaInfo) {
172 runner.total_assert_passes++
173 runner.fn_assert_passes++
174
175 filepath := i.fpath.clone()
176 msg := '>>> assertion passed ${filepath}:${i.line_nr + 1}'
177 println(term.green(msg))
178
179 unsafe { i.free() }
180}
181
182fn (mut runner TeamcityTestRunner) assert_fail(i &VAssertMetaInfo) {
183 runner.total_assert_fails++
184 runner.assertion_info = *i
185}
186