Skip to content

Commit

Permalink
Add default reporter for Bazel integration
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lukokr-aarch64 committed Apr 5, 2022
1 parent 797c3e7 commit 4b73ef1
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 15 deletions.
39 changes: 28 additions & 11 deletions BUILD.bazel
@@ -1,13 +1,14 @@
# Load the cc_library rule.
load("@rules_cc//cc:defs.bzl", "cc_library")

load("@bazel_skylib//rules:expand_template.bzl", "expand_template")

expand_template(
name = "catch_user_config.hpp",
name = "catch_user_config",
out = "catch2/catch_user_config.hpp",
substitutions = {
"#cmakedefine CATCH_CONFIG_ANDROID_LOGWRITE": "",
"#cmakedefine CATCH_CONFIG_BAZEL_SUPPORT": "#define CATCH_CONFIG_BAZEL_SUPPORT",
"#cmakedefine CATCH_CONFIG_NO_COLOUR_WIN32": "",
"#cmakedefine CATCH_CONFIG_COLOUR_WIN32": "",
"#cmakedefine CATCH_CONFIG_COUNTER": "",
"#cmakedefine CATCH_CONFIG_CPP11_TO_STRING": "",
Expand Down Expand Up @@ -55,23 +56,39 @@ expand_template(
template = "src/catch2/catch_user_config.hpp.in",
)

# Generated header library, modifies the include prefix to account for
# generation path so that we can include <catch2/catch_user_config.hpp>
# correctly.
cc_library(
name = "catch2_generated",
hdrs = ["catch2/catch_user_config.hpp"],
copts = ["-std=c++14"],
include_prefix = ".", # to manipulate -I of dependenices
visibility = ["//visibility:public"],
)

# Static library, without main.
cc_library(
name = "catch2",
hdrs = glob(["src/catch2/**/*.hpp"]) + ["catch_user_config.hpp"],
srcs = glob(["src/catch2/**/*.cpp"],
exclude=[ "src/catch2/internal/catch_main.cpp"]),
visibility = ["//visibility:public"],
linkstatic = True,
srcs = glob(
["src/catch2/**/*.cpp"],
exclude = ["src/catch2/internal/catch_main.cpp"],
),
hdrs = glob(["src/catch2/**/*.hpp"]),
copts = ["-std=c++14"],
includes = ["src/"],
linkstatic = True,
visibility = ["//visibility:public"],
deps = [":catch2_generated"],
)

# Static library, with main.
cc_library(
name = "catch2_main",
srcs = ["src/catch2/internal/catch_main.cpp"],
deps = [":catch2"],
visibility = ["//visibility:public"],
linkstatic = True,
copts = ["-std=c++14"],
includes = ["src/"],
)
linkstatic = True,
visibility = ["//visibility:public"],
deps = [":catch2"],
)
1 change: 1 addition & 0 deletions CMake/CatchConfigOptions.cmake
Expand Up @@ -26,6 +26,7 @@ endmacro()

set(_OverridableOptions
"ANDROID_LOGWRITE"
"BAZEL_SUPPORT"
"COLOUR_WIN32"
"COUNTER"
"CPP11_TO_STRING"
Expand Down
11 changes: 7 additions & 4 deletions WORKSPACE
@@ -1,11 +1,14 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
name = "bazel_skylib",
strip_prefix = "bazel-skylib-2a87d4a62af886fb320883aba102255aba87275e",
urls = [
"https://github.com/Vertexwahn/bazel-skylib/archive/b0cd4bbd4bf4af76c380e1f8fafdbe3964161aff.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/archive/2a87d4a62af886fb320883aba102255aba87275e.tar.gz",
],
strip_prefix = "bazel-skylib-b0cd4bbd4bf4af76c380e1f8fafdbe3964161aff",
sha256 = "e57f3ff541c65678f3c2b344c73945531838e86ea0be71c63eea862ab43e792b",
sha256 = "d847b08d6702d2779e9eb399b54ff8920fa7521dc45e3e53572d1d8907767de7",
)

load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
bazel_skylib_workspace()

bazel_skylib_workspace()
7 changes: 7 additions & 0 deletions docs/configuration.md
Expand Up @@ -8,6 +8,7 @@
[stdout](#stdout)<br>
[Fallback stringifier](#fallback-stringifier)<br>
[Default reporter](#default-reporter)<br>
[Bazel support](#bazel-support)<br>
[C++11 toggles](#c11-toggles)<br>
[C++17 toggles](#c17-toggles)<br>
[Other toggles](#other-toggles)<br>
Expand Down Expand Up @@ -96,6 +97,12 @@ This means that defining `CATCH_CONFIG_DEFAULT_REPORTER` to `"console"`
is equivalent with the out-of-the-box experience.


## Bazel support
When `CATCH_CONFIG_BAZEL_SUPPORT` is defined, Catch2 will register a `JUnit`
reporter writing to a path pointed by `XML_OUTPUT_FILE` provided by Bazel.

> `CATCH_CONFIG_BAZEL_SUPPORT` was [introduced](https://github.com/catchorg/Catch2/pull/2399) in Catch2 3.0.0.
## C++11 toggles

CATCH_CONFIG_CPP11_TO_STRING // Use `std::to_string`
Expand Down
21 changes: 21 additions & 0 deletions src/catch2/catch_config.cpp
Expand Up @@ -8,6 +8,7 @@
#include <catch2/catch_config.hpp>
#include <catch2/catch_user_config.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_platform.hpp>
#include <catch2/internal/catch_stream.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_string_manip.hpp>
Expand Down Expand Up @@ -80,6 +81,26 @@ namespace Catch {
} );
}

#if defined( CATCH_CONFIG_BAZEL_SUPPORT )
// Register a JUnit reporter for Bazel. Bazel sets an environment
// variable with the path to XML output. If this file is written to
// during test, Bazel will not generate a default XML output.
// This allows the XML output file to contain higher level of detail
// than what is possible otherwise.
#if defined( _MSC_VER )
// On Windows getenv throws a warning as there is no input validation,
// since the key is hardcoded, this should not be an issue.
#pragma warning(suppress: 4996)
auto const bazelOutputFile = std::getenv( "XML_OUTPUT_FILE" );
#else
auto const bazelOutputFile = std::getenv( "XML_OUTPUT_FILE" );
#endif
if ( bazelOutputFile != nullptr ) {
m_data.reporterSpecifications.push_back(
{ "junit", { bazelOutputFile } } );
}
#endif

bool defaultOutputUsed = false;
m_reporterStreams.reserve( m_data.reporterSpecifications.size() );
for ( auto const& reporterAndFile : m_data.reporterSpecifications ) {
Expand Down
1 change: 1 addition & 0 deletions src/catch2/catch_user_config.hpp.in
Expand Up @@ -165,6 +165,7 @@
// ------


#cmakedefine CATCH_CONFIG_BAZEL_SUPPORT
#cmakedefine CATCH_CONFIG_DISABLE_EXCEPTIONS
#cmakedefine CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER
#cmakedefine CATCH_CONFIG_DISABLE
Expand Down
8 changes: 8 additions & 0 deletions tests/ExtraTests/CMakeLists.txt
Expand Up @@ -121,6 +121,14 @@ set_tests_properties(
FAIL_REGULAR_EXPRESSION "abort;terminate;fatal"
)

add_executable( BazelReporter ${TESTS_DIR}/X29-BazelReporter.cpp )
target_compile_definitions( BazelReporter PRIVATE CATCH_CONFIG_BAZEL_SUPPORT )
target_link_libraries(BazelReporter Catch2_buildall_interface)
add_test(NAME CATCH_CONFIG_BAZEL_REPORTER-1
COMMAND
"${PYTHON_EXECUTABLE}" "${CATCH_DIR}/tests/TestScripts/testBazelReporter.py" "${CMAKE_CURRENT_BINARY_DIR}" "BazelReporter"
)


# The default handler on Windows leads to the just-in-time debugger firing,
# which makes this test unsuitable for CI and headless runs, as it opens
Expand Down
18 changes: 18 additions & 0 deletions tests/ExtraTests/X29-BazelReporter.cpp
@@ -0,0 +1,18 @@

// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0

/**\file
* Test the Bazel report functionality with a simple set
* of dummy test cases.
*/

#include <catch2/catch_test_macros.hpp>
#include <iostream>

TEST_CASE( "Passing test case" ) { REQUIRE( 1 == 1 ); }
TEST_CASE( "Failing test case" ) { REQUIRE( 2 == 1 ); }
100 changes: 100 additions & 0 deletions tests/TestScripts/testBazelReporter.py
@@ -0,0 +1,100 @@
#!/usr/bin/env python3

# Copyright Catch2 Authors
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# https://www.boost.org/LICENSE_1_0.txt)

# SPDX-License-Identifier: BSL-1.0

import os
import re
import sys
import xml.etree.ElementTree as ET
import subprocess

"""
Tests the CMake configure option for CATCH_CONFIG_DEFAULT_REPORTER
Requires 2 arguments, path to where the output files should be stored
and the name of the test
"""
if len(sys.argv) != 3:
print("Wrong number of arguments: {}".format(len(sys.argv)))
print("Usage: {} bin-path bin-name".format(sys.argv[0]))
exit(1)


bin_path = os.path.abspath(sys.argv[1])
bin_name = sys.argv[2]
xml_out_path = os.path.join(bin_path, "{}.xml".format(bin_name))
config_path = "Debug" if os.name == "nt" else ""

# Ensure no file exists from previous test runs
if os.path.isfile(xml_out_path):
os.remove(xml_out_path)

args = [os.path.join(bin_path, config_path, bin_name)]
env = os.environ.copy()
env["XML_OUTPUT_FILE"] = xml_out_path
test_passing = True

try:
ret = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
universal_newlines=True,
env=env
)
stdout = ret.stdout
except subprocess.SubprocessError as ex:
if ex.returncode == 1:
# The test cases are allowed to fail.
test_passing = False
stdout = ex.stdout
else:
print('Could not run "{}"'.format(args))
print("Return code: {}".format(ex.returncode))
print("stdout: {}".format(ex.stdout))
print("stderr: {}".format(ex.stdout))
raise

# Check for valid XML output
try:
tree = ET.parse(xml_out_path)
except ET.ParseError as ex:
print("Invalid XML: '{}'".format(ex))
raise
except FileNotFoundError as ex:
print("Could not find '{}'".format(xml_out_path))
raise

# Check for matching testsuite
if not tree.find('.//testsuite[@name="{}"]'.format(bin_name)):
print("Could not find '{}' testsuite".format(bin_name))
exit(2)

summary_test_cases = re.findall(r'test cases: \d* \| \d* passed \| \d* failed', stdout)
if len(summary_test_cases) == 0:
print("Could not find test summary in {}".format(stdout))
exit(2)

total, passed, failed = [int(s) for s in summary_test_cases[0].split() if s.isdigit()]

if failed == 0 and not test_passing:
print("Expected at least 1 test failure!")
exit(2)

if len(tree.findall('.//testcase')) != total:
print("Unexpected number of test cases!")
exit(2)

if len(tree.findall('.//failure')) != failed:
print("Unexpected number of test failures!")
exit(2)

if (passed + failed) != total:
print("Something has gone very wrong, ({} + {}) != {}".format(passed, failed, total))
exit(2)

0 comments on commit 4b73ef1

Please sign in to comment.