mirror of
https://github.com/catchorg/Catch2.git
synced 2025-09-21 12:05:39 +02:00

Previously it could be just plain reporter name, e.g. `xml`, but it could not specify other reporter options. This change is not particularly useful for the built-in reporters, as it mostly comes in handy for combining specific custom reporter with custom arguments, and the built-in reporters do not have those.
250 lines
10 KiB
C++
250 lines
10 KiB
C++
|
|
// Copyright Catch2 Authors
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// (See accompanying file LICENSE.txt or copy at
|
|
// https://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
// SPDX-License-Identifier: BSL-1.0
|
|
#include <catch2/catch_config.hpp>
|
|
#include <catch2/catch_user_config.hpp>
|
|
#include <catch2/internal/catch_enforce.hpp>
|
|
#include <catch2/internal/catch_parse_numbers.hpp>
|
|
#include <catch2/internal/catch_stdstreams.hpp>
|
|
#include <catch2/internal/catch_stringref.hpp>
|
|
#include <catch2/internal/catch_string_manip.hpp>
|
|
#include <catch2/internal/catch_test_spec_parser.hpp>
|
|
#include <catch2/interfaces/catch_interfaces_tag_alias_registry.hpp>
|
|
#include <catch2/internal/catch_getenv.hpp>
|
|
|
|
#include <fstream>
|
|
|
|
namespace Catch {
|
|
|
|
namespace {
|
|
static bool enableBazelEnvSupport() {
|
|
#if defined( CATCH_CONFIG_BAZEL_SUPPORT )
|
|
return true;
|
|
#else
|
|
return Detail::getEnv( "BAZEL_TEST" ) != nullptr;
|
|
#endif
|
|
}
|
|
|
|
struct bazelShardingOptions {
|
|
unsigned int shardIndex, shardCount;
|
|
std::string shardFilePath;
|
|
};
|
|
|
|
static Optional<bazelShardingOptions> readBazelShardingOptions() {
|
|
const auto bazelShardIndex = Detail::getEnv( "TEST_SHARD_INDEX" );
|
|
const auto bazelShardTotal = Detail::getEnv( "TEST_TOTAL_SHARDS" );
|
|
const auto bazelShardInfoFile = Detail::getEnv( "TEST_SHARD_STATUS_FILE" );
|
|
|
|
|
|
const bool has_all =
|
|
bazelShardIndex && bazelShardTotal && bazelShardInfoFile;
|
|
if ( !has_all ) {
|
|
// We provide nice warning message if the input is
|
|
// misconfigured.
|
|
auto warn = []( const char* env_var ) {
|
|
Catch::cerr()
|
|
<< "Warning: Bazel shard configuration is missing '"
|
|
<< env_var << "'. Shard configuration is skipped.\n";
|
|
};
|
|
if ( !bazelShardIndex ) {
|
|
warn( "TEST_SHARD_INDEX" );
|
|
}
|
|
if ( !bazelShardTotal ) {
|
|
warn( "TEST_TOTAL_SHARDS" );
|
|
}
|
|
if ( !bazelShardInfoFile ) {
|
|
warn( "TEST_SHARD_STATUS_FILE" );
|
|
}
|
|
return {};
|
|
}
|
|
|
|
auto shardIndex = parseUInt( bazelShardIndex );
|
|
if ( !shardIndex ) {
|
|
Catch::cerr()
|
|
<< "Warning: could not parse 'TEST_SHARD_INDEX' ('" << bazelShardIndex
|
|
<< "') as unsigned int.\n";
|
|
return {};
|
|
}
|
|
auto shardTotal = parseUInt( bazelShardTotal );
|
|
if ( !shardTotal ) {
|
|
Catch::cerr()
|
|
<< "Warning: could not parse 'TEST_TOTAL_SHARD' ('"
|
|
<< bazelShardTotal << "') as unsigned int.\n";
|
|
return {};
|
|
}
|
|
|
|
return bazelShardingOptions{
|
|
*shardIndex, *shardTotal, bazelShardInfoFile };
|
|
|
|
}
|
|
} // end namespace
|
|
|
|
|
|
bool operator==( ProcessedReporterSpec const& lhs,
|
|
ProcessedReporterSpec const& rhs ) {
|
|
return lhs.name == rhs.name &&
|
|
lhs.outputFilename == rhs.outputFilename &&
|
|
lhs.colourMode == rhs.colourMode &&
|
|
lhs.customOptions == rhs.customOptions;
|
|
}
|
|
|
|
Config::Config( ConfigData const& data ):
|
|
m_data( data ) {
|
|
// We need to trim filter specs to avoid trouble with superfluous
|
|
// whitespace (esp. important for bdd macros, as those are manually
|
|
// aligned with whitespace).
|
|
|
|
for (auto& elem : m_data.testsOrTags) {
|
|
elem = trim(elem);
|
|
}
|
|
for (auto& elem : m_data.sectionsToRun) {
|
|
elem = trim(elem);
|
|
}
|
|
|
|
// Insert the default reporter if user hasn't asked for a specific one
|
|
if ( m_data.reporterSpecifications.empty() ) {
|
|
#if defined( CATCH_CONFIG_DEFAULT_REPORTER )
|
|
const auto default_spec = CATCH_CONFIG_DEFAULT_REPORTER;
|
|
#else
|
|
const auto default_spec = "console";
|
|
#endif
|
|
auto parsed = parseReporterSpec(default_spec);
|
|
CATCH_ENFORCE( parsed,
|
|
"Cannot parse the provided default reporter spec: '"
|
|
<< default_spec << '\'' );
|
|
m_data.reporterSpecifications.push_back( std::move( *parsed ) );
|
|
}
|
|
|
|
if ( enableBazelEnvSupport() ) {
|
|
readBazelEnvVars();
|
|
}
|
|
|
|
// Bazel support can modify the test specs, so parsing has to happen
|
|
// after reading Bazel env vars.
|
|
TestSpecParser parser( ITagAliasRegistry::get() );
|
|
if ( !m_data.testsOrTags.empty() ) {
|
|
m_hasTestFilters = true;
|
|
for ( auto const& testOrTags : m_data.testsOrTags ) {
|
|
parser.parse( testOrTags );
|
|
}
|
|
}
|
|
m_testSpec = parser.testSpec();
|
|
|
|
|
|
// We now fixup the reporter specs to handle default output spec,
|
|
// default colour spec, etc
|
|
bool defaultOutputUsed = false;
|
|
for ( auto const& reporterSpec : m_data.reporterSpecifications ) {
|
|
// We do the default-output check separately, while always
|
|
// using the default output below to make the code simpler
|
|
// and avoid superfluous copies.
|
|
if ( reporterSpec.outputFile().none() ) {
|
|
CATCH_ENFORCE( !defaultOutputUsed,
|
|
"Internal error: cannot use default output for "
|
|
"multiple reporters" );
|
|
defaultOutputUsed = true;
|
|
}
|
|
|
|
m_processedReporterSpecs.push_back( ProcessedReporterSpec{
|
|
reporterSpec.name(),
|
|
reporterSpec.outputFile() ? *reporterSpec.outputFile()
|
|
: data.defaultOutputFilename,
|
|
reporterSpec.colourMode().valueOr( data.defaultColourMode ),
|
|
reporterSpec.customOptions() } );
|
|
}
|
|
}
|
|
|
|
Config::~Config() = default;
|
|
|
|
|
|
bool Config::listTests() const { return m_data.listTests; }
|
|
bool Config::listTags() const { return m_data.listTags; }
|
|
bool Config::listReporters() const { return m_data.listReporters; }
|
|
bool Config::listListeners() const { return m_data.listListeners; }
|
|
|
|
std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
|
|
std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
|
|
|
|
std::vector<ReporterSpec> const& Config::getReporterSpecs() const {
|
|
return m_data.reporterSpecifications;
|
|
}
|
|
|
|
std::vector<ProcessedReporterSpec> const&
|
|
Config::getProcessedReporterSpecs() const {
|
|
return m_processedReporterSpecs;
|
|
}
|
|
|
|
TestSpec const& Config::testSpec() const { return m_testSpec; }
|
|
bool Config::hasTestFilters() const { return m_hasTestFilters; }
|
|
|
|
bool Config::showHelp() const { return m_data.showHelp; }
|
|
|
|
// IConfig interface
|
|
bool Config::allowThrows() const { return !m_data.noThrow; }
|
|
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
|
|
bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; }
|
|
bool Config::warnAboutMissingAssertions() const {
|
|
return !!( m_data.warnings & WarnAbout::NoAssertions );
|
|
}
|
|
bool Config::warnAboutUnmatchedTestSpecs() const {
|
|
return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec );
|
|
}
|
|
bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; }
|
|
ShowDurations Config::showDurations() const { return m_data.showDurations; }
|
|
double Config::minDuration() const { return m_data.minDuration; }
|
|
TestRunOrder Config::runOrder() const { return m_data.runOrder; }
|
|
uint32_t Config::rngSeed() const { return m_data.rngSeed; }
|
|
unsigned int Config::shardCount() const { return m_data.shardCount; }
|
|
unsigned int Config::shardIndex() const { return m_data.shardIndex; }
|
|
ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; }
|
|
bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; }
|
|
int Config::abortAfter() const { return m_data.abortAfter; }
|
|
bool Config::showInvisibles() const { return m_data.showInvisibles; }
|
|
Verbosity Config::verbosity() const { return m_data.verbosity; }
|
|
|
|
bool Config::skipBenchmarks() const { return m_data.skipBenchmarks; }
|
|
bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; }
|
|
unsigned int Config::benchmarkSamples() const { return m_data.benchmarkSamples; }
|
|
double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; }
|
|
unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; }
|
|
std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); }
|
|
|
|
void Config::readBazelEnvVars() {
|
|
// 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.
|
|
const auto bazelOutputFile = Detail::getEnv( "XML_OUTPUT_FILE" );
|
|
|
|
if ( bazelOutputFile ) {
|
|
m_data.reporterSpecifications.push_back(
|
|
{ "junit", std::string( bazelOutputFile ), {}, {} } );
|
|
}
|
|
|
|
const auto bazelTestSpec = Detail::getEnv( "TESTBRIDGE_TEST_ONLY" );
|
|
if ( bazelTestSpec ) {
|
|
// Presumably the test spec from environment should overwrite
|
|
// the one we got from CLI (if we got any)
|
|
m_data.testsOrTags.clear();
|
|
m_data.testsOrTags.push_back( bazelTestSpec );
|
|
}
|
|
|
|
const auto bazelShardOptions = readBazelShardingOptions();
|
|
if ( bazelShardOptions ) {
|
|
std::ofstream f( bazelShardOptions->shardFilePath,
|
|
std::ios_base::out | std::ios_base::trunc );
|
|
if ( f.is_open() ) {
|
|
f << "";
|
|
m_data.shardIndex = bazelShardOptions->shardIndex;
|
|
m_data.shardCount = bazelShardOptions->shardCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // end namespace Catch
|