diff --git a/BUILD.bazel b/BUILD.bazel index 9b7d608dff..e29deaf48b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,13 +1,13 @@ # 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_COLOUR_ANSI": "", "#cmakedefine CATCH_CONFIG_COLOUR_NONE": "", "#cmakedefine CATCH_CONFIG_COLOUR_WINDOWS": "", @@ -57,23 +57,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 +# 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"], ) diff --git a/docs/configuration.md b/docs/configuration.md index a7db4b13ee..45242c17be 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -97,6 +97,10 @@ 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. + ## C++11 toggles CATCH_CONFIG_CPP11_TO_STRING // Use `std::to_string` diff --git a/src/catch2/catch_config.cpp b/src/catch2/catch_config.cpp index 8541d22338..44266ba2c9 100644 --- a/src/catch2/catch_config.cpp +++ b/src/catch2/catch_config.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -80,6 +81,26 @@ namespace Catch { } ); } +#if defined( CATCH_CONFIG_BAZEL_SUPPORT ) + // Register a JUnit reporter for Bazel. Bazel sets an enviorment + // 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( CATCH_PLATFORM_WINDOWS ) + // 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 ) { diff --git a/src/catch2/catch_user_config.hpp.in b/src/catch2/catch_user_config.hpp.in index b3a518f9ff..2ff0ba7b93 100644 --- a/src/catch2/catch_user_config.hpp.in +++ b/src/catch2/catch_user_config.hpp.in @@ -159,6 +159,7 @@ // ------ +#cmakedefine CATCH_CONFIG_BAZEL_SUPPORT #cmakedefine CATCH_CONFIG_COLOUR_ANSI #cmakedefine CATCH_CONFIG_COLOUR_NONE #cmakedefine CATCH_CONFIG_COLOUR_WINDOWS diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d5521a8440..218871c25a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -473,6 +473,10 @@ set_tests_properties("Reporters::DashAsLocationInReporterSpecSendsOutputToStdout ) if (CATCH_ENABLE_CONFIGURE_TESTS) + add_test(NAME "CMakeConfig::ConfigureBazelReporter" + COMMAND + "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_LIST_DIR}/TestScripts/testConfigureBazelReporter.py" "${CATCH_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" + ) add_test(NAME "CMakeConfig::DefaultReporter" COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_LIST_DIR}/TestScripts/testConfigureDefaultReporter.py" "${CATCH_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" diff --git a/tests/TestScripts/testConfigureBazelReporter.py b/tests/TestScripts/testConfigureBazelReporter.py new file mode 100644 index 0000000000..89f0cd1259 --- /dev/null +++ b/tests/TestScripts/testConfigureBazelReporter.py @@ -0,0 +1,90 @@ +#!/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 + +from ConfigureTestsCommon import configure_and_build, run_and_return_output + +import os +import re +import sys +import xml.etree.ElementTree as ET + +""" +Tests the CMake configure option for CATCH_CONFIG_DEFAULT_REPORTER + +Requires 2 arguments, path folder where the Catch2's main CMakeLists.txt +exists, and path to where the output files should be stored. +""" +if len(sys.argv) != 3: + print("Wrong number of arguments: {}".format(len(sys.argv))) + print("Usage: {} catch2-top-level-dir base-build-output-dir".format(sys.argv[0])) + exit(1) + +catch2_source_path = os.path.abspath(sys.argv[1]) +build_dir_path = os.path.join( + os.path.abspath(sys.argv[2]), "CMakeConfigTests", "ConfigureBazelReporter" +) + +configure_and_build( + catch2_source_path, build_dir_path, [("CATCH_CONFIG_BAZEL_SUPPORT", "ON")] +) + +xml_out_path = os.path.join( + build_dir_path, + "test.xml", +) + +# Ensure no file exists from previous test runs +if os.path.isfile(xml_out_path): + os.remove(xml_out_path) + + +os.environ["XML_OUTPUT_FILE"] = xml_out_path +stdout, _ = run_and_return_output( + os.path.join(build_dir_path, "tests"), + "SelfTest", + ["-s", "[approx][custom]"], +) + +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 + +testcases = tree.findall(".//testcase") +if len(testcases) != 1: + print( + "Unexpected number of testcases '{}' in '{}'".format( + len(testcases), xml_out_path + ) + ) + exit(2) + +expected = { + "classname": "SelfTest.global", + "name": "Use a custom approx", + "status": "run", +} +testcase = testcases[0] + +actual = testcase.attrib +for expect_key, expect_val in expected.items(): + if expect_key not in actual: + print("Key not found in testcase tags: '{}'".format(expect_key)) + exit(2) + if expect_val != actual[expect_key]: + print( + "Value mismatch in testcase tags '{}' != '{}' for key '{}'".format( + expect_val, actual[expect_key], expect_key + ) + ) + exit(2)