Skip to content

Commit db21112

Browse files
author
Makhnev Petr
authored
tests: add a teamcity output format for V's test runner (#16681)
1 parent 3fa23b7 commit db21112

File tree

5 files changed

+201
-4
lines changed

5 files changed

+201
-4
lines changed

cmd/tools/modules/testing/common.v

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ fn (mut ts TestSession) handle_test_runner_option() {
292292
'dump' {
293293
ts.reporter = DumpReporter{}
294294
}
295+
'teamcity' {
296+
ts.reporter = TeamcityReporter{}
297+
}
295298
else {
296299
dump('just set ts.reporter to an instance of your own struct here')
297300
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
module testing
2+
3+
// TeamcityReporter implements the interface `testing.Reporter`.
4+
// It is used by `v -test-runner teamcity test .`
5+
pub struct TeamcityReporter {
6+
}
7+
8+
pub fn (r TeamcityReporter) session_start(message string, mut ts TestSession) {
9+
}
10+
11+
pub fn (r TeamcityReporter) session_stop(message string, mut ts TestSession) {
12+
}
13+
14+
pub fn (r TeamcityReporter) report(index int, message LogMessage) {
15+
name := r.get_test_suite_name_by_file(message.file)
16+
match message.kind {
17+
.cmd_begin {
18+
eprintln("##teamcity[testSuiteStarted name='${name}' flowId='${message.flow_id}']")
19+
}
20+
.cmd_end {
21+
eprintln("##teamcity[testSuiteFinished name='${name}' flowId='${message.flow_id}' duration='${message.took}']")
22+
}
23+
else {}
24+
}
25+
}
26+
27+
pub fn (r TeamcityReporter) get_test_suite_name_by_file(path string) string {
28+
file_name := path.replace('\\', '/').split('/').last()
29+
return file_name.split('.').first()
30+
}
31+
32+
pub fn (r TeamcityReporter) report_stop() {
33+
}
34+
35+
pub fn (r TeamcityReporter) progress(index int, message string) {
36+
}
37+
38+
pub fn (r TeamcityReporter) update_last_line(index int, message string) {
39+
}
40+
41+
pub fn (r TeamcityReporter) update_last_line_and_move_to_next(index int, message string) {
42+
}
43+
44+
pub fn (r TeamcityReporter) message(index int, message string) {
45+
}
46+
47+
pub fn (r TeamcityReporter) divider() {
48+
}
49+
50+
pub fn (r TeamcityReporter) worker_threads_start(files []string, mut ts TestSession) {
51+
}
52+
53+
pub fn (r TeamcityReporter) worker_threads_finish(mut ts TestSession) {
54+
}
55+
56+
pub fn (r TeamcityReporter) list_of_failed_commands(failed_cmds []string) {
57+
}

cmd/v/help/test.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ You can use several alternative test result formats, using `-test-runner name`,
3535
or by setting VTEST_RUNNER (the command line option has higher priority).
3636

3737
The names of the available test runners are:
38-
`simple` Fastest, does not import additional modules, does no processing.
39-
`tap` Format the output as required by the Test Anything Protocol (TAP).
40-
`normal` Supports color output, nicest/most human readable, the default.
38+
`simple` Fastest, does not import additional modules, does no processing.
39+
`tap` Format the output as required by the Test Anything Protocol (TAP).
40+
`normal` Supports color output, nicest/most human readable, the default.
41+
`teamcity` Format the output as required by the Teamcity and JetBrains IDEs.
4142

4243
You can also implement your own custom test runner, by providing the path to
4344
your .v file, that implements it to this option. For example, see:

vlib/v/pref/pref.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pub enum Arch {
9090
pub const list_of_flags_with_param = ['o', 'd', 'define', 'b', 'backend', 'cc', 'os', 'cf', 'cflags',
9191
'path', 'arch']
9292

93-
pub const supported_test_runners = ['normal', 'simple', 'tap', 'dump']
93+
pub const supported_test_runners = ['normal', 'simple', 'tap', 'dump', 'teamcity']
9494

9595
[heap; minify]
9696
pub struct Preferences {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
module main
2+
3+
import time
4+
5+
// Provide a Teamcity implementation of the TestRunner interface.
6+
// Used in Teamcity and JetBrains IDEs for nice test reporting.
7+
8+
fn vtest_init() {
9+
change_test_runner(&TestRunner(TeamcityTestRunner{}))
10+
}
11+
12+
struct TeamcityTestRunner {
13+
mut:
14+
fname string
15+
start_time time.Time
16+
17+
file_test_info VTestFileMetaInfo
18+
fn_test_info VTestFnMetaInfo
19+
fn_assert_passes u64
20+
fn_passes u64
21+
fn_fails u64
22+
23+
total_assert_passes u64
24+
total_assert_fails u64
25+
}
26+
27+
fn (mut runner TeamcityTestRunner) free() {
28+
unsafe {
29+
runner.fname.free()
30+
runner.fn_test_info.free()
31+
runner.file_test_info.free()
32+
}
33+
}
34+
35+
fn normalise_fname(name string) string {
36+
return name.replace('__', '.').replace('main.', '')
37+
}
38+
39+
fn (mut runner TeamcityTestRunner) start(ntests int) {
40+
}
41+
42+
fn (mut runner TeamcityTestRunner) finish() {
43+
}
44+
45+
fn (mut runner TeamcityTestRunner) exit_code() int {
46+
if runner.fn_fails > 0 {
47+
return 1
48+
}
49+
if runner.total_assert_fails > 0 {
50+
return 1
51+
}
52+
return 0
53+
}
54+
55+
//
56+
57+
fn (mut runner TeamcityTestRunner) fn_start() bool {
58+
runner.fn_assert_passes = 0
59+
runner.start_time = time.now()
60+
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+
return true
64+
}
65+
66+
fn (mut runner TeamcityTestRunner) fn_pass() {
67+
runner.fn_passes++
68+
duration := runner.test_duration()
69+
eprintln("##teamcity[testFinished name='${runner.fname}' duration='${duration}']")
70+
println('\n')
71+
}
72+
73+
fn (mut runner TeamcityTestRunner) fn_fail() {
74+
runner.fn_fails++
75+
duration := runner.test_duration()
76+
eprintln("##teamcity[testFailed name='${runner.fname}' duration='${duration}' message='assertion failed']")
77+
println('\n')
78+
}
79+
80+
fn (mut runner TeamcityTestRunner) fn_error(line_nr int, file string, mod string, fn_name string, errmsg string) {
81+
eprintln('>>> TeamcityTestRunner fn_error ${runner.fname}, line_nr: ${line_nr}, file: ${file}, mod: ${mod}, fn_name: ${fn_name}, errmsg: ${errmsg}')
82+
}
83+
84+
fn (mut runner TeamcityTestRunner) test_duration() i64 {
85+
return time.now().unix_time_milli() - runner.start_time.unix_time_milli()
86+
}
87+
88+
//
89+
90+
fn (mut runner TeamcityTestRunner) assert_pass(i &VAssertMetaInfo) {
91+
runner.total_assert_passes++
92+
runner.fn_assert_passes++
93+
94+
filepath := i.fpath.clone()
95+
println('>>> assert_pass ${filepath}:${i.line_nr + 1}')
96+
97+
unsafe { i.free() }
98+
}
99+
100+
fn (mut runner TeamcityTestRunner) assert_fail(i &VAssertMetaInfo) {
101+
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() }
136+
}

0 commit comments

Comments
 (0)