Skip to content

Commit 4b73ef1

Browse files
Add default reporter for Bazel integration
When the added Bazel configuration flag is enabled, a default JUnit reporter will be added if the XML envrioment variable is defined. Fix include paths for generated config header. Enable Bazel config by default when building with Bazel.
1 parent 797c3e7 commit 4b73ef1

9 files changed

+191
-15
lines changed

BUILD.bazel

+28-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# Load the cc_library rule.
22
load("@rules_cc//cc:defs.bzl", "cc_library")
3-
43
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
54

65
expand_template(
7-
name = "catch_user_config.hpp",
6+
name = "catch_user_config",
87
out = "catch2/catch_user_config.hpp",
98
substitutions = {
109
"#cmakedefine CATCH_CONFIG_ANDROID_LOGWRITE": "",
10+
"#cmakedefine CATCH_CONFIG_BAZEL_SUPPORT": "#define CATCH_CONFIG_BAZEL_SUPPORT",
11+
"#cmakedefine CATCH_CONFIG_NO_COLOUR_WIN32": "",
1112
"#cmakedefine CATCH_CONFIG_COLOUR_WIN32": "",
1213
"#cmakedefine CATCH_CONFIG_COUNTER": "",
1314
"#cmakedefine CATCH_CONFIG_CPP11_TO_STRING": "",
@@ -55,23 +56,39 @@ expand_template(
5556
template = "src/catch2/catch_user_config.hpp.in",
5657
)
5758

59+
# Generated header library, modifies the include prefix to account for
60+
# generation path so that we can include <catch2/catch_user_config.hpp>
61+
# correctly.
62+
cc_library(
63+
name = "catch2_generated",
64+
hdrs = ["catch2/catch_user_config.hpp"],
65+
copts = ["-std=c++14"],
66+
include_prefix = ".", # to manipulate -I of dependenices
67+
visibility = ["//visibility:public"],
68+
)
69+
5870
# Static library, without main.
5971
cc_library(
6072
name = "catch2",
61-
hdrs = glob(["src/catch2/**/*.hpp"]) + ["catch_user_config.hpp"],
62-
srcs = glob(["src/catch2/**/*.cpp"],
63-
exclude=[ "src/catch2/internal/catch_main.cpp"]),
64-
visibility = ["//visibility:public"],
65-
linkstatic = True,
73+
srcs = glob(
74+
["src/catch2/**/*.cpp"],
75+
exclude = ["src/catch2/internal/catch_main.cpp"],
76+
),
77+
hdrs = glob(["src/catch2/**/*.hpp"]),
78+
copts = ["-std=c++14"],
6679
includes = ["src/"],
80+
linkstatic = True,
81+
visibility = ["//visibility:public"],
82+
deps = [":catch2_generated"],
6783
)
6884

6985
# Static library, with main.
7086
cc_library(
7187
name = "catch2_main",
7288
srcs = ["src/catch2/internal/catch_main.cpp"],
73-
deps = [":catch2"],
74-
visibility = ["//visibility:public"],
75-
linkstatic = True,
89+
copts = ["-std=c++14"],
7690
includes = ["src/"],
77-
)
91+
linkstatic = True,
92+
visibility = ["//visibility:public"],
93+
deps = [":catch2"],
94+
)

CMake/CatchConfigOptions.cmake

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ endmacro()
2626

2727
set(_OverridableOptions
2828
"ANDROID_LOGWRITE"
29+
"BAZEL_SUPPORT"
2930
"COLOUR_WIN32"
3031
"COUNTER"
3132
"CPP11_TO_STRING"

WORKSPACE

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
2+
23
http_archive(
34
name = "bazel_skylib",
5+
strip_prefix = "bazel-skylib-2a87d4a62af886fb320883aba102255aba87275e",
46
urls = [
5-
"https://github.com/Vertexwahn/bazel-skylib/archive/b0cd4bbd4bf4af76c380e1f8fafdbe3964161aff.tar.gz",
7+
"https://github.com/bazelbuild/bazel-skylib/archive/2a87d4a62af886fb320883aba102255aba87275e.tar.gz",
68
],
7-
strip_prefix = "bazel-skylib-b0cd4bbd4bf4af76c380e1f8fafdbe3964161aff",
8-
sha256 = "e57f3ff541c65678f3c2b344c73945531838e86ea0be71c63eea862ab43e792b",
9+
sha256 = "d847b08d6702d2779e9eb399b54ff8920fa7521dc45e3e53572d1d8907767de7",
910
)
11+
1012
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
11-
bazel_skylib_workspace()
13+
14+
bazel_skylib_workspace()

docs/configuration.md

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
[stdout](#stdout)<br>
99
[Fallback stringifier](#fallback-stringifier)<br>
1010
[Default reporter](#default-reporter)<br>
11+
[Bazel support](#bazel-support)<br>
1112
[C++11 toggles](#c11-toggles)<br>
1213
[C++17 toggles](#c17-toggles)<br>
1314
[Other toggles](#other-toggles)<br>
@@ -96,6 +97,12 @@ This means that defining `CATCH_CONFIG_DEFAULT_REPORTER` to `"console"`
9697
is equivalent with the out-of-the-box experience.
9798

9899

100+
## Bazel support
101+
When `CATCH_CONFIG_BAZEL_SUPPORT` is defined, Catch2 will register a `JUnit`
102+
reporter writing to a path pointed by `XML_OUTPUT_FILE` provided by Bazel.
103+
104+
> `CATCH_CONFIG_BAZEL_SUPPORT` was [introduced](https://github.com/catchorg/Catch2/pull/2399) in Catch2 3.0.0.
105+
99106
## C++11 toggles
100107

101108
CATCH_CONFIG_CPP11_TO_STRING // Use `std::to_string`

src/catch2/catch_config.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <catch2/catch_config.hpp>
99
#include <catch2/catch_user_config.hpp>
1010
#include <catch2/internal/catch_enforce.hpp>
11+
#include <catch2/internal/catch_platform.hpp>
1112
#include <catch2/internal/catch_stream.hpp>
1213
#include <catch2/internal/catch_stringref.hpp>
1314
#include <catch2/internal/catch_string_manip.hpp>
@@ -80,6 +81,26 @@ namespace Catch {
8081
} );
8182
}
8283

84+
#if defined( CATCH_CONFIG_BAZEL_SUPPORT )
85+
// Register a JUnit reporter for Bazel. Bazel sets an environment
86+
// variable with the path to XML output. If this file is written to
87+
// during test, Bazel will not generate a default XML output.
88+
// This allows the XML output file to contain higher level of detail
89+
// than what is possible otherwise.
90+
#if defined( _MSC_VER )
91+
// On Windows getenv throws a warning as there is no input validation,
92+
// since the key is hardcoded, this should not be an issue.
93+
#pragma warning(suppress: 4996)
94+
auto const bazelOutputFile = std::getenv( "XML_OUTPUT_FILE" );
95+
#else
96+
auto const bazelOutputFile = std::getenv( "XML_OUTPUT_FILE" );
97+
#endif
98+
if ( bazelOutputFile != nullptr ) {
99+
m_data.reporterSpecifications.push_back(
100+
{ "junit", { bazelOutputFile } } );
101+
}
102+
#endif
103+
83104
bool defaultOutputUsed = false;
84105
m_reporterStreams.reserve( m_data.reporterSpecifications.size() );
85106
for ( auto const& reporterAndFile : m_data.reporterSpecifications ) {

src/catch2/catch_user_config.hpp.in

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
// ------
166166

167167

168+
#cmakedefine CATCH_CONFIG_BAZEL_SUPPORT
168169
#cmakedefine CATCH_CONFIG_DISABLE_EXCEPTIONS
169170
#cmakedefine CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER
170171
#cmakedefine CATCH_CONFIG_DISABLE

tests/ExtraTests/CMakeLists.txt

+8
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ set_tests_properties(
121121
FAIL_REGULAR_EXPRESSION "abort;terminate;fatal"
122122
)
123123

124+
add_executable( BazelReporter ${TESTS_DIR}/X29-BazelReporter.cpp )
125+
target_compile_definitions( BazelReporter PRIVATE CATCH_CONFIG_BAZEL_SUPPORT )
126+
target_link_libraries(BazelReporter Catch2_buildall_interface)
127+
add_test(NAME CATCH_CONFIG_BAZEL_REPORTER-1
128+
COMMAND
129+
"${PYTHON_EXECUTABLE}" "${CATCH_DIR}/tests/TestScripts/testBazelReporter.py" "${CMAKE_CURRENT_BINARY_DIR}" "BazelReporter"
130+
)
131+
124132

125133
# The default handler on Windows leads to the just-in-time debugger firing,
126134
# which makes this test unsuitable for CI and headless runs, as it opens
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
// Copyright Catch2 Authors
3+
// Distributed under the Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt or copy at
5+
// https://www.boost.org/LICENSE_1_0.txt)
6+
7+
// SPDX-License-Identifier: BSL-1.0
8+
9+
/**\file
10+
* Test the Bazel report functionality with a simple set
11+
* of dummy test cases.
12+
*/
13+
14+
#include <catch2/catch_test_macros.hpp>
15+
#include <iostream>
16+
17+
TEST_CASE( "Passing test case" ) { REQUIRE( 1 == 1 ); }
18+
TEST_CASE( "Failing test case" ) { REQUIRE( 2 == 1 ); }
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright Catch2 Authors
4+
# Distributed under the Boost Software License, Version 1.0.
5+
# (See accompanying file LICENSE_1_0.txt or copy at
6+
# https://www.boost.org/LICENSE_1_0.txt)
7+
8+
# SPDX-License-Identifier: BSL-1.0
9+
10+
import os
11+
import re
12+
import sys
13+
import xml.etree.ElementTree as ET
14+
import subprocess
15+
16+
"""
17+
Tests the CMake configure option for CATCH_CONFIG_DEFAULT_REPORTER
18+
19+
Requires 2 arguments, path to where the output files should be stored
20+
and the name of the test
21+
"""
22+
if len(sys.argv) != 3:
23+
print("Wrong number of arguments: {}".format(len(sys.argv)))
24+
print("Usage: {} bin-path bin-name".format(sys.argv[0]))
25+
exit(1)
26+
27+
28+
bin_path = os.path.abspath(sys.argv[1])
29+
bin_name = sys.argv[2]
30+
xml_out_path = os.path.join(bin_path, "{}.xml".format(bin_name))
31+
config_path = "Debug" if os.name == "nt" else ""
32+
33+
# Ensure no file exists from previous test runs
34+
if os.path.isfile(xml_out_path):
35+
os.remove(xml_out_path)
36+
37+
args = [os.path.join(bin_path, config_path, bin_name)]
38+
env = os.environ.copy()
39+
env["XML_OUTPUT_FILE"] = xml_out_path
40+
test_passing = True
41+
42+
try:
43+
ret = subprocess.run(
44+
args,
45+
stdout=subprocess.PIPE,
46+
stderr=subprocess.PIPE,
47+
check=True,
48+
universal_newlines=True,
49+
env=env
50+
)
51+
stdout = ret.stdout
52+
except subprocess.SubprocessError as ex:
53+
if ex.returncode == 1:
54+
# The test cases are allowed to fail.
55+
test_passing = False
56+
stdout = ex.stdout
57+
else:
58+
print('Could not run "{}"'.format(args))
59+
print("Return code: {}".format(ex.returncode))
60+
print("stdout: {}".format(ex.stdout))
61+
print("stderr: {}".format(ex.stdout))
62+
raise
63+
64+
# Check for valid XML output
65+
try:
66+
tree = ET.parse(xml_out_path)
67+
except ET.ParseError as ex:
68+
print("Invalid XML: '{}'".format(ex))
69+
raise
70+
except FileNotFoundError as ex:
71+
print("Could not find '{}'".format(xml_out_path))
72+
raise
73+
74+
# Check for matching testsuite
75+
if not tree.find('.//testsuite[@name="{}"]'.format(bin_name)):
76+
print("Could not find '{}' testsuite".format(bin_name))
77+
exit(2)
78+
79+
summary_test_cases = re.findall(r'test cases: \d* \| \d* passed \| \d* failed', stdout)
80+
if len(summary_test_cases) == 0:
81+
print("Could not find test summary in {}".format(stdout))
82+
exit(2)
83+
84+
total, passed, failed = [int(s) for s in summary_test_cases[0].split() if s.isdigit()]
85+
86+
if failed == 0 and not test_passing:
87+
print("Expected at least 1 test failure!")
88+
exit(2)
89+
90+
if len(tree.findall('.//testcase')) != total:
91+
print("Unexpected number of test cases!")
92+
exit(2)
93+
94+
if len(tree.findall('.//failure')) != failed:
95+
print("Unexpected number of test failures!")
96+
exit(2)
97+
98+
if (passed + failed) != total:
99+
print("Something has gone very wrong, ({} + {}) != {}".format(passed, failed, total))
100+
exit(2)

0 commit comments

Comments
 (0)