Skip to content

Commit e6d8a3a

Browse files
kvakiltargos
authored andcommitted
[hack] build: convert V8 test JSON to JUnit XML
This introduces some code to convert from V8's test JSON output to JUnit XML. We need this because V8's latest refactor of their test runner has made it difficult to float our JUnit reporter patch on top (see the referenced issue). I also think that there needs to be the same changes to vcbuild.bat, but I don't know how to do test those yet. I can create a Windows VM and test it if we decide to go with this approach. Refs: nodejs/node-v8#236
1 parent 1cd87b3 commit e6d8a3a

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ tools/*/*.i.tmp
117117
# === Rules for test artifacts ===
118118
/*.tap
119119
/*.xml
120+
/v8*-tap.json
120121
/node_trace.*.log
121122
# coverage related
122123
/gcovr

Makefile

+24
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,27 @@ ifdef ENABLE_V8_TAP
3333
TAP_V8 := --junitout $(PWD)/v8-tap.xml
3434
TAP_V8_INTL := --junitout $(PWD)/v8-intl-tap.xml
3535
TAP_V8_BENCHMARKS := --junitout $(PWD)/v8-benchmarks-tap.xml
36+
define convert_to_junit
37+
@true
38+
endef
39+
endif
40+
41+
ifdef ENABLE_CONVERT_V8_JSON_TO_XML
42+
TAP_V8_JSON := $(PWD)/v8-tap.json
43+
TAP_V8_INTL_JSON := $(PWD)/v8-intl-tap.json
44+
TAP_V8_BENCHMARKS_JSON := $(PWD)/v8-benchmarks-tap.json
45+
46+
# By default, the V8's JSON test output only includes the tests which have
47+
# failed. We use --slow-tests-cutoff to ensure that all tests are present
48+
# in the output, including those which pass.
49+
TAP_V8 := --json-test-results $(TAP_V8_JSON) --slow-tests-cutoff 1000000
50+
TAP_V8_INTL := --json-test-results $(TAP_V8_INTL_JSON) --slow-tests-cutoff 1000000
51+
TAP_V8_BENCHMARKS := --json-test-results $(TAP_V8_BENCHMARKS_JSON) --slow-tests-cutoff 1000000
52+
53+
define convert_to_junit
54+
export PATH="$(NO_BIN_OVERRIDE_PATH)" && \
55+
$(PYTHON) tools/v8-json-to-junit.py < $(1) > $(1:.json=.xml)
56+
endef
3657
endif
3758

3859
V8_TEST_OPTIONS = $(V8_EXTRA_TEST_OPTIONS)
@@ -683,6 +704,7 @@ test-v8: v8 ## Runs the V8 test suite on deps/v8.
683704
$(PYTHON) deps/v8/tools/run-tests.py --gn --arch=$(V8_ARCH) $(V8_TEST_OPTIONS) \
684705
mjsunit cctest debugger inspector message preparser \
685706
$(TAP_V8)
707+
$(call convert_to_junit,$(TAP_V8_JSON))
686708
$(info Testing hash seed)
687709
$(MAKE) test-hash-seed
688710

@@ -691,12 +713,14 @@ test-v8-intl: v8
691713
$(PYTHON) deps/v8/tools/run-tests.py --gn --arch=$(V8_ARCH) \
692714
intl \
693715
$(TAP_V8_INTL)
716+
$(call convert_to_junit,$(TAP_V8_INTL_JSON))
694717

695718
test-v8-benchmarks: v8
696719
export PATH="$(NO_BIN_OVERRIDE_PATH)" && \
697720
$(PYTHON) deps/v8/tools/run-tests.py --gn --arch=$(V8_ARCH) \
698721
benchmarks \
699722
$(TAP_V8_BENCHMARKS)
723+
$(call convert_to_junit,$(TAP_V8_BENCHMARKS_JSON))
700724

701725
test-v8-updates:
702726
$(PYTHON) tools/test.py $(PARALLEL_ARGS) --mode=$(BUILDTYPE_LOWER) v8-updates

tools/test-v8.bat

+9-6
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,21 @@ if errorlevel 1 set ERROR_STATUS=1&goto test-v8-exit
2020
set path=%savedpath%
2121

2222
if not defined test_v8 goto test-v8-intl
23-
echo running 'python tools\run-tests.py %common_v8_test_options% %v8_test_options% --junitout ./v8-tap.xml'
24-
call python tools\run-tests.py %common_v8_test_options% %v8_test_options% --junitout ./v8-tap.xml
23+
echo running 'python tools\run-tests.py %common_v8_test_options% %v8_test_options% --slow-tests-cutoff 1000000 --json-test-results v8-tap.xml'
24+
call python tools\run-tests.py %common_v8_test_options% %v8_test_options% --slow-tests-cutoff 1000000 --json-test-results v8-tap.xml
25+
call python ..\..\tools\v8-json-to-junit.py < v8-tap.xml > v8-tap.json
2526

2627
:test-v8-intl
2728
if not defined test_v8_intl goto test-v8-benchmarks
28-
echo running 'python tools\run-tests.py %common_v8_test_options% intl --junitout ./v8-intl-tap.xml'
29-
call python tools\run-tests.py %common_v8_test_options% intl --junitout ./v8-intl-tap.xml
29+
echo running 'python tools\run-tests.py %common_v8_test_options% intl --slow-tests-cutoff 1000000 --json-test-results v8-intl-tap.xml'
30+
call python tools\run-tests.py %common_v8_test_options% intl --slow-tests-cutoff 1000000 --json-test-results ./v8-intl-tap.xml
31+
call python ..\..\tools\v8-json-to-junit.py < v8-intl-tap.xml > v8-intl-tap.json
3032

3133
:test-v8-benchmarks
3234
if not defined test_v8_benchmarks goto test-v8-exit
33-
echo running 'python tools\run-tests.py %common_v8_test_options% benchmarks --junitout ./v8-benchmarks-tap.xml'
34-
call python tools\run-tests.py %common_v8_test_options% benchmarks --junitout ./v8-benchmarks-tap.xml
35+
echo running 'python tools\run-tests.py %common_v8_test_options% benchmarks --slow-tests-cutoff 1000000 --json-test-results v8-benchmarks-tap.xml'
36+
call python tools\run-tests.py %common_v8_test_options% benchmarks --slow-tests-cutoff 1000000 --json-test-results ./v8-benchmarks-tap.xml
37+
call python ..\..\tools\v8-json-to-junit.py < v8-benchmarks-tap.xml > v8-benchmarks-tap.json
3538
goto test-v8-exit
3639

3740
:test-v8-exit

tools/v8-json-to-junit.py

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python
2+
# Large parts of this file are modified from
3+
# deps/v8/tools/testrunner/local/junit_output.py, which no longer exists in
4+
# latest V8.
5+
#
6+
# Copyright 2013 the V8 project authors. All rights reserved.
7+
# Redistribution and use in source and binary forms, with or without
8+
# modification, are permitted provided that the following conditions are
9+
# met:
10+
#
11+
# * Redistributions of source code must retain the above copyright
12+
# notice, this list of conditions and the following disclaimer.
13+
# * Redistributions in binary form must reproduce the above
14+
# copyright notice, this list of conditions and the following
15+
# disclaimer in the documentation and/or other materials provided
16+
# with the distribution.
17+
# * Neither the name of Google Inc. nor the names of its
18+
# contributors may be used to endorse or promote products derived
19+
# from this software without specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
33+
import json
34+
import utils
35+
import signal
36+
import sys
37+
import xml.etree.ElementTree as xml
38+
39+
def IsExitCodeCrashing(exit_code):
40+
if utils.IsWindows():
41+
return 0x80000000 & exit_code and not (0x3FFFFF00 & exit_code)
42+
return exit_code < 0 and exit_code != -signal.SIGABRT
43+
44+
45+
class JUnitTestOutput:
46+
def __init__(self, test_suite_name):
47+
self.root = xml.Element("testsuite")
48+
self.root.attrib["name"] = test_suite_name
49+
50+
def HasRunTest(self, test_name, test_cmd, test_duration, test_failure):
51+
test_case_element = xml.Element("testcase")
52+
test_case_element.attrib["name"] = test_name
53+
test_case_element.attrib["cmd"] = test_cmd
54+
test_case_element.attrib["time"] = str(round(test_duration, 3))
55+
if test_failure is not None:
56+
failure_element = xml.Element("failure")
57+
failure_element.text = test_failure
58+
test_case_element.append(failure_element)
59+
self.root.append(test_case_element)
60+
61+
def FinishAndWrite(self, f):
62+
xml.ElementTree(self.root).write(f, "UTF-8")
63+
64+
65+
def Main():
66+
test_results = json.load(sys.stdin)
67+
68+
# V8's JSON test runner only logs failing and flaky tests under "results". We
69+
# assume the caller has put a large number for --slow-tests-cutoff, to ensure
70+
# that all the tests appear under "slowest_tests".
71+
72+
failing_tests = {result["name"]: result for result in test_results["results"]}
73+
all_tests = {result["name"]: result for result in test_results["slowest_tests"]}
74+
passing_tests = {
75+
name: result for name, result in all_tests.items() if name not in failing_tests
76+
}
77+
78+
# These check that --slow-tests-cutoff was passed correctly.
79+
assert len(failing_tests) + len(passing_tests) == len(all_tests)
80+
assert len(all_tests) == len(test_results["slowest_tests"])
81+
82+
output = JUnitTestOutput("v8tests")
83+
84+
for name, failing_test in failing_tests.items():
85+
failing_output = []
86+
87+
stdout = failing_test["stdout"].strip()
88+
if len(stdout):
89+
failing_output.append("stdout:")
90+
failing_output.append(stdout)
91+
92+
stderr = failing_test["stderr"].strip()
93+
if len(stderr):
94+
failing_output.append("stderr:")
95+
failing_output.append(stderr)
96+
97+
failing_output.append("Command: " + failing_test["command"])
98+
99+
exit_code = failing_test["exit_code"]
100+
if failing_test["result"] == "TIMEOUT":
101+
failing_output.append("--- TIMEOUT ---")
102+
elif IsExitCodeCrashing(exit_code):
103+
failing_output.append("exit code: " + str(exit_code))
104+
failing_output.append("--- CRASHED ---")
105+
106+
output.HasRunTest(
107+
test_name=name,
108+
test_cmd=failing_test["command"],
109+
test_duration=failing_test["duration"],
110+
test_failure="\n".join(failing_output),
111+
)
112+
113+
for name, passing_test in passing_tests.items():
114+
output.HasRunTest(
115+
test_name=name,
116+
test_cmd=passing_test["command"],
117+
test_duration=passing_test["duration"],
118+
test_failure=None,
119+
)
120+
121+
output.FinishAndWrite(sys.stdout.buffer)
122+
123+
if __name__ == '__main__':
124+
Main()

0 commit comments

Comments
 (0)