Skip to content

Commit 59befa9

Browse files
committed
tests: optionally write results as JUnit-style .xml
This will come in handy when publishing the results of Git's test suite during an automated Azure DevOps run. Note: we need to make extra sure that invalid UTF-8 encoding is turned into valid UTF-8 (using the Replacement Character, \uFFFD) because t9902's trace contains such invalid byte sequences, and the task in the Azure Pipeline that uploads the test results would refuse to do anything if it was asked to parse an .xml file with invalid UTF-8 in it. Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0f9fdff commit 59befa9

File tree

6 files changed

+175
-0
lines changed

6 files changed

+175
-0
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ TEST_BUILTINS_OBJS += test-submodule-config.o
754754
TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
755755
TEST_BUILTINS_OBJS += test-subprocess.o
756756
TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
757+
TEST_BUILTINS_OBJS += test-xml-encode.o
757758
TEST_BUILTINS_OBJS += test-wildmatch.o
758759
TEST_BUILTINS_OBJS += test-windows-named-pipe.o
759760
TEST_BUILTINS_OBJS += test-write-cache.o

t/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/test-results
33
/.prove
44
/chainlinttmp
5+
/out/

t/helper/test-tool.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ static struct test_cmd cmds[] = {
4949
{ "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
5050
{ "subprocess", cmd__subprocess },
5151
{ "urlmatch-normalization", cmd__urlmatch_normalization },
52+
{ "xml-encode", cmd__xml_encode },
5253
{ "wildmatch", cmd__wildmatch },
5354
#ifdef GIT_WINDOWS_NATIVE
5455
{ "windows-named-pipe", cmd__windows_named_pipe },

t/helper/test-tool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ int cmd__submodule_config(int argc, const char **argv);
4545
int cmd__submodule_nested_repo_config(int argc, const char **argv);
4646
int cmd__subprocess(int argc, const char **argv);
4747
int cmd__urlmatch_normalization(int argc, const char **argv);
48+
int cmd__xml_encode(int argc, const char **argv);
4849
int cmd__wildmatch(int argc, const char **argv);
4950
#ifdef GIT_WINDOWS_NATIVE
5051
int cmd__windows_named_pipe(int argc, const char **argv);

t/helper/test-xml-encode.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include "test-tool.h"
2+
3+
static const char *utf8_replace_character = "&#xfffd;";
4+
5+
/*
6+
* Encodes (possibly incorrect) UTF-8 on <stdin> to <stdout>, to be embedded
7+
* in an XML file.
8+
*/
9+
int cmd__xml_encode(int argc, const char **argv)
10+
{
11+
unsigned char buf[1024], tmp[4], *tmp2 = NULL;
12+
ssize_t cur = 0, len = 1, remaining = 0;
13+
unsigned char ch;
14+
15+
for (;;) {
16+
if (++cur == len) {
17+
len = xread(0, buf, sizeof(buf));
18+
if (!len)
19+
return 0;
20+
if (len < 0)
21+
die_errno("Could not read <stdin>");
22+
cur = 0;
23+
}
24+
ch = buf[cur];
25+
26+
if (tmp2) {
27+
if ((ch & 0xc0) != 0x80) {
28+
fputs(utf8_replace_character, stdout);
29+
tmp2 = NULL;
30+
cur--;
31+
continue;
32+
}
33+
*tmp2 = ch;
34+
tmp2++;
35+
if (--remaining == 0) {
36+
fwrite(tmp, tmp2 - tmp, 1, stdout);
37+
tmp2 = NULL;
38+
}
39+
continue;
40+
}
41+
42+
if (!(ch & 0x80)) {
43+
/* 0xxxxxxx */
44+
if (ch == '&')
45+
fputs("&amp;", stdout);
46+
else if (ch == '\'')
47+
fputs("&apos;", stdout);
48+
else if (ch == '"')
49+
fputs("&quot;", stdout);
50+
else if (ch == '<')
51+
fputs("&lt;", stdout);
52+
else if (ch == '>')
53+
fputs("&gt;", stdout);
54+
else if (ch >= 0x20)
55+
fputc(ch, stdout);
56+
else if (ch == 0x09 || ch == 0x0a || ch == 0x0d)
57+
fprintf(stdout, "&#x%02x;", ch);
58+
else
59+
fputs(utf8_replace_character, stdout);
60+
} else if ((ch & 0xe0) == 0xc0) {
61+
/* 110XXXXx 10xxxxxx */
62+
tmp[0] = ch;
63+
remaining = 1;
64+
tmp2 = tmp + 1;
65+
} else if ((ch & 0xf0) == 0xe0) {
66+
/* 1110XXXX 10Xxxxxx 10xxxxxx */
67+
tmp[0] = ch;
68+
remaining = 2;
69+
tmp2 = tmp + 1;
70+
} else if ((ch & 0xf8) == 0xf0) {
71+
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
72+
tmp[0] = ch;
73+
remaining = 3;
74+
tmp2 = tmp + 1;
75+
} else
76+
fputs(utf8_replace_character, stdout);
77+
}
78+
79+
return 0;
80+
}

t/test-lib.sh

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ do
339339
-V|--verbose-log)
340340
verbose_log=t
341341
shift ;;
342+
--write-junit-xml)
343+
write_junit_xml=t
344+
shift ;;
342345
*)
343346
echo "error: unknown test option '$1'" >&2; exit 1 ;;
344347
esac
@@ -486,11 +489,24 @@ trap 'exit $?' INT
486489
# the test_expect_* functions instead.
487490

488491
test_ok_ () {
492+
if test -n "$write_junit_xml"
493+
then
494+
write_junit_xml_testcase "$*"
495+
fi
489496
test_success=$(($test_success + 1))
490497
say_color "" "ok $test_count - $@"
491498
}
492499

493500
test_failure_ () {
501+
if test -n "$write_junit_xml"
502+
then
503+
junit_insert="<failure message=\"not ok $test_count -"
504+
junit_insert="$junit_insert $(xml_attr_encode "$1")\">"
505+
junit_insert="$junit_insert $(xml_attr_encode \
506+
"$(printf '%s\n' "$@" | sed 1d)")"
507+
junit_insert="$junit_insert</failure>"
508+
write_junit_xml_testcase "$1" " $junit_insert"
509+
fi
494510
test_failure=$(($test_failure + 1))
495511
say_color error "not ok $test_count - $1"
496512
shift
@@ -499,11 +515,19 @@ test_failure_ () {
499515
}
500516

501517
test_known_broken_ok_ () {
518+
if test -n "$write_junit_xml"
519+
then
520+
write_junit_xml_testcase "$* (breakage fixed)"
521+
fi
502522
test_fixed=$(($test_fixed+1))
503523
say_color error "ok $test_count - $@ # TODO known breakage vanished"
504524
}
505525

506526
test_known_broken_failure_ () {
527+
if test -n "$write_junit_xml"
528+
then
529+
write_junit_xml_testcase "$* (known breakage)"
530+
fi
507531
test_broken=$(($test_broken+1))
508532
say_color warn "not ok $test_count - $@ # TODO known breakage"
509533
}
@@ -761,6 +785,10 @@ test_start_ () {
761785
test_count=$(($test_count+1))
762786
maybe_setup_verbose
763787
maybe_setup_valgrind
788+
if test -n "$write_junit_xml"
789+
then
790+
junit_start=$(test-tool date getnanos)
791+
fi
764792
}
765793

766794
test_finish_ () {
@@ -798,6 +826,13 @@ test_skip () {
798826

799827
case "$to_skip" in
800828
t)
829+
if test -n "$write_junit_xml"
830+
then
831+
message="$(xml_attr_encode "$skipped_reason")"
832+
write_junit_xml_testcase "$1" \
833+
" <skipped message=\"$message\" />"
834+
fi
835+
801836
say_color skip >&3 "skipping test: $@"
802837
say_color skip "ok $test_count # skip $1 ($skipped_reason)"
803838
: true
@@ -813,9 +848,51 @@ test_at_end_hook_ () {
813848
:
814849
}
815850

851+
write_junit_xml () {
852+
case "$1" in
853+
--truncate)
854+
>"$junit_xml_path"
855+
junit_have_testcase=
856+
shift
857+
;;
858+
esac
859+
printf '%s\n' "$@" >>"$junit_xml_path"
860+
}
861+
862+
xml_attr_encode () {
863+
printf '%s\n' "$@" | test-tool xml-encode
864+
}
865+
866+
write_junit_xml_testcase () {
867+
junit_attrs="name=\"$(xml_attr_encode "$this_test.$test_count $1")\""
868+
shift
869+
junit_attrs="$junit_attrs classname=\"$this_test\""
870+
junit_attrs="$junit_attrs time=\"$(test-tool \
871+
date getnanos $junit_start)\""
872+
write_junit_xml "$(printf '%s\n' \
873+
" <testcase $junit_attrs>" "$@" " </testcase>")"
874+
junit_have_testcase=t
875+
}
876+
816877
test_done () {
817878
GIT_EXIT_OK=t
818879

880+
if test -n "$write_junit_xml" && test -n "$junit_xml_path"
881+
then
882+
test -n "$junit_have_testcase" || {
883+
junit_start=$(test-tool date getnanos)
884+
write_junit_xml_testcase "all tests skipped"
885+
}
886+
887+
# adjust the overall time
888+
junit_time=$(test-tool date getnanos $junit_suite_start)
889+
sed "s/<testsuite [^>]*/& time=\"$junit_time\"/" \
890+
<"$junit_xml_path" >"$junit_xml_path.new"
891+
mv "$junit_xml_path.new" "$junit_xml_path"
892+
893+
write_junit_xml " </testsuite>" "</testsuites>"
894+
fi
895+
819896
if test -z "$HARNESS_ACTIVE"
820897
then
821898
test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results"
@@ -1051,6 +1128,7 @@ then
10511128
else
10521129
mkdir -p "$TRASH_DIRECTORY"
10531130
fi
1131+
10541132
# Use -P to resolve symlinks in our working directory so that the cwd
10551133
# in subprocesses like git equals our $PWD (for pathname comparisons).
10561134
cd -P "$TRASH_DIRECTORY" || exit 1
@@ -1064,6 +1142,19 @@ then
10641142
test_done
10651143
fi
10661144

1145+
if test -n "$write_junit_xml"
1146+
then
1147+
junit_xml_dir="$TEST_OUTPUT_DIRECTORY/out"
1148+
mkdir -p "$junit_xml_dir"
1149+
junit_xml_base=${0##*/}
1150+
junit_xml_path="$junit_xml_dir/TEST-${junit_xml_base%.sh}.xml"
1151+
junit_attrs="name=\"${junit_xml_base%.sh}\""
1152+
junit_attrs="$junit_attrs timestamp=\"$(TZ=UTC \
1153+
date +%Y-%m-%dT%H:%M:%S)\""
1154+
write_junit_xml --truncate "<testsuites>" " <testsuite $junit_attrs>"
1155+
junit_suite_start=$(test-tool date getnanos)
1156+
fi
1157+
10671158
# Provide an implementation of the 'yes' utility
10681159
yes () {
10691160
if test $# = 0

0 commit comments

Comments
 (0)