11639 lines
405 KiB
C++
11639 lines
405 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
|
|
|
|
// Catch v3.6.0
|
|
// Generated: 2024-05-05 20:53:27.562886
|
|
// ----------------------------------------------------------
|
|
// This file is an amalgamation of multiple different files.
|
|
// You probably shouldn't edit it directly.
|
|
// ----------------------------------------------------------
|
|
|
|
#include "catch_amalgamated.hpp"
|
|
|
|
|
|
#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED
|
|
#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED
|
|
|
|
|
|
#if defined(CATCH_PLATFORM_WINDOWS)
|
|
|
|
// We might end up with the define made globally through the compiler,
|
|
// and we don't want to trigger warnings for this
|
|
#if !defined(NOMINMAX)
|
|
# define NOMINMAX
|
|
#endif
|
|
#if !defined(WIN32_LEAN_AND_MEAN)
|
|
# define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
|
|
#include <windows.h>
|
|
|
|
#endif // defined(CATCH_PLATFORM_WINDOWS)
|
|
|
|
#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
namespace Benchmark {
|
|
namespace Detail {
|
|
ChronometerConcept::~ChronometerConcept() = default;
|
|
} // namespace Detail
|
|
} // namespace Benchmark
|
|
} // namespace Catch
|
|
|
|
|
|
// Adapted from donated nonius code.
|
|
|
|
|
|
#include <vector>
|
|
|
|
namespace Catch {
|
|
namespace Benchmark {
|
|
namespace Detail {
|
|
SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last) {
|
|
if (!cfg.benchmarkNoAnalysis()) {
|
|
std::vector<double> samples;
|
|
samples.reserve(static_cast<size_t>(last - first));
|
|
for (auto current = first; current != last; ++current) {
|
|
samples.push_back( current->count() );
|
|
}
|
|
|
|
auto analysis = Catch::Benchmark::Detail::analyse_samples(
|
|
cfg.benchmarkConfidenceInterval(),
|
|
cfg.benchmarkResamples(),
|
|
samples.data(),
|
|
samples.data() + samples.size() );
|
|
auto outliers = Catch::Benchmark::Detail::classify_outliers(
|
|
samples.data(), samples.data() + samples.size() );
|
|
|
|
auto wrap_estimate = [](Estimate<double> e) {
|
|
return Estimate<FDuration> {
|
|
FDuration(e.point),
|
|
FDuration(e.lower_bound),
|
|
FDuration(e.upper_bound),
|
|
e.confidence_interval,
|
|
};
|
|
};
|
|
std::vector<FDuration> samples2;
|
|
samples2.reserve(samples.size());
|
|
for (auto s : samples) {
|
|
samples2.push_back( FDuration( s ) );
|
|
}
|
|
|
|
return {
|
|
CATCH_MOVE(samples2),
|
|
wrap_estimate(analysis.mean),
|
|
wrap_estimate(analysis.standard_deviation),
|
|
outliers,
|
|
analysis.outlier_variance,
|
|
};
|
|
} else {
|
|
std::vector<FDuration> samples;
|
|
samples.reserve(static_cast<size_t>(last - first));
|
|
|
|
FDuration mean = FDuration(0);
|
|
int i = 0;
|
|
for (auto it = first; it < last; ++it, ++i) {
|
|
samples.push_back(*it);
|
|
mean += *it;
|
|
}
|
|
mean /= i;
|
|
|
|
return SampleAnalysis{
|
|
CATCH_MOVE(samples),
|
|
Estimate<FDuration>{ mean, mean, mean, 0.0 },
|
|
Estimate<FDuration>{ FDuration( 0 ),
|
|
FDuration( 0 ),
|
|
FDuration( 0 ),
|
|
0.0 },
|
|
OutlierClassification{},
|
|
0.0
|
|
};
|
|
}
|
|
}
|
|
} // namespace Detail
|
|
} // namespace Benchmark
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
namespace Benchmark {
|
|
namespace Detail {
|
|
BenchmarkFunction::callable::~callable() = default;
|
|
} // namespace Detail
|
|
} // namespace Benchmark
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
#include <exception>
|
|
|
|
namespace Catch {
|
|
namespace Benchmark {
|
|
namespace Detail {
|
|
struct optimized_away_error : std::exception {
|
|
const char* what() const noexcept override;
|
|
};
|
|
|
|
const char* optimized_away_error::what() const noexcept {
|
|
return "could not measure benchmark, maybe it was optimized away";
|
|
}
|
|
|
|
void throw_optimized_away_error() {
|
|
Catch::throw_exception(optimized_away_error{});
|
|
}
|
|
|
|
} // namespace Detail
|
|
} // namespace Benchmark
|
|
} // namespace Catch
|
|
|
|
|
|
// Adapted from donated nonius code.
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <numeric>
|
|
#include <random>
|
|
|
|
|
|
#if defined(CATCH_CONFIG_USE_ASYNC)
|
|
#include <future>
|
|
#endif
|
|
|
|
namespace Catch {
|
|
namespace Benchmark {
|
|
namespace Detail {
|
|
namespace {
|
|
|
|
template <typename URng, typename Estimator>
|
|
static sample
|
|
resample( URng& rng,
|
|
unsigned int resamples,
|
|
double const* first,
|
|
double const* last,
|
|
Estimator& estimator ) {
|
|
auto n = static_cast<size_t>( last - first );
|
|
Catch::uniform_integer_distribution<size_t> dist( 0, n - 1 );
|
|
|
|
sample out;
|
|
out.reserve( resamples );
|
|
std::vector<double> resampled;
|
|
resampled.reserve( n );
|
|
for ( size_t i = 0; i < resamples; ++i ) {
|
|
resampled.clear();
|
|
for ( size_t s = 0; s < n; ++s ) {
|
|
resampled.push_back( first[dist( rng )] );
|
|
}
|
|
const auto estimate =
|
|
estimator( resampled.data(), resampled.data() + resampled.size() );
|
|
out.push_back( estimate );
|
|
}
|
|
std::sort( out.begin(), out.end() );
|
|
return out;
|
|
}
|
|
|
|
static double outlier_variance( Estimate<double> mean,
|
|
Estimate<double> stddev,
|
|
int n ) {
|
|
double sb = stddev.point;
|
|
double mn = mean.point / n;
|
|
double mg_min = mn / 2.;
|
|
double sg = (std::min)( mg_min / 4., sb / std::sqrt( n ) );
|
|
double sg2 = sg * sg;
|
|
double sb2 = sb * sb;
|
|
|
|
auto c_max = [n, mn, sb2, sg2]( double x ) -> double {
|
|
double k = mn - x;
|
|
double d = k * k;
|
|
double nd = n * d;
|
|
double k0 = -n * nd;
|
|
double k1 = sb2 - n * sg2 + nd;
|
|
double det = k1 * k1 - 4 * sg2 * k0;
|
|
return static_cast<int>( -2. * k0 /
|
|
( k1 + std::sqrt( det ) ) );
|
|
};
|
|
|
|
auto var_out = [n, sb2, sg2]( double c ) {
|
|
double nc = n - c;
|
|
return ( nc / n ) * ( sb2 - nc * sg2 );
|
|
};
|
|
|
|
return (std::min)( var_out( 1 ),
|
|
var_out(
|
|
(std::min)( c_max( 0. ),
|
|
c_max( mg_min ) ) ) ) /
|
|
sb2;
|
|
}
|
|
|
|
static double erf_inv( double x ) {
|
|
// Code accompanying the article "Approximating the erfinv
|
|
// function" in GPU Computing Gems, Volume 2
|
|
double w, p;
|
|
|
|
w = -log( ( 1.0 - x ) * ( 1.0 + x ) );
|
|
|
|
if ( w < 6.250000 ) {
|
|
w = w - 3.125000;
|
|
p = -3.6444120640178196996e-21;
|
|
p = -1.685059138182016589e-19 + p * w;
|
|
p = 1.2858480715256400167e-18 + p * w;
|
|
p = 1.115787767802518096e-17 + p * w;
|
|
p = -1.333171662854620906e-16 + p * w;
|
|
p = 2.0972767875968561637e-17 + p * w;
|
|
p = 6.6376381343583238325e-15 + p * w;
|
|
p = -4.0545662729752068639e-14 + p * w;
|
|
p = -8.1519341976054721522e-14 + p * w;
|
|
p = 2.6335093153082322977e-12 + p * w;
|
|
p = -1.2975133253453532498e-11 + p * w;
|
|
p = -5.4154120542946279317e-11 + p * w;
|
|
p = 1.051212273321532285e-09 + p * w;
|
|
p = -4.1126339803469836976e-09 + p * w;
|
|
p = -2.9070369957882005086e-08 + p * w;
|
|
p = 4.2347877827932403518e-07 + p * w;
|
|
p = -1.3654692000834678645e-06 + p * w;
|
|
p = -1.3882523362786468719e-05 + p * w;
|
|
p = 0.0001867342080340571352 + p * w;
|
|
p = -0.00074070253416626697512 + p * w;
|
|
p = -0.0060336708714301490533 + p * w;
|
|
p = 0.24015818242558961693 + p * w;
|
|
p = 1.6536545626831027356 + p * w;
|
|
} else if ( w < 16.000000 ) {
|
|
w = sqrt( w ) - 3.250000;
|
|
p = 2.2137376921775787049e-09;
|
|
p = 9.0756561938885390979e-08 + p * w;
|
|
p = -2.7517406297064545428e-07 + p * w;
|
|
p = 1.8239629214389227755e-08 + p * w;
|
|
p = 1.5027403968909827627e-06 + p * w;
|
|
p = -4.013867526981545969e-06 + p * w;
|
|
p = 2.9234449089955446044e-06 + p * w;
|
|
p = 1.2475304481671778723e-05 + p * w;
|
|
p = -4.7318229009055733981e-05 + p * w;
|
|
p = 6.8284851459573175448e-05 + p * w;
|
|
p = 2.4031110387097893999e-05 + p * w;
|
|
p = -0.0003550375203628474796 + p * w;
|
|
p = 0.00095328937973738049703 + p * w;
|
|
p = -0.0016882755560235047313 + p * w;
|
|
p = 0.0024914420961078508066 + p * w;
|
|
p = -0.0037512085075692412107 + p * w;
|
|
p = 0.005370914553590063617 + p * w;
|
|
p = 1.0052589676941592334 + p * w;
|
|
p = 3.0838856104922207635 + p * w;
|
|
} else {
|
|
w = sqrt( w ) - 5.000000;
|
|
p = -2.7109920616438573243e-11;
|
|
p = -2.5556418169965252055e-10 + p * w;
|
|
p = 1.5076572693500548083e-09 + p * w;
|
|
p = -3.7894654401267369937e-09 + p * w;
|
|
p = 7.6157012080783393804e-09 + p * w;
|
|
p = -1.4960026627149240478e-08 + p * w;
|
|
p = 2.9147953450901080826e-08 + p * w;
|
|
p = -6.7711997758452339498e-08 + p * w;
|
|
p = 2.2900482228026654717e-07 + p * w;
|
|
p = -9.9298272942317002539e-07 + p * w;
|
|
p = 4.5260625972231537039e-06 + p * w;
|
|
p = -1.9681778105531670567e-05 + p * w;
|
|
p = 7.5995277030017761139e-05 + p * w;
|
|
p = -0.00021503011930044477347 + p * w;
|
|
p = -0.00013871931833623122026 + p * w;
|
|
p = 1.0103004648645343977 + p * w;
|
|
p = 4.8499064014085844221 + p * w;
|
|
}
|
|
return p * x;
|
|
}
|
|
|
|
static double
|
|
standard_deviation( double const* first, double const* last ) {
|
|
auto m = Catch::Benchmark::Detail::mean( first, last );
|
|
double variance =
|
|
std::accumulate( first,
|
|
last,
|
|
0.,
|
|
[m]( double a, double b ) {
|
|
double diff = b - m;
|
|
return a + diff * diff;
|
|
} ) /
|
|
( last - first );
|
|
return std::sqrt( variance );
|
|
}
|
|
|
|
static sample jackknife( double ( *estimator )( double const*,
|
|
double const* ),
|
|
double* first,
|
|
double* last ) {
|
|
const auto second = first + 1;
|
|
sample results;
|
|
results.reserve( static_cast<size_t>( last - first ) );
|
|
|
|
for ( auto it = first; it != last; ++it ) {
|
|
std::iter_swap( it, first );
|
|
results.push_back( estimator( second, last ) );
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
|
|
} // namespace
|
|
} // namespace Detail
|
|
} // namespace Benchmark
|
|
} // namespace Catch
|
|
|
|
namespace Catch {
|
|
namespace Benchmark {
|
|
namespace Detail {
|
|
|
|
double weighted_average_quantile( int k,
|
|
int q,
|
|
double* first,
|
|
double* last ) {
|
|
auto count = last - first;
|
|
double idx = (count - 1) * k / static_cast<double>(q);
|
|
int j = static_cast<int>(idx);
|
|
double g = idx - j;
|
|
std::nth_element(first, first + j, last);
|
|
auto xj = first[j];
|
|
if ( Catch::Detail::directCompare( g, 0 ) ) {
|
|
return xj;
|
|
}
|
|
|
|
auto xj1 = *std::min_element(first + (j + 1), last);
|
|
return xj + g * (xj1 - xj);
|
|
}
|
|
|
|
OutlierClassification
|
|
classify_outliers( double const* first, double const* last ) {
|
|
std::vector<double> copy( first, last );
|
|
|
|
auto q1 = weighted_average_quantile( 1, 4, copy.data(), copy.data() + copy.size() );
|
|
auto q3 = weighted_average_quantile( 3, 4, copy.data(), copy.data() + copy.size() );
|
|
auto iqr = q3 - q1;
|
|
auto los = q1 - ( iqr * 3. );
|
|
auto lom = q1 - ( iqr * 1.5 );
|
|
auto him = q3 + ( iqr * 1.5 );
|
|
auto his = q3 + ( iqr * 3. );
|
|
|
|
OutlierClassification o;
|
|
for ( ; first != last; ++first ) {
|
|
const double t = *first;
|
|
if ( t < los ) {
|
|
++o.low_severe;
|
|
} else if ( t < lom ) {
|
|
++o.low_mild;
|
|
} else if ( t > his ) {
|
|
++o.high_severe;
|
|
} else if ( t > him ) {
|
|
++o.high_mild;
|
|
}
|
|
++o.samples_seen;
|
|
}
|
|
return o;
|
|
}
|
|
|
|
double mean( double const* first, double const* last ) {
|
|
auto count = last - first;
|
|
double sum = 0.;
|
|
while (first != last) {
|
|
sum += *first;
|
|
++first;
|
|
}
|
|
return sum / static_cast<double>(count);
|
|
}
|
|
|
|
double normal_cdf( double x ) {
|
|
return std::erfc( -x / std::sqrt( 2.0 ) ) / 2.0;
|
|
}
|
|
|
|
double erfc_inv(double x) {
|
|
return erf_inv(1.0 - x);
|
|
}
|
|
|
|
double normal_quantile(double p) {
|
|
static const double ROOT_TWO = std::sqrt(2.0);
|
|
|
|
double result = 0.0;
|
|
assert(p >= 0 && p <= 1);
|
|
if (p < 0 || p > 1) {
|
|
return result;
|
|
}
|
|
|
|
result = -erfc_inv(2.0 * p);
|
|
// result *= normal distribution standard deviation (1.0) * sqrt(2)
|
|
result *= /*sd * */ ROOT_TWO;
|
|
// result += normal disttribution mean (0)
|
|
return result;
|
|
}
|
|
|
|
Estimate<double>
|
|
bootstrap( double confidence_level,
|
|
double* first,
|
|
double* last,
|
|
sample const& resample,
|
|
double ( *estimator )( double const*, double const* ) ) {
|
|
auto n_samples = last - first;
|
|
|
|
double point = estimator( first, last );
|
|
// Degenerate case with a single sample
|
|
if ( n_samples == 1 )
|
|
return { point, point, point, confidence_level };
|
|
|
|
sample jack = jackknife( estimator, first, last );
|
|
double jack_mean =
|
|
mean( jack.data(), jack.data() + jack.size() );
|
|
double sum_squares = 0, sum_cubes = 0;
|
|
for ( double x : jack ) {
|
|
auto difference = jack_mean - x;
|
|
auto square = difference * difference;
|
|
auto cube = square * difference;
|
|
sum_squares += square;
|
|
sum_cubes += cube;
|
|
}
|
|
|
|
double accel = sum_cubes / ( 6 * std::pow( sum_squares, 1.5 ) );
|
|
long n = static_cast<long>( resample.size() );
|
|
double prob_n =
|
|
std::count_if( resample.begin(),
|
|
resample.end(),
|
|
[point]( double x ) { return x < point; } ) /
|
|
static_cast<double>( n );
|
|
// degenerate case with uniform samples
|
|
if ( Catch::Detail::directCompare( prob_n, 0. ) ) {
|
|
return { point, point, point, confidence_level };
|
|
}
|
|
|
|
double bias = normal_quantile( prob_n );
|
|
double z1 = normal_quantile( ( 1. - confidence_level ) / 2. );
|
|
|
|
auto cumn = [n]( double x ) -> long {
|
|
return std::lround( normal_cdf( x ) *
|
|
static_cast<double>( n ) );
|
|
};
|
|
auto a = [bias, accel]( double b ) {
|
|
return bias + b / ( 1. - accel * b );
|
|
};
|
|
double b1 = bias + z1;
|
|
double b2 = bias - z1;
|
|
double a1 = a( b1 );
|
|
double a2 = a( b2 );
|
|
auto lo = static_cast<size_t>( (std::max)( cumn( a1 ), 0l ) );
|
|
auto hi =
|
|
static_cast<size_t>( (std::min)( cumn( a2 ), n - 1 ) );
|
|
|
|
return { point, resample[lo], resample[hi], confidence_level };
|
|
}
|
|
|
|
bootstrap_analysis analyse_samples(double confidence_level,
|
|
unsigned int n_resamples,
|
|
double* first,
|
|
double* last) {
|
|
auto mean = &Detail::mean;
|
|
auto stddev = &standard_deviation;
|
|
|
|
#if defined(CATCH_CONFIG_USE_ASYNC)
|
|
auto Estimate = [=](double(*f)(double const*, double const*)) {
|
|
std::random_device rd;
|
|
auto seed = rd();
|
|
return std::async(std::launch::async, [=] {
|
|
SimplePcg32 rng( seed );
|
|
auto resampled = resample(rng, n_resamples, first, last, f);
|
|
return bootstrap(confidence_level, first, last, resampled, f);
|
|
});
|
|
};
|
|
|
|
auto mean_future = Estimate(mean);
|
|
auto stddev_future = Estimate(stddev);
|
|
|
|
auto mean_estimate = mean_future.get();
|
|
auto stddev_estimate = stddev_future.get();
|
|
#else
|
|
auto Estimate = [=](double(*f)(double const* , double const*)) {
|
|
std::random_device rd;
|
|
auto seed = rd();
|
|
SimplePcg32 rng( seed );
|
|
auto resampled = resample(rng, n_resamples, first, last, f);
|
|
return bootstrap(confidence_level, first, last, resampled, f);
|
|
};
|
|
|
|
auto mean_estimate = Estimate(mean);
|
|
auto stddev_estimate = Estimate(stddev);
|
|
#endif // CATCH_USE_ASYNC
|
|
|
|
auto n = static_cast<int>(last - first); // seriously, one can't use integral types without hell in C++
|
|
double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n);
|
|
|
|
return { mean_estimate, stddev_estimate, outlier_variance };
|
|
}
|
|
} // namespace Detail
|
|
} // namespace Benchmark
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
|
|
namespace {
|
|
|
|
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
|
|
// But without the subtraction to allow for INFINITY in comparison
|
|
bool marginComparison(double lhs, double rhs, double margin) {
|
|
return (lhs + margin >= rhs) && (rhs + margin >= lhs);
|
|
}
|
|
|
|
}
|
|
|
|
namespace Catch {
|
|
|
|
Approx::Approx ( double value )
|
|
: m_epsilon( static_cast<double>(std::numeric_limits<float>::epsilon())*100. ),
|
|
m_margin( 0.0 ),
|
|
m_scale( 0.0 ),
|
|
m_value( value )
|
|
{}
|
|
|
|
Approx Approx::custom() {
|
|
return Approx( 0 );
|
|
}
|
|
|
|
Approx Approx::operator-() const {
|
|
auto temp(*this);
|
|
temp.m_value = -temp.m_value;
|
|
return temp;
|
|
}
|
|
|
|
|
|
std::string Approx::toString() const {
|
|
ReusableStringStream rss;
|
|
rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )";
|
|
return rss.str();
|
|
}
|
|
|
|
bool Approx::equalityComparisonImpl(const double other) const {
|
|
// First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
|
|
// Thanks to Richard Harris for his help refining the scaled margin value
|
|
return marginComparison(m_value, other, m_margin)
|
|
|| marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value)));
|
|
}
|
|
|
|
void Approx::setMargin(double newMargin) {
|
|
CATCH_ENFORCE(newMargin >= 0,
|
|
"Invalid Approx::margin: " << newMargin << '.'
|
|
<< " Approx::Margin has to be non-negative.");
|
|
m_margin = newMargin;
|
|
}
|
|
|
|
void Approx::setEpsilon(double newEpsilon) {
|
|
CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0,
|
|
"Invalid Approx::epsilon: " << newEpsilon << '.'
|
|
<< " Approx::epsilon has to be in [0, 1]");
|
|
m_epsilon = newEpsilon;
|
|
}
|
|
|
|
namespace literals {
|
|
Approx operator ""_a(long double val) {
|
|
return Approx(val);
|
|
}
|
|
Approx operator ""_a(unsigned long long val) {
|
|
return Approx(val);
|
|
}
|
|
} // end namespace literals
|
|
|
|
std::string StringMaker<Catch::Approx>::convert(Catch::Approx const& value) {
|
|
return value.toString();
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
namespace Catch {
|
|
|
|
AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression):
|
|
lazyExpression(_lazyExpression),
|
|
resultType(_resultType) {}
|
|
|
|
std::string AssertionResultData::reconstructExpression() const {
|
|
|
|
if( reconstructedExpression.empty() ) {
|
|
if( lazyExpression ) {
|
|
ReusableStringStream rss;
|
|
rss << lazyExpression;
|
|
reconstructedExpression = rss.str();
|
|
}
|
|
}
|
|
return reconstructedExpression;
|
|
}
|
|
|
|
AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData&& data )
|
|
: m_info( info ),
|
|
m_resultData( CATCH_MOVE(data) )
|
|
{}
|
|
|
|
// Result was a success
|
|
bool AssertionResult::succeeded() const {
|
|
return Catch::isOk( m_resultData.resultType );
|
|
}
|
|
|
|
// Result was a success, or failure is suppressed
|
|
bool AssertionResult::isOk() const {
|
|
return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
|
|
}
|
|
|
|
ResultWas::OfType AssertionResult::getResultType() const {
|
|
return m_resultData.resultType;
|
|
}
|
|
|
|
bool AssertionResult::hasExpression() const {
|
|
return !m_info.capturedExpression.empty();
|
|
}
|
|
|
|
bool AssertionResult::hasMessage() const {
|
|
return !m_resultData.message.empty();
|
|
}
|
|
|
|
std::string AssertionResult::getExpression() const {
|
|
// Possibly overallocating by 3 characters should be basically free
|
|
std::string expr; expr.reserve(m_info.capturedExpression.size() + 3);
|
|
if (isFalseTest(m_info.resultDisposition)) {
|
|
expr += "!(";
|
|
}
|
|
expr += m_info.capturedExpression;
|
|
if (isFalseTest(m_info.resultDisposition)) {
|
|
expr += ')';
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
std::string AssertionResult::getExpressionInMacro() const {
|
|
if ( m_info.macroName.empty() ) {
|
|
return static_cast<std::string>( m_info.capturedExpression );
|
|
}
|
|
std::string expr;
|
|
expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 );
|
|
expr += m_info.macroName;
|
|
expr += "( ";
|
|
expr += m_info.capturedExpression;
|
|
expr += " )";
|
|
return expr;
|
|
}
|
|
|
|
bool AssertionResult::hasExpandedExpression() const {
|
|
return hasExpression() && getExpandedExpression() != getExpression();
|
|
}
|
|
|
|
std::string AssertionResult::getExpandedExpression() const {
|
|
std::string expr = m_resultData.reconstructExpression();
|
|
return expr.empty()
|
|
? getExpression()
|
|
: expr;
|
|
}
|
|
|
|
StringRef AssertionResult::getMessage() const {
|
|
return m_resultData.message;
|
|
}
|
|
SourceLineInfo AssertionResult::getSourceInfo() const {
|
|
return m_info.lineInfo;
|
|
}
|
|
|
|
StringRef AssertionResult::getTestMacroName() const {
|
|
return m_info.macroName;
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
std::uint32_t getSeed() {
|
|
return getCurrentContext().getConfig()->rngSeed();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#include <cassert>
|
|
#include <stack>
|
|
|
|
namespace Catch {
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
|
|
m_info( CATCH_MOVE(builder.m_info) ) {
|
|
m_info.message = builder.m_stream.str();
|
|
getResultCapture().pushScopedMessage( m_info );
|
|
}
|
|
|
|
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
|
|
m_info( CATCH_MOVE( old.m_info ) ) {
|
|
old.m_moved = true;
|
|
}
|
|
|
|
ScopedMessage::~ScopedMessage() {
|
|
if ( !uncaught_exceptions() && !m_moved ){
|
|
getResultCapture().popScopedMessage(m_info);
|
|
}
|
|
}
|
|
|
|
|
|
Capturer::Capturer( StringRef macroName,
|
|
SourceLineInfo const& lineInfo,
|
|
ResultWas::OfType resultType,
|
|
StringRef names ):
|
|
m_resultCapture( getResultCapture() ) {
|
|
auto trimmed = [&] (size_t start, size_t end) {
|
|
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
|
|
++start;
|
|
}
|
|
while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) {
|
|
--end;
|
|
}
|
|
return names.substr(start, end - start + 1);
|
|
};
|
|
auto skipq = [&] (size_t start, char quote) {
|
|
for (auto i = start + 1; i < names.size() ; ++i) {
|
|
if (names[i] == quote)
|
|
return i;
|
|
if (names[i] == '\\')
|
|
++i;
|
|
}
|
|
CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote");
|
|
};
|
|
|
|
size_t start = 0;
|
|
std::stack<char> openings;
|
|
for (size_t pos = 0; pos < names.size(); ++pos) {
|
|
char c = names[pos];
|
|
switch (c) {
|
|
case '[':
|
|
case '{':
|
|
case '(':
|
|
// It is basically impossible to disambiguate between
|
|
// comparison and start of template args in this context
|
|
// case '<':
|
|
openings.push(c);
|
|
break;
|
|
case ']':
|
|
case '}':
|
|
case ')':
|
|
// case '>':
|
|
openings.pop();
|
|
break;
|
|
case '"':
|
|
case '\'':
|
|
pos = skipq(pos, c);
|
|
break;
|
|
case ',':
|
|
if (start != pos && openings.empty()) {
|
|
m_messages.emplace_back(macroName, lineInfo, resultType);
|
|
m_messages.back().message = static_cast<std::string>(trimmed(start, pos));
|
|
m_messages.back().message += " := ";
|
|
start = pos;
|
|
}
|
|
default:; // noop
|
|
}
|
|
}
|
|
assert(openings.empty() && "Mismatched openings");
|
|
m_messages.emplace_back(macroName, lineInfo, resultType);
|
|
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));
|
|
m_messages.back().message += " := ";
|
|
}
|
|
Capturer::~Capturer() {
|
|
if ( !uncaught_exceptions() ){
|
|
assert( m_captured == m_messages.size() );
|
|
for( size_t i = 0; i < m_captured; ++i )
|
|
m_resultCapture.popScopedMessage( m_messages[i] );
|
|
}
|
|
}
|
|
|
|
void Capturer::captureValue( size_t index, std::string const& value ) {
|
|
assert( index < m_messages.size() );
|
|
m_messages[index].message += value;
|
|
m_resultCapture.pushScopedMessage( m_messages[index] );
|
|
m_captured++;
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
|
|
#include <exception>
|
|
|
|
namespace Catch {
|
|
|
|
namespace {
|
|
|
|
class RegistryHub : public IRegistryHub,
|
|
public IMutableRegistryHub,
|
|
private Detail::NonCopyable {
|
|
|
|
public: // IRegistryHub
|
|
RegistryHub() = default;
|
|
ReporterRegistry const& getReporterRegistry() const override {
|
|
return m_reporterRegistry;
|
|
}
|
|
ITestCaseRegistry const& getTestCaseRegistry() const override {
|
|
return m_testCaseRegistry;
|
|
}
|
|
IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override {
|
|
return m_exceptionTranslatorRegistry;
|
|
}
|
|
ITagAliasRegistry const& getTagAliasRegistry() const override {
|
|
return m_tagAliasRegistry;
|
|
}
|
|
StartupExceptionRegistry const& getStartupExceptionRegistry() const override {
|
|
return m_exceptionRegistry;
|
|
}
|
|
|
|
public: // IMutableRegistryHub
|
|
void registerReporter( std::string const& name, IReporterFactoryPtr factory ) override {
|
|
m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) );
|
|
}
|
|
void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) override {
|
|
m_reporterRegistry.registerListener( CATCH_MOVE(factory) );
|
|
}
|
|
void registerTest( Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker ) override {
|
|
m_testCaseRegistry.registerTest( CATCH_MOVE(testInfo), CATCH_MOVE(invoker) );
|
|
}
|
|
void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) override {
|
|
m_exceptionTranslatorRegistry.registerTranslator( CATCH_MOVE(translator) );
|
|
}
|
|
void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override {
|
|
m_tagAliasRegistry.add( alias, tag, lineInfo );
|
|
}
|
|
void registerStartupException() noexcept override {
|
|
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
|
|
m_exceptionRegistry.add(std::current_exception());
|
|
#else
|
|
CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!");
|
|
#endif
|
|
}
|
|
IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {
|
|
return m_enumValuesRegistry;
|
|
}
|
|
|
|
private:
|
|
TestRegistry m_testCaseRegistry;
|
|
ReporterRegistry m_reporterRegistry;
|
|
ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
|
|
TagAliasRegistry m_tagAliasRegistry;
|
|
StartupExceptionRegistry m_exceptionRegistry;
|
|
Detail::EnumValuesRegistry m_enumValuesRegistry;
|
|
};
|
|
}
|
|
|
|
using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;
|
|
|
|
IRegistryHub const& getRegistryHub() {
|
|
return RegistryHubSingleton::get();
|
|
}
|
|
IMutableRegistryHub& getMutableRegistryHub() {
|
|
return RegistryHubSingleton::getMutable();
|
|
}
|
|
void cleanUp() {
|
|
cleanupSingletons();
|
|
cleanUpContext();
|
|
}
|
|
std::string translateActiveException() {
|
|
return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
|
|
}
|
|
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <exception>
|
|
#include <iomanip>
|
|
#include <set>
|
|
|
|
namespace Catch {
|
|
|
|
namespace {
|
|
const int MaxExitCode = 255;
|
|
|
|
IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) {
|
|
auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config));
|
|
CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\'');
|
|
|
|
return reporter;
|
|
}
|
|
|
|
IEventListenerPtr prepareReporters(Config const* config) {
|
|
if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()
|
|
&& config->getProcessedReporterSpecs().size() == 1) {
|
|
auto const& spec = config->getProcessedReporterSpecs()[0];
|
|
return createReporter(
|
|
spec.name,
|
|
ReporterConfig( config,
|
|
makeStream( spec.outputFilename ),
|
|
spec.colourMode,
|
|
spec.customOptions ) );
|
|
}
|
|
|
|
auto multi = Detail::make_unique<MultiReporter>(config);
|
|
|
|
auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();
|
|
for (auto const& listener : listeners) {
|
|
multi->addListener(listener->create(config));
|
|
}
|
|
|
|
for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) {
|
|
multi->addReporter( createReporter(
|
|
reporterSpec.name,
|
|
ReporterConfig( config,
|
|
makeStream( reporterSpec.outputFilename ),
|
|
reporterSpec.colourMode,
|
|
reporterSpec.customOptions ) ) );
|
|
}
|
|
|
|
return multi;
|
|
}
|
|
|
|
class TestGroup {
|
|
public:
|
|
explicit TestGroup(IEventListenerPtr&& reporter, Config const* config):
|
|
m_reporter(reporter.get()),
|
|
m_config{config},
|
|
m_context{config, CATCH_MOVE(reporter)} {
|
|
|
|
assert( m_config->testSpec().getInvalidSpecs().empty() &&
|
|
"Invalid test specs should be handled before running tests" );
|
|
|
|
auto const& allTestCases = getAllTestCasesSorted(*m_config);
|
|
auto const& testSpec = m_config->testSpec();
|
|
if ( !testSpec.hasFilters() ) {
|
|
for ( auto const& test : allTestCases ) {
|
|
if ( !test.getTestCaseInfo().isHidden() ) {
|
|
m_tests.emplace( &test );
|
|
}
|
|
}
|
|
} else {
|
|
m_matches =
|
|
testSpec.matchesByFilter( allTestCases, *m_config );
|
|
for ( auto const& match : m_matches ) {
|
|
m_tests.insert( match.tests.begin(),
|
|
match.tests.end() );
|
|
}
|
|
}
|
|
|
|
m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex());
|
|
}
|
|
|
|
Totals execute() {
|
|
Totals totals;
|
|
for (auto const& testCase : m_tests) {
|
|
if (!m_context.aborting())
|
|
totals += m_context.runTest(*testCase);
|
|
else
|
|
m_reporter->skipTest(testCase->getTestCaseInfo());
|
|
}
|
|
|
|
for (auto const& match : m_matches) {
|
|
if (match.tests.empty()) {
|
|
m_unmatchedTestSpecs = true;
|
|
m_reporter->noMatchingTestCases( match.name );
|
|
}
|
|
}
|
|
|
|
return totals;
|
|
}
|
|
|
|
bool hadUnmatchedTestSpecs() const {
|
|
return m_unmatchedTestSpecs;
|
|
}
|
|
|
|
|
|
private:
|
|
IEventListener* m_reporter;
|
|
Config const* m_config;
|
|
RunContext m_context;
|
|
std::set<TestCaseHandle const*> m_tests;
|
|
TestSpec::Matches m_matches;
|
|
bool m_unmatchedTestSpecs = false;
|
|
};
|
|
|
|
void applyFilenamesAsTags() {
|
|
for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) {
|
|
testInfo->addFilenameTag();
|
|
}
|
|
}
|
|
|
|
} // anon namespace
|
|
|
|
Session::Session() {
|
|
static bool alreadyInstantiated = false;
|
|
if( alreadyInstantiated ) {
|
|
CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); }
|
|
CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); }
|
|
}
|
|
|
|
// There cannot be exceptions at startup in no-exception mode.
|
|
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
|
|
const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();
|
|
if ( !exceptions.empty() ) {
|
|
config();
|
|
getCurrentMutableContext().setConfig(m_config.get());
|
|
|
|
m_startupExceptions = true;
|
|
auto errStream = makeStream( "%stderr" );
|
|
auto colourImpl = makeColourImpl(
|
|
ColourMode::PlatformDefault, errStream.get() );
|
|
auto guard = colourImpl->guardColour( Colour::Red );
|
|
errStream->stream() << "Errors occurred during startup!" << '\n';
|
|
// iterate over all exceptions and notify user
|
|
for ( const auto& ex_ptr : exceptions ) {
|
|
try {
|
|
std::rethrow_exception(ex_ptr);
|
|
} catch ( std::exception const& ex ) {
|
|
errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\n';
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
alreadyInstantiated = true;
|
|
m_cli = makeCommandLineParser( m_configData );
|
|
}
|
|
Session::~Session() {
|
|
Catch::cleanUp();
|
|
}
|
|
|
|
void Session::showHelp() const {
|
|
Catch::cout()
|
|
<< "\nCatch2 v" << libraryVersion() << '\n'
|
|
<< m_cli << '\n'
|
|
<< "For more detailed usage please see the project docs\n\n" << std::flush;
|
|
}
|
|
void Session::libIdentify() {
|
|
Catch::cout()
|
|
<< std::left << std::setw(16) << "description: " << "A Catch2 test executable\n"
|
|
<< std::left << std::setw(16) << "category: " << "testframework\n"
|
|
<< std::left << std::setw(16) << "framework: " << "Catch2\n"
|
|
<< std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush;
|
|
}
|
|
|
|
int Session::applyCommandLine( int argc, char const * const * argv ) {
|
|
if( m_startupExceptions )
|
|
return 1;
|
|
|
|
auto result = m_cli.parse( Clara::Args( argc, argv ) );
|
|
|
|
if( !result ) {
|
|
config();
|
|
getCurrentMutableContext().setConfig(m_config.get());
|
|
auto errStream = makeStream( "%stderr" );
|
|
auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() );
|
|
|
|
errStream->stream()
|
|
<< colour->guardColour( Colour::Red )
|
|
<< "\nError(s) in input:\n"
|
|
<< TextFlow::Column( result.errorMessage() ).indent( 2 )
|
|
<< "\n\n";
|
|
errStream->stream() << "Run with -? for usage\n\n" << std::flush;
|
|
return MaxExitCode;
|
|
}
|
|
|
|
if( m_configData.showHelp )
|
|
showHelp();
|
|
if( m_configData.libIdentify )
|
|
libIdentify();
|
|
|
|
m_config.reset();
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)
|
|
int Session::applyCommandLine( int argc, wchar_t const * const * argv ) {
|
|
|
|
char **utf8Argv = new char *[ argc ];
|
|
|
|
for ( int i = 0; i < argc; ++i ) {
|
|
int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr );
|
|
|
|
utf8Argv[ i ] = new char[ bufSize ];
|
|
|
|
WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr );
|
|
}
|
|
|
|
int returnCode = applyCommandLine( argc, utf8Argv );
|
|
|
|
for ( int i = 0; i < argc; ++i )
|
|
delete [] utf8Argv[ i ];
|
|
|
|
delete [] utf8Argv;
|
|
|
|
return returnCode;
|
|
}
|
|
#endif
|
|
|
|
void Session::useConfigData( ConfigData const& configData ) {
|
|
m_configData = configData;
|
|
m_config.reset();
|
|
}
|
|
|
|
int Session::run() {
|
|
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) {
|
|
Catch::cout() << "...waiting for enter/ return before starting\n" << std::flush;
|
|
static_cast<void>(std::getchar());
|
|
}
|
|
int exitCode = runInternal();
|
|
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
|
|
Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush;
|
|
static_cast<void>(std::getchar());
|
|
}
|
|
return exitCode;
|
|
}
|
|
|
|
Clara::Parser const& Session::cli() const {
|
|
return m_cli;
|
|
}
|
|
void Session::cli( Clara::Parser const& newParser ) {
|
|
m_cli = newParser;
|
|
}
|
|
ConfigData& Session::configData() {
|
|
return m_configData;
|
|
}
|
|
Config& Session::config() {
|
|
if( !m_config )
|
|
m_config = Detail::make_unique<Config>( m_configData );
|
|
return *m_config;
|
|
}
|
|
|
|
int Session::runInternal() {
|
|
if( m_startupExceptions )
|
|
return 1;
|
|
|
|
if (m_configData.showHelp || m_configData.libIdentify) {
|
|
return 0;
|
|
}
|
|
|
|
if ( m_configData.shardIndex >= m_configData.shardCount ) {
|
|
Catch::cerr() << "The shard count (" << m_configData.shardCount
|
|
<< ") must be greater than the shard index ("
|
|
<< m_configData.shardIndex << ")\n"
|
|
<< std::flush;
|
|
return 1;
|
|
}
|
|
|
|
CATCH_TRY {
|
|
config(); // Force config to be constructed
|
|
|
|
seedRng( *m_config );
|
|
|
|
if (m_configData.filenamesAsTags) {
|
|
applyFilenamesAsTags();
|
|
}
|
|
|
|
// Set up global config instance before we start calling into other functions
|
|
getCurrentMutableContext().setConfig(m_config.get());
|
|
|
|
// Create reporter(s) so we can route listings through them
|
|
auto reporter = prepareReporters(m_config.get());
|
|
|
|
auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs();
|
|
if ( !invalidSpecs.empty() ) {
|
|
for ( auto const& spec : invalidSpecs ) {
|
|
reporter->reportInvalidTestSpec( spec );
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Handle list request
|
|
if (list(*reporter, *m_config)) {
|
|
return 0;
|
|
}
|
|
|
|
TestGroup tests { CATCH_MOVE(reporter), m_config.get() };
|
|
auto const totals = tests.execute();
|
|
|
|
if ( tests.hadUnmatchedTestSpecs()
|
|
&& m_config->warnAboutUnmatchedTestSpecs() ) {
|
|
return 3;
|
|
}
|
|
|
|
if ( totals.testCases.total() == 0
|
|
&& !m_config->zeroTestsCountAsSuccess() ) {
|
|
return 2;
|
|
}
|
|
|
|
if ( totals.testCases.total() > 0 &&
|
|
totals.testCases.total() == totals.testCases.skipped
|
|
&& !m_config->zeroTestsCountAsSuccess() ) {
|
|
return 4;
|
|
}
|
|
|
|
// Note that on unices only the lower 8 bits are usually used, clamping
|
|
// the return value to 255 prevents false negative when some multiple
|
|
// of 256 tests has failed
|
|
return (std::min) (MaxExitCode, static_cast<int>(totals.assertions.failed));
|
|
}
|
|
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
|
|
catch( std::exception& ex ) {
|
|
Catch::cerr() << ex.what() << '\n' << std::flush;
|
|
return MaxExitCode;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
|
|
RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) {
|
|
CATCH_TRY {
|
|
getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo);
|
|
} CATCH_CATCH_ALL {
|
|
// Do not throw when constructing global objects, instead register the exception to be processed later
|
|
getMutableRegistryHub().registerStartupException();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include <cassert>
|
|
#include <cctype>
|
|
#include <algorithm>
|
|
|
|
namespace Catch {
|
|
|
|
namespace {
|
|
using TCP_underlying_type = uint8_t;
|
|
static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type),
|
|
"The size of the TestCaseProperties is different from the assumed size");
|
|
|
|
TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) {
|
|
return static_cast<TestCaseProperties>(
|
|
static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
|
|
);
|
|
}
|
|
|
|
TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) {
|
|
lhs = static_cast<TestCaseProperties>(
|
|
static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
|
|
);
|
|
return lhs;
|
|
}
|
|
|
|
TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) {
|
|
return static_cast<TestCaseProperties>(
|
|
static_cast<TCP_underlying_type>(lhs) & static_cast<TCP_underlying_type>(rhs)
|
|
);
|
|
}
|
|
|
|
bool applies(TestCaseProperties tcp) {
|
|
static_assert(static_cast<TCP_underlying_type>(TestCaseProperties::None) == 0,
|
|
"TestCaseProperties::None must be equal to 0");
|
|
return tcp != TestCaseProperties::None;
|
|
}
|
|
|
|
TestCaseProperties parseSpecialTag( StringRef tag ) {
|
|
if( !tag.empty() && tag[0] == '.' )
|
|
return TestCaseProperties::IsHidden;
|
|
else if( tag == "!throws"_sr )
|
|
return TestCaseProperties::Throws;
|
|
else if( tag == "!shouldfail"_sr )
|
|
return TestCaseProperties::ShouldFail;
|
|
else if( tag == "!mayfail"_sr )
|
|
return TestCaseProperties::MayFail;
|
|
else if( tag == "!nonportable"_sr )
|
|
return TestCaseProperties::NonPortable;
|
|
else if( tag == "!benchmark"_sr )
|
|
return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden;
|
|
else
|
|
return TestCaseProperties::None;
|
|
}
|
|
bool isReservedTag( StringRef tag ) {
|
|
return parseSpecialTag( tag ) == TestCaseProperties::None
|
|
&& tag.size() > 0
|
|
&& !std::isalnum( static_cast<unsigned char>(tag[0]) );
|
|
}
|
|
void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) {
|
|
CATCH_ENFORCE( !isReservedTag(tag),
|
|
"Tag name: [" << tag << "] is not allowed.\n"
|
|
<< "Tag names starting with non alphanumeric characters are reserved\n"
|
|
<< _lineInfo );
|
|
}
|
|
|
|
std::string makeDefaultName() {
|
|
static size_t counter = 0;
|
|
return "Anonymous test case " + std::to_string(++counter);
|
|
}
|
|
|
|
StringRef extractFilenamePart(StringRef filename) {
|
|
size_t lastDot = filename.size();
|
|
while (lastDot > 0 && filename[lastDot - 1] != '.') {
|
|
--lastDot;
|
|
}
|
|
// In theory we could have filename without any extension in it
|
|
if ( lastDot == 0 ) { return StringRef(); }
|
|
|
|
--lastDot;
|
|
size_t nameStart = lastDot;
|
|
while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') {
|
|
--nameStart;
|
|
}
|
|
|
|
return filename.substr(nameStart, lastDot - nameStart);
|
|
}
|
|
|
|
// Returns the upper bound on size of extra tags ([#file]+[.])
|
|
size_t sizeOfExtraTags(StringRef filepath) {
|
|
// [.] is 3, [#] is another 3
|
|
const size_t extras = 3 + 3;
|
|
return extractFilenamePart(filepath).size() + extras;
|
|
}
|
|
} // end unnamed namespace
|
|
|
|
bool operator<( Tag const& lhs, Tag const& rhs ) {
|
|
Detail::CaseInsensitiveLess cmp;
|
|
return cmp( lhs.original, rhs.original );
|
|
}
|
|
bool operator==( Tag const& lhs, Tag const& rhs ) {
|
|
Detail::CaseInsensitiveEqualTo cmp;
|
|
return cmp( lhs.original, rhs.original );
|
|
}
|
|
|
|
Detail::unique_ptr<TestCaseInfo>
|
|
makeTestCaseInfo(StringRef _className,
|
|
NameAndTags const& nameAndTags,
|
|
SourceLineInfo const& _lineInfo ) {
|
|
return Detail::make_unique<TestCaseInfo>(_className, nameAndTags, _lineInfo);
|
|
}
|
|
|
|
TestCaseInfo::TestCaseInfo(StringRef _className,
|
|
NameAndTags const& _nameAndTags,
|
|
SourceLineInfo const& _lineInfo):
|
|
name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ),
|
|
className( _className ),
|
|
lineInfo( _lineInfo )
|
|
{
|
|
StringRef originalTags = _nameAndTags.tags;
|
|
// We need to reserve enough space to store all of the tags
|
|
// (including optional hidden tag and filename tag)
|
|
auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file);
|
|
backingTags.reserve(requiredSize);
|
|
|
|
// We cannot copy the tags directly, as we need to normalize
|
|
// some tags, so that [.foo] is copied as [.][foo].
|
|
size_t tagStart = 0;
|
|
size_t tagEnd = 0;
|
|
bool inTag = false;
|
|
for (size_t idx = 0; idx < originalTags.size(); ++idx) {
|
|
auto c = originalTags[idx];
|
|
if (c == '[') {
|
|
CATCH_ENFORCE(
|
|
!inTag,
|
|
"Found '[' inside a tag while registering test case '"
|
|
<< _nameAndTags.name << "' at " << _lineInfo );
|
|
|
|
inTag = true;
|
|
tagStart = idx;
|
|
}
|
|
if (c == ']') {
|
|
CATCH_ENFORCE(
|
|
inTag,
|
|
"Found unmatched ']' while registering test case '"
|
|
<< _nameAndTags.name << "' at " << _lineInfo );
|
|
|
|
inTag = false;
|
|
tagEnd = idx;
|
|
assert(tagStart < tagEnd);
|
|
|
|
// We need to check the tag for special meanings, copy
|
|
// it over to backing storage and actually reference the
|
|
// backing storage in the saved tags
|
|
StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1);
|
|
CATCH_ENFORCE( !tagStr.empty(),
|
|
"Found an empty tag while registering test case '"
|
|
<< _nameAndTags.name << "' at "
|
|
<< _lineInfo );
|
|
|
|
enforceNotReservedTag(tagStr, lineInfo);
|
|
properties |= parseSpecialTag(tagStr);
|
|
// When copying a tag to the backing storage, we need to
|
|
// check if it is a merged hide tag, such as [.foo], and
|
|
// if it is, we need to handle it as if it was [foo].
|
|
if (tagStr.size() > 1 && tagStr[0] == '.') {
|
|
tagStr = tagStr.substr(1, tagStr.size() - 1);
|
|
}
|
|
// We skip over dealing with the [.] tag, as we will add
|
|
// it later unconditionally and then sort and unique all
|
|
// the tags.
|
|
internalAppendTag(tagStr);
|
|
}
|
|
}
|
|
CATCH_ENFORCE( !inTag,
|
|
"Found an unclosed tag while registering test case '"
|
|
<< _nameAndTags.name << "' at " << _lineInfo );
|
|
|
|
|
|
// Add [.] if relevant
|
|
if (isHidden()) {
|
|
internalAppendTag("."_sr);
|
|
}
|
|
|
|
// Sort and prepare tags
|
|
std::sort(begin(tags), end(tags));
|
|
tags.erase(std::unique(begin(tags), end(tags)),
|
|
end(tags));
|
|
}
|
|
|
|
bool TestCaseInfo::isHidden() const {
|
|
return applies( properties & TestCaseProperties::IsHidden );
|
|
}
|
|
bool TestCaseInfo::throws() const {
|
|
return applies( properties & TestCaseProperties::Throws );
|
|
}
|
|
bool TestCaseInfo::okToFail() const {
|
|
return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) );
|
|
}
|
|
bool TestCaseInfo::expectedToFail() const {
|
|
return applies( properties & (TestCaseProperties::ShouldFail) );
|
|
}
|
|
|
|
void TestCaseInfo::addFilenameTag() {
|
|
std::string combined("#");
|
|
combined += extractFilenamePart(lineInfo.file);
|
|
internalAppendTag(combined);
|
|
}
|
|
|
|
std::string TestCaseInfo::tagsAsString() const {
|
|
std::string ret;
|
|
// '[' and ']' per tag
|
|
std::size_t full_size = 2 * tags.size();
|
|
for (const auto& tag : tags) {
|
|
full_size += tag.original.size();
|
|
}
|
|
ret.reserve(full_size);
|
|
for (const auto& tag : tags) {
|
|
ret.push_back('[');
|
|
ret += tag.original;
|
|
ret.push_back(']');
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void TestCaseInfo::internalAppendTag(StringRef tagStr) {
|
|
backingTags += '[';
|
|
const auto backingStart = backingTags.size();
|
|
backingTags += tagStr;
|
|
const auto backingEnd = backingTags.size();
|
|
backingTags += ']';
|
|
tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart));
|
|
}
|
|
|
|
bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) {
|
|
// We want to avoid redoing the string comparisons multiple times,
|
|
// so we store the result of a three-way comparison before using
|
|
// it in the actual comparison logic.
|
|
const auto cmpName = lhs.name.compare( rhs.name );
|
|
if ( cmpName != 0 ) {
|
|
return cmpName < 0;
|
|
}
|
|
const auto cmpClassName = lhs.className.compare( rhs.className );
|
|
if ( cmpClassName != 0 ) {
|
|
return cmpClassName < 0;
|
|
}
|
|
return lhs.tags < rhs.tags;
|
|
}
|
|
|
|
TestCaseInfo const& TestCaseHandle::getTestCaseInfo() const {
|
|
return *m_info;
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <ostream>
|
|
|
|
namespace Catch {
|
|
|
|
TestSpec::Pattern::Pattern( std::string const& name )
|
|
: m_name( name )
|
|
{}
|
|
|
|
TestSpec::Pattern::~Pattern() = default;
|
|
|
|
std::string const& TestSpec::Pattern::name() const {
|
|
return m_name;
|
|
}
|
|
|
|
|
|
TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString )
|
|
: Pattern( filterString )
|
|
, m_wildcardPattern( toLower( name ), CaseSensitive::No )
|
|
{}
|
|
|
|
bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const {
|
|
return m_wildcardPattern.matches( testCase.name );
|
|
}
|
|
|
|
void TestSpec::NamePattern::serializeTo( std::ostream& out ) const {
|
|
out << '"' << name() << '"';
|
|
}
|
|
|
|
|
|
TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString )
|
|
: Pattern( filterString )
|
|
, m_tag( tag )
|
|
{}
|
|
|
|
bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {
|
|
return std::find( begin( testCase.tags ),
|
|
end( testCase.tags ),
|
|
Tag( m_tag ) ) != end( testCase.tags );
|
|
}
|
|
|
|
void TestSpec::TagPattern::serializeTo( std::ostream& out ) const {
|
|
out << name();
|
|
}
|
|
|
|
bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {
|
|
bool should_use = !testCase.isHidden();
|
|
for (auto const& pattern : m_required) {
|
|
should_use = true;
|
|
if (!pattern->matches(testCase)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (auto const& pattern : m_forbidden) {
|
|
if (pattern->matches(testCase)) {
|
|
return false;
|
|
}
|
|
}
|
|
return should_use;
|
|
}
|
|
|
|
void TestSpec::Filter::serializeTo( std::ostream& out ) const {
|
|
bool first = true;
|
|
for ( auto const& pattern : m_required ) {
|
|
if ( !first ) {
|
|
out << ' ';
|
|
}
|
|
out << *pattern;
|
|
first = false;
|
|
}
|
|
for ( auto const& pattern : m_forbidden ) {
|
|
if ( !first ) {
|
|
out << ' ';
|
|
}
|
|
out << *pattern;
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
|
|
std::string TestSpec::extractFilterName( Filter const& filter ) {
|
|
Catch::ReusableStringStream sstr;
|
|
sstr << filter;
|
|
return sstr.str();
|
|
}
|
|
|
|
bool TestSpec::hasFilters() const {
|
|
return !m_filters.empty();
|
|
}
|
|
|
|
bool TestSpec::matches( TestCaseInfo const& testCase ) const {
|
|
return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } );
|
|
}
|
|
|
|
TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const {
|
|
Matches matches;
|
|
matches.reserve( m_filters.size() );
|
|
for ( auto const& filter : m_filters ) {
|
|
std::vector<TestCaseHandle const*> currentMatches;
|
|
for ( auto const& test : testCases )
|
|
if ( isThrowSafe( test, config ) &&
|
|
filter.matches( test.getTestCaseInfo() ) )
|
|
currentMatches.emplace_back( &test );
|
|
matches.push_back(
|
|
FilterMatch{ extractFilterName( filter ), currentMatches } );
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
const TestSpec::vectorStrings& TestSpec::getInvalidSpecs() const {
|
|
return m_invalidSpecs;
|
|
}
|
|
|
|
void TestSpec::serializeTo( std::ostream& out ) const {
|
|
bool first = true;
|
|
for ( auto const& filter : m_filters ) {
|
|
if ( !first ) {
|
|
out << ',';
|
|
}
|
|
out << filter;
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
namespace Catch {
|
|
|
|
namespace {
|
|
static auto getCurrentNanosecondsSinceEpoch() -> uint64_t {
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
|
}
|
|
} // end unnamed namespace
|
|
|
|
void Timer::start() {
|
|
m_nanoseconds = getCurrentNanosecondsSinceEpoch();
|
|
}
|
|
auto Timer::getElapsedNanoseconds() const -> uint64_t {
|
|
return getCurrentNanosecondsSinceEpoch() - m_nanoseconds;
|
|
}
|
|
auto Timer::getElapsedMicroseconds() const -> uint64_t {
|
|
return getElapsedNanoseconds()/1000;
|
|
}
|
|
auto Timer::getElapsedMilliseconds() const -> unsigned int {
|
|
return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
|
|
}
|
|
auto Timer::getElapsedSeconds() const -> double {
|
|
return getElapsedMicroseconds()/1000000.0;
|
|
}
|
|
|
|
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
|
|
namespace Catch {
|
|
|
|
namespace Detail {
|
|
|
|
namespace {
|
|
const int hexThreshold = 255;
|
|
|
|
struct Endianness {
|
|
enum Arch { Big, Little };
|
|
|
|
static Arch which() {
|
|
int one = 1;
|
|
// If the lowest byte we read is non-zero, we can assume
|
|
// that little endian format is used.
|
|
auto value = *reinterpret_cast<char*>(&one);
|
|
return value ? Little : Big;
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
std::string fpToString(T value, int precision) {
|
|
if (Catch::isnan(value)) {
|
|
return "nan";
|
|
}
|
|
|
|
ReusableStringStream rss;
|
|
rss << std::setprecision(precision)
|
|
<< std::fixed
|
|
<< value;
|
|
std::string d = rss.str();
|
|
std::size_t i = d.find_last_not_of('0');
|
|
if (i != std::string::npos && i != d.size() - 1) {
|
|
if (d[i] == '.')
|
|
i++;
|
|
d = d.substr(0, i + 1);
|
|
}
|
|
return d;
|
|
}
|
|
} // end unnamed namespace
|
|
|
|
std::string convertIntoString(StringRef string, bool escapeInvisibles) {
|
|
std::string ret;
|
|
// This is enough for the "don't escape invisibles" case, and a good
|
|
// lower bound on the "escape invisibles" case.
|
|
ret.reserve(string.size() + 2);
|
|
|
|
if (!escapeInvisibles) {
|
|
ret += '"';
|
|
ret += string;
|
|
ret += '"';
|
|
return ret;
|
|
}
|
|
|
|
ret += '"';
|
|
for (char c : string) {
|
|
switch (c) {
|
|
case '\r':
|
|
ret.append("\\r");
|
|
break;
|
|
case '\n':
|
|
ret.append("\\n");
|
|
break;
|
|
case '\t':
|
|
ret.append("\\t");
|
|
break;
|
|
case '\f':
|
|
ret.append("\\f");
|
|
break;
|
|
default:
|
|
ret.push_back(c);
|
|
break;
|
|
}
|
|
}
|
|
ret += '"';
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::string convertIntoString(StringRef string) {
|
|
return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles());
|
|
}
|
|
|
|
std::string rawMemoryToString( const void *object, std::size_t size ) {
|
|
// Reverse order for little endian architectures
|
|
int i = 0, end = static_cast<int>( size ), inc = 1;
|
|
if( Endianness::which() == Endianness::Little ) {
|
|
i = end-1;
|
|
end = inc = -1;
|
|
}
|
|
|
|
unsigned char const *bytes = static_cast<unsigned char const *>(object);
|
|
ReusableStringStream rss;
|
|
rss << "0x" << std::setfill('0') << std::hex;
|
|
for( ; i != end; i += inc )
|
|
rss << std::setw(2) << static_cast<unsigned>(bytes[i]);
|
|
return rss.str();
|
|
}
|
|
} // end Detail namespace
|
|
|
|
|
|
|
|
//// ======================================================= ////
|
|
//
|
|
// Out-of-line defs for full specialization of StringMaker
|
|
//
|
|
//// ======================================================= ////
|
|
|
|
std::string StringMaker<std::string>::convert(const std::string& str) {
|
|
return Detail::convertIntoString( str );
|
|
}
|
|
|
|
#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
|
|
std::string StringMaker<std::string_view>::convert(std::string_view str) {
|
|
return Detail::convertIntoString( StringRef( str.data(), str.size() ) );
|
|
}
|
|
#endif
|
|
|
|
std::string StringMaker<char const*>::convert(char const* str) {
|
|
if (str) {
|
|
return Detail::convertIntoString( str );
|
|
} else {
|
|
return{ "{null string}" };
|
|
}
|
|
}
|
|
std::string StringMaker<char*>::convert(char* str) { // NOLINT(readability-non-const-parameter)
|
|
if (str) {
|
|
return Detail::convertIntoString( str );
|
|
} else {
|
|
return{ "{null string}" };
|
|
}
|
|
}
|
|
|
|
#ifdef CATCH_CONFIG_WCHAR
|
|
std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) {
|
|
std::string s;
|
|
s.reserve(wstr.size());
|
|
for (auto c : wstr) {
|
|
s += (c <= 0xff) ? static_cast<char>(c) : '?';
|
|
}
|
|
return ::Catch::Detail::stringify(s);
|
|
}
|
|
|
|
# ifdef CATCH_CONFIG_CPP17_STRING_VIEW
|
|
std::string StringMaker<std::wstring_view>::convert(std::wstring_view str) {
|
|
return StringMaker<std::wstring>::convert(std::wstring(str));
|
|
}
|
|
# endif
|
|
|
|
std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) {
|
|
if (str) {
|
|
return ::Catch::Detail::stringify(std::wstring{ str });
|
|
} else {
|
|
return{ "{null string}" };
|
|
}
|
|
}
|
|
std::string StringMaker<wchar_t *>::convert(wchar_t * str) {
|
|
if (str) {
|
|
return ::Catch::Detail::stringify(std::wstring{ str });
|
|
} else {
|
|
return{ "{null string}" };
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(CATCH_CONFIG_CPP17_BYTE)
|
|
#include <cstddef>
|
|
std::string StringMaker<std::byte>::convert(std::byte value) {
|
|
return ::Catch::Detail::stringify(std::to_integer<unsigned long long>(value));
|
|
}
|
|
#endif // defined(CATCH_CONFIG_CPP17_BYTE)
|
|
|
|
std::string StringMaker<int>::convert(int value) {
|
|
return ::Catch::Detail::stringify(static_cast<long long>(value));
|
|
}
|
|
std::string StringMaker<long>::convert(long value) {
|
|
return ::Catch::Detail::stringify(static_cast<long long>(value));
|
|
}
|
|
std::string StringMaker<long long>::convert(long long value) {
|
|
ReusableStringStream rss;
|
|
rss << value;
|
|
if (value > Detail::hexThreshold) {
|
|
rss << " (0x" << std::hex << value << ')';
|
|
}
|
|
return rss.str();
|
|
}
|
|
|
|
std::string StringMaker<unsigned int>::convert(unsigned int value) {
|
|
return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));
|
|
}
|
|
std::string StringMaker<unsigned long>::convert(unsigned long value) {
|
|
return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));
|
|
}
|
|
std::string StringMaker<unsigned long long>::convert(unsigned long long value) {
|
|
ReusableStringStream rss;
|
|
rss << value;
|
|
if (value > Detail::hexThreshold) {
|
|
rss << " (0x" << std::hex << value << ')';
|
|
}
|
|
return rss.str();
|
|
}
|
|
|
|
std::string StringMaker<signed char>::convert(signed char value) {
|
|
if (value == '\r') {
|
|
return "'\\r'";
|
|
} else if (value == '\f') {
|
|
return "'\\f'";
|
|
} else if (value == '\n') {
|
|
return "'\\n'";
|
|
} else if (value == '\t') {
|
|
return "'\\t'";
|
|
} else if ('\0' <= value && value < ' ') {
|
|
return ::Catch::Detail::stringify(static_cast<unsigned int>(value));
|
|
} else {
|
|
char chstr[] = "' '";
|
|
chstr[1] = value;
|
|
return chstr;
|
|
}
|
|
}
|
|
std::string StringMaker<char>::convert(char c) {
|
|
return ::Catch::Detail::stringify(static_cast<signed char>(c));
|
|
}
|
|
std::string StringMaker<unsigned char>::convert(unsigned char value) {
|
|
return ::Catch::Detail::stringify(static_cast<char>(value));
|
|
}
|
|
|
|
int StringMaker<float>::precision = std::numeric_limits<float>::max_digits10;
|
|
|
|
std::string StringMaker<float>::convert(float value) {
|
|
return Detail::fpToString(value, precision) + 'f';
|
|
}
|
|
|
|
int StringMaker<double>::precision = std::numeric_limits<double>::max_digits10;
|
|
|
|
std::string StringMaker<double>::convert(double value) {
|
|
return Detail::fpToString(value, precision);
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
namespace Catch {
|
|
|
|
Counts Counts::operator - ( Counts const& other ) const {
|
|
Counts diff;
|
|
diff.passed = passed - other.passed;
|
|
diff.failed = failed - other.failed;
|
|
diff.failedButOk = failedButOk - other.failedButOk;
|
|
diff.skipped = skipped - other.skipped;
|
|
return diff;
|
|
}
|
|
|
|
Counts& Counts::operator += ( Counts const& other ) {
|
|
passed += other.passed;
|
|
failed += other.failed;
|
|
failedButOk += other.failedButOk;
|
|
skipped += other.skipped;
|
|
return *this;
|
|
}
|
|
|
|
std::uint64_t Counts::total() const {
|
|
return passed + failed + failedButOk + skipped;
|
|
}
|
|
bool Counts::allPassed() const {
|
|
return failed == 0 && failedButOk == 0 && skipped == 0;
|
|
}
|
|
bool Counts::allOk() const {
|
|
return failed == 0;
|
|
}
|
|
|
|
Totals Totals::operator - ( Totals const& other ) const {
|
|
Totals diff;
|
|
diff.assertions = assertions - other.assertions;
|
|
diff.testCases = testCases - other.testCases;
|
|
return diff;
|
|
}
|
|
|
|
Totals& Totals::operator += ( Totals const& other ) {
|
|
assertions += other.assertions;
|
|
testCases += other.testCases;
|
|
return *this;
|
|
}
|
|
|
|
Totals Totals::delta( Totals const& prevTotals ) const {
|
|
Totals diff = *this - prevTotals;
|
|
if( diff.assertions.failed > 0 )
|
|
++diff.testCases.failed;
|
|
else if( diff.assertions.failedButOk > 0 )
|
|
++diff.testCases.failedButOk;
|
|
else if ( diff.assertions.skipped > 0 )
|
|
++ diff.testCases.skipped;
|
|
else
|
|
++diff.testCases.passed;
|
|
return diff;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
namespace Detail {
|
|
void registerTranslatorImpl(
|
|
Detail::unique_ptr<IExceptionTranslator>&& translator ) {
|
|
getMutableRegistryHub().registerTranslator(
|
|
CATCH_MOVE( translator ) );
|
|
}
|
|
} // namespace Detail
|
|
} // namespace Catch
|
|
|
|
|
|
#include <ostream>
|
|
|
|
namespace Catch {
|
|
|
|
Version::Version
|
|
( unsigned int _majorVersion,
|
|
unsigned int _minorVersion,
|
|
unsigned int _patchNumber,
|
|
char const * const _branchName,
|
|
unsigned int _buildNumber )
|
|
: majorVersion( _majorVersion ),
|
|
minorVersion( _minorVersion ),
|
|
patchNumber( _patchNumber ),
|
|
branchName( _branchName ),
|
|
buildNumber( _buildNumber )
|
|
{}
|
|
|
|
std::ostream& operator << ( std::ostream& os, Version const& version ) {
|
|
os << version.majorVersion << '.'
|
|
<< version.minorVersion << '.'
|
|
<< version.patchNumber;
|
|
// branchName is never null -> 0th char is \0 if it is empty
|
|
if (version.branchName[0]) {
|
|
os << '-' << version.branchName
|
|
<< '.' << version.buildNumber;
|
|
}
|
|
return os;
|
|
}
|
|
|
|
Version const& libraryVersion() {
|
|
static Version version( 3, 6, 0, "", 0 );
|
|
return version;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
|
|
const char* GeneratorException::what() const noexcept {
|
|
return m_msg;
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
|
|
IGeneratorTracker::~IGeneratorTracker() = default;
|
|
|
|
namespace Generators {
|
|
|
|
namespace Detail {
|
|
|
|
[[noreturn]]
|
|
void throw_generator_exception(char const* msg) {
|
|
Catch::throw_exception(GeneratorException{ msg });
|
|
}
|
|
} // end namespace Detail
|
|
|
|
GeneratorUntypedBase::~GeneratorUntypedBase() = default;
|
|
|
|
IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) {
|
|
return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo );
|
|
}
|
|
|
|
IGeneratorTracker* createGeneratorTracker( StringRef generatorName,
|
|
SourceLineInfo lineInfo,
|
|
GeneratorBasePtr&& generator ) {
|
|
return getResultCapture().createGeneratorTracker(
|
|
generatorName, lineInfo, CATCH_MOVE( generator ) );
|
|
}
|
|
|
|
} // namespace Generators
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
#include <random>
|
|
|
|
namespace Catch {
|
|
namespace Generators {
|
|
namespace Detail {
|
|
std::uint32_t getSeed() { return sharedRng()(); }
|
|
} // namespace Detail
|
|
|
|
struct RandomFloatingGenerator<long double>::PImpl {
|
|
PImpl( long double a, long double b, uint32_t seed ):
|
|
rng( seed ), dist( a, b ) {}
|
|
|
|
Catch::SimplePcg32 rng;
|
|
std::uniform_real_distribution<long double> dist;
|
|
};
|
|
|
|
RandomFloatingGenerator<long double>::RandomFloatingGenerator(
|
|
long double a, long double b, std::uint32_t seed) :
|
|
m_pimpl(Catch::Detail::make_unique<PImpl>(a, b, seed)) {
|
|
static_cast<void>( next() );
|
|
}
|
|
|
|
RandomFloatingGenerator<long double>::~RandomFloatingGenerator() =
|
|
default;
|
|
bool RandomFloatingGenerator<long double>::next() {
|
|
m_current_number = m_pimpl->dist( m_pimpl->rng );
|
|
return true;
|
|
}
|
|
} // namespace Generators
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
IResultCapture::~IResultCapture() = default;
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
IConfig::~IConfig() = default;
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
IExceptionTranslator::~IExceptionTranslator() = default;
|
|
IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default;
|
|
}
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
namespace Catch {
|
|
namespace Generators {
|
|
|
|
bool GeneratorUntypedBase::countedNext() {
|
|
auto ret = next();
|
|
if ( ret ) {
|
|
m_stringReprCache.clear();
|
|
++m_currentElementIndex;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
StringRef GeneratorUntypedBase::currentElementAsString() const {
|
|
if ( m_stringReprCache.empty() ) {
|
|
m_stringReprCache = stringifyImpl();
|
|
}
|
|
return m_stringReprCache;
|
|
}
|
|
|
|
} // namespace Generators
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
IRegistryHub::~IRegistryHub() = default;
|
|
IMutableRegistryHub::~IMutableRegistryHub() = default;
|
|
}
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
namespace Catch {
|
|
|
|
ReporterConfig::ReporterConfig(
|
|
IConfig const* _fullConfig,
|
|
Detail::unique_ptr<IStream> _stream,
|
|
ColourMode colourMode,
|
|
std::map<std::string, std::string> customOptions ):
|
|
m_stream( CATCH_MOVE(_stream) ),
|
|
m_fullConfig( _fullConfig ),
|
|
m_colourMode( colourMode ),
|
|
m_customOptions( CATCH_MOVE( customOptions ) ) {}
|
|
|
|
Detail::unique_ptr<IStream> ReporterConfig::takeStream() && {
|
|
assert( m_stream );
|
|
return CATCH_MOVE( m_stream );
|
|
}
|
|
IConfig const * ReporterConfig::fullConfig() const { return m_fullConfig; }
|
|
ColourMode ReporterConfig::colourMode() const { return m_colourMode; }
|
|
|
|
std::map<std::string, std::string> const&
|
|
ReporterConfig::customOptions() const {
|
|
return m_customOptions;
|
|
}
|
|
|
|
ReporterConfig::~ReporterConfig() = default;
|
|
|
|
AssertionStats::AssertionStats( AssertionResult const& _assertionResult,
|
|
std::vector<MessageInfo> const& _infoMessages,
|
|
Totals const& _totals )
|
|
: assertionResult( _assertionResult ),
|
|
infoMessages( _infoMessages ),
|
|
totals( _totals )
|
|
{
|
|
if( assertionResult.hasMessage() ) {
|
|
// Copy message into messages list.
|
|
// !TBD This should have been done earlier, somewhere
|
|
MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );
|
|
builder.m_info.message = static_cast<std::string>(assertionResult.getMessage());
|
|
|
|
infoMessages.push_back( CATCH_MOVE(builder.m_info) );
|
|
}
|
|
}
|
|
|
|
SectionStats::SectionStats( SectionInfo&& _sectionInfo,
|
|
Counts const& _assertions,
|
|
double _durationInSeconds,
|
|
bool _missingAssertions )
|
|
: sectionInfo( CATCH_MOVE(_sectionInfo) ),
|
|
assertions( _assertions ),
|
|
durationInSeconds( _durationInSeconds ),
|
|
missingAssertions( _missingAssertions )
|
|
{}
|
|
|
|
|
|
TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo,
|
|
Totals const& _totals,
|
|
std::string&& _stdOut,
|
|
std::string&& _stdErr,
|
|
bool _aborting )
|
|
: testInfo( &_testInfo ),
|
|
totals( _totals ),
|
|
stdOut( CATCH_MOVE(_stdOut) ),
|
|
stdErr( CATCH_MOVE(_stdErr) ),
|
|
aborting( _aborting )
|
|
{}
|
|
|
|
|
|
TestRunStats::TestRunStats( TestRunInfo const& _runInfo,
|
|
Totals const& _totals,
|
|
bool _aborting )
|
|
: runInfo( _runInfo ),
|
|
totals( _totals ),
|
|
aborting( _aborting )
|
|
{}
|
|
|
|
IEventListener::~IEventListener() = default;
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
IReporterFactory::~IReporterFactory() = default;
|
|
EventListenerFactory::~EventListenerFactory() = default;
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace Catch {
|
|
ITestCaseRegistry::~ITestCaseRegistry() = default;
|
|
}
|
|
|
|
|
|
|
|
namespace Catch {
|
|
|
|
AssertionHandler::AssertionHandler
|
|
( StringRef macroName,
|
|
SourceLineInfo const& lineInfo,
|
|
StringRef capturedExpression,
|
|
ResultDisposition::Flags resultDisposition )
|
|
: m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },
|
|
m_resultCapture( getResultCapture() )
|
|
{
|
|
m_resultCapture.notifyAssertionStarted( m_assertionInfo );
|
|
}
|
|
|
|
void AssertionHandler::handleExpr( ITransientExpression const& expr ) {
|
|
m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction );
|
|
}
|
|
void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef message) {
|
|
m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction );
|
|
}
|
|
|
|
auto AssertionHandler::allowThrows() const -> bool {
|
|
return getCurrentContext().getConfig()->allowThrows();
|
|
}
|
|
|
|
void AssertionHandler::complete() {
|
|
m_completed = true;
|
|
if( m_reaction.shouldDebugBreak ) {
|
|
|
|
// If you find your debugger stopping you here then go one level up on the
|
|
// call-stack for the code that caused it (typically a failed assertion)
|
|
|
|
// (To go back to the test and change execution, jump over the throw, next)
|
|
CATCH_BREAK_INTO_DEBUGGER();
|
|
}
|
|
if (m_reaction.shouldThrow) {
|
|
throw_test_failure_exception();
|
|
}
|
|
if ( m_reaction.shouldSkip ) {
|
|
throw_test_skip_exception();
|
|
}
|
|
}
|
|
|
|
void AssertionHandler::handleUnexpectedInflightException() {
|
|
m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction );
|
|
}
|
|
|
|
void AssertionHandler::handleExceptionThrownAsExpected() {
|
|
m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
|
|
}
|
|
void AssertionHandler::handleExceptionNotThrownAsExpected() {
|
|
m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
|
|
}
|
|
|
|
void AssertionHandler::handleUnexpectedExceptionNotThrown() {
|
|
m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction );
|
|
}
|
|
|
|
void AssertionHandler::handleThrowingCallSkipped() {
|
|
m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);
|
|
}
|
|
|
|
// This is the overload that takes a string and infers the Equals matcher from it
|
|
// The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp
|
|
void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str ) {
|
|
handleExceptionMatchExpr( handler, Matchers::Equals( str ) );
|
|
}
|
|
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
namespace Catch {
|
|
namespace Detail {
|
|
|
|
bool CaseInsensitiveLess::operator()( StringRef lhs,
|
|
StringRef rhs ) const {
|
|
return std::lexicographical_compare(
|
|
lhs.begin(), lhs.end(),
|
|
rhs.begin(), rhs.end(),
|
|
[]( char l, char r ) { return toLower( l ) < toLower( r ); } );
|
|
}
|
|
|
|
bool
|
|
CaseInsensitiveEqualTo::operator()( StringRef lhs,
|
|
StringRef rhs ) const {
|
|
return std::equal(
|
|
lhs.begin(), lhs.end(),
|
|
rhs.begin(), rhs.end(),
|
|
[]( char l, char r ) { return toLower( l ) == toLower( r ); } );
|
|
}
|
|
|
|
} // namespace Detail
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
#include <ostream>
|
|
|
|
namespace {
|
|
bool isOptPrefix( char c ) {
|
|
return c == '-'
|
|
#ifdef CATCH_PLATFORM_WINDOWS
|
|
|| c == '/'
|
|
#endif
|
|
;
|
|
}
|
|
|
|
Catch::StringRef normaliseOpt( Catch::StringRef optName ) {
|
|
if ( optName[0] == '-'
|
|
#if defined(CATCH_PLATFORM_WINDOWS)
|
|
|| optName[0] == '/'
|
|
#endif
|
|
) {
|
|
return optName.substr( 1, optName.size() );
|
|
}
|
|
|
|
return optName;
|
|
}
|
|
|
|
static size_t find_first_separator(Catch::StringRef sr) {
|
|
auto is_separator = []( char c ) {
|
|
return c == ' ' || c == ':' || c == '=';
|
|
};
|
|
size_t pos = 0;
|
|
while (pos < sr.size()) {
|
|
if (is_separator(sr[pos])) { return pos; }
|
|
++pos;
|
|
}
|
|
|
|
return Catch::StringRef::npos;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Catch {
|
|
namespace Clara {
|
|
namespace Detail {
|
|
|
|
void TokenStream::loadBuffer() {
|
|
m_tokenBuffer.clear();
|
|
|
|
// Skip any empty strings
|
|
while ( it != itEnd && it->empty() ) {
|
|
++it;
|
|
}
|
|
|
|
if ( it != itEnd ) {
|
|
StringRef next = *it;
|
|
if ( isOptPrefix( next[0] ) ) {
|
|
auto delimiterPos = find_first_separator(next);
|
|
if ( delimiterPos != StringRef::npos ) {
|
|
m_tokenBuffer.push_back(
|
|
{ TokenType::Option,
|
|
next.substr( 0, delimiterPos ) } );
|
|
m_tokenBuffer.push_back(
|
|
{ TokenType::Argument,
|
|
next.substr( delimiterPos + 1, next.size() ) } );
|
|
} else {
|
|
if ( next[1] != '-' && next.size() > 2 ) {
|
|
// Combined short args, e.g. "-ab" for "-a -b"
|
|
for ( size_t i = 1; i < next.size(); ++i ) {
|
|
m_tokenBuffer.push_back(
|
|
{ TokenType::Option,
|
|
next.substr( i, 1 ) } );
|
|
}
|
|
} else {
|
|
m_tokenBuffer.push_back(
|
|
{ TokenType::Option, next } );
|
|
}
|
|
}
|
|
} else {
|
|
m_tokenBuffer.push_back(
|
|
{ TokenType::Argument, next } );
|
|
}
|
|
}
|
|
}
|
|
|
|
TokenStream::TokenStream( Args const& args ):
|
|
TokenStream( args.m_args.begin(), args.m_args.end() ) {}
|
|
|
|
TokenStream::TokenStream( Iterator it_, Iterator itEnd_ ):
|
|
it( it_ ), itEnd( itEnd_ ) {
|
|
loadBuffer();
|
|
}
|
|
|
|
TokenStream& TokenStream::operator++() {
|
|
if ( m_tokenBuffer.size() >= 2 ) {
|
|
m_tokenBuffer.erase( m_tokenBuffer.begin() );
|
|
} else {
|
|
if ( it != itEnd )
|
|
++it;
|
|
loadBuffer();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
ParserResult convertInto( std::string const& source,
|
|
std::string& target ) {
|
|
target = source;
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
}
|
|
|
|
ParserResult convertInto( std::string const& source,
|
|
bool& target ) {
|
|
std::string srcLC = toLower( source );
|
|
|
|
if ( srcLC == "y" || srcLC == "1" || srcLC == "true" ||
|
|
srcLC == "yes" || srcLC == "on" ) {
|
|
target = true;
|
|
} else if ( srcLC == "n" || srcLC == "0" || srcLC == "false" ||
|
|
srcLC == "no" || srcLC == "off" ) {
|
|
target = false;
|
|
} else {
|
|
return ParserResult::runtimeError(
|
|
"Expected a boolean value but did not recognise: '" +
|
|
source + '\'' );
|
|
}
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
}
|
|
|
|
size_t ParserBase::cardinality() const { return 1; }
|
|
|
|
InternalParseResult ParserBase::parse( Args const& args ) const {
|
|
return parse( static_cast<std::string>(args.exeName()), TokenStream( args ) );
|
|
}
|
|
|
|
ParseState::ParseState( ParseResultType type,
|
|
TokenStream remainingTokens ):
|
|
m_type( type ), m_remainingTokens( CATCH_MOVE(remainingTokens) ) {}
|
|
|
|
ParserResult BoundFlagRef::setFlag( bool flag ) {
|
|
m_ref = flag;
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
}
|
|
|
|
ResultBase::~ResultBase() = default;
|
|
|
|
bool BoundRef::isContainer() const { return false; }
|
|
|
|
bool BoundRef::isFlag() const { return false; }
|
|
|
|
bool BoundFlagRefBase::isFlag() const { return true; }
|
|
|
|
} // namespace Detail
|
|
|
|
Detail::InternalParseResult Arg::parse(std::string const&,
|
|
Detail::TokenStream tokens) const {
|
|
auto validationResult = validate();
|
|
if (!validationResult)
|
|
return Detail::InternalParseResult(validationResult);
|
|
|
|
auto token = *tokens;
|
|
if (token.type != Detail::TokenType::Argument)
|
|
return Detail::InternalParseResult::ok(Detail::ParseState(
|
|
ParseResultType::NoMatch, CATCH_MOVE(tokens)));
|
|
|
|
assert(!m_ref->isFlag());
|
|
auto valueRef =
|
|
static_cast<Detail::BoundValueRefBase*>(m_ref.get());
|
|
|
|
auto result = valueRef->setValue(static_cast<std::string>(token.token));
|
|
if ( !result )
|
|
return Detail::InternalParseResult( result );
|
|
else
|
|
return Detail::InternalParseResult::ok(
|
|
Detail::ParseState( ParseResultType::Matched,
|
|
CATCH_MOVE( ++tokens ) ) );
|
|
}
|
|
|
|
Opt::Opt(bool& ref) :
|
|
ParserRefImpl(std::make_shared<Detail::BoundFlagRef>(ref)) {}
|
|
|
|
Detail::HelpColumns Opt::getHelpColumns() const {
|
|
ReusableStringStream oss;
|
|
bool first = true;
|
|
for (auto const& opt : m_optNames) {
|
|
if (first)
|
|
first = false;
|
|
else
|
|
oss << ", ";
|
|
oss << opt;
|
|
}
|
|
if (!m_hint.empty())
|
|
oss << " <" << m_hint << '>';
|
|
return { oss.str(), m_description };
|
|
}
|
|
|
|
bool Opt::isMatch(StringRef optToken) const {
|
|
auto normalisedToken = normaliseOpt(optToken);
|
|
for (auto const& name : m_optNames) {
|
|
if (normaliseOpt(name) == normalisedToken)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Detail::InternalParseResult Opt::parse(std::string const&,
|
|
Detail::TokenStream tokens) const {
|
|
auto validationResult = validate();
|
|
if (!validationResult)
|
|
return Detail::InternalParseResult(validationResult);
|
|
|
|
if (tokens &&
|
|
tokens->type == Detail::TokenType::Option) {
|
|
auto const& token = *tokens;
|
|
if (isMatch(token.token)) {
|
|
if (m_ref->isFlag()) {
|
|
auto flagRef =
|
|
static_cast<Detail::BoundFlagRefBase*>(
|
|
m_ref.get());
|
|
auto result = flagRef->setFlag(true);
|
|
if (!result)
|
|
return Detail::InternalParseResult(result);
|
|
if (result.value() ==
|
|
ParseResultType::ShortCircuitAll)
|
|
return Detail::InternalParseResult::ok(Detail::ParseState(
|
|
result.value(), CATCH_MOVE(tokens)));
|
|
} else {
|
|
auto valueRef =
|
|
static_cast<Detail::BoundValueRefBase*>(
|
|
m_ref.get());
|
|
++tokens;
|
|
if (!tokens)
|
|
return Detail::InternalParseResult::runtimeError(
|
|
"Expected argument following " +
|
|
token.token);
|
|
auto const& argToken = *tokens;
|
|
if (argToken.type != Detail::TokenType::Argument)
|
|
return Detail::InternalParseResult::runtimeError(
|
|
"Expected argument following " +
|
|
token.token);
|
|
const auto result = valueRef->setValue(static_cast<std::string>(argToken.token));
|
|
if (!result)
|
|
return Detail::InternalParseResult(result);
|
|
if (result.value() ==
|
|
ParseResultType::ShortCircuitAll)
|
|
return Detail::InternalParseResult::ok(Detail::ParseState(
|
|
result.value(), CATCH_MOVE(tokens)));
|
|
}
|
|
return Detail::InternalParseResult::ok(Detail::ParseState(
|
|
ParseResultType::Matched, CATCH_MOVE(++tokens)));
|
|
}
|
|
}
|
|
return Detail::InternalParseResult::ok(
|
|
Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens)));
|
|
}
|
|
|
|
Detail::Result Opt::validate() const {
|
|
if (m_optNames.empty())
|
|
return Detail::Result::logicError("No options supplied to Opt");
|
|
for (auto const& name : m_optNames) {
|
|
if (name.empty())
|
|
return Detail::Result::logicError(
|
|
"Option name cannot be empty");
|
|
#ifdef CATCH_PLATFORM_WINDOWS
|
|
if (name[0] != '-' && name[0] != '/')
|
|
return Detail::Result::logicError(
|
|
"Option name must begin with '-' or '/'");
|
|
#else
|
|
if (name[0] != '-')
|
|
return Detail::Result::logicError(
|
|
"Option name must begin with '-'");
|
|
#endif
|
|
}
|
|
return ParserRefImpl::validate();
|
|
}
|
|
|
|
ExeName::ExeName() :
|
|
m_name(std::make_shared<std::string>("<executable>")) {}
|
|
|
|
ExeName::ExeName(std::string& ref) : ExeName() {
|
|
m_ref = std::make_shared<Detail::BoundValueRef<std::string>>(ref);
|
|
}
|
|
|
|
Detail::InternalParseResult
|
|
ExeName::parse(std::string const&,
|
|
Detail::TokenStream tokens) const {
|
|
return Detail::InternalParseResult::ok(
|
|
Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens)));
|
|
}
|
|
|
|
ParserResult ExeName::set(std::string const& newName) {
|
|
auto lastSlash = newName.find_last_of("\\/");
|
|
auto filename = (lastSlash == std::string::npos)
|
|
? newName
|
|
: newName.substr(lastSlash + 1);
|
|
|
|
*m_name = filename;
|
|
if (m_ref)
|
|
return m_ref->setValue(filename);
|
|
else
|
|
return ParserResult::ok(ParseResultType::Matched);
|
|
}
|
|
|
|
|
|
|
|
|
|
Parser& Parser::operator|=( Parser const& other ) {
|
|
m_options.insert( m_options.end(),
|
|
other.m_options.begin(),
|
|
other.m_options.end() );
|
|
m_args.insert(
|
|
m_args.end(), other.m_args.begin(), other.m_args.end() );
|
|
return *this;
|
|
}
|
|
|
|
std::vector<Detail::HelpColumns> Parser::getHelpColumns() const {
|
|
std::vector<Detail::HelpColumns> cols;
|
|
cols.reserve( m_options.size() );
|
|
for ( auto const& o : m_options ) {
|
|
cols.push_back(o.getHelpColumns());
|
|
}
|
|
return cols;
|
|
}
|
|
|
|
void Parser::writeToStream( std::ostream& os ) const {
|
|
if ( !m_exeName.name().empty() ) {
|
|
os << "usage:\n"
|
|
<< " " << m_exeName.name() << ' ';
|
|
bool required = true, first = true;
|
|
for ( auto const& arg : m_args ) {
|
|
if ( first )
|
|
first = false;
|
|
else
|
|
os << ' ';
|
|
if ( arg.isOptional() && required ) {
|
|
os << '[';
|
|
required = false;
|
|
}
|
|
os << '<' << arg.hint() << '>';
|
|
if ( arg.cardinality() == 0 )
|
|
os << " ... ";
|
|
}
|
|
if ( !required )
|
|
os << ']';
|
|
if ( !m_options.empty() )
|
|
os << " options";
|
|
os << "\n\nwhere options are:\n";
|
|
}
|
|
|
|
auto rows = getHelpColumns();
|
|
size_t consoleWidth = CATCH_CONFIG_CONSOLE_WIDTH;
|
|
size_t optWidth = 0;
|
|
for ( auto const& cols : rows )
|
|
optWidth = ( std::max )( optWidth, cols.left.size() + 2 );
|
|
|
|
optWidth = ( std::min )( optWidth, consoleWidth / 2 );
|
|
|
|
for ( auto& cols : rows ) {
|
|
auto row = TextFlow::Column( CATCH_MOVE(cols.left) )
|
|
.width( optWidth )
|
|
.indent( 2 ) +
|
|
TextFlow::Spacer( 4 ) +
|
|
TextFlow::Column( static_cast<std::string>(cols.descriptions) )
|
|
.width( consoleWidth - 7 - optWidth );
|
|
os << row << '\n';
|
|
}
|
|
}
|
|
|
|
Detail::Result Parser::validate() const {
|
|
for ( auto const& opt : m_options ) {
|
|
auto result = opt.validate();
|
|
if ( !result )
|
|
return result;
|
|
}
|
|
for ( auto const& arg : m_args ) {
|
|
auto result = arg.validate();
|
|
if ( !result )
|
|
return result;
|
|
}
|
|
return Detail::Result::ok();
|
|
}
|
|
|
|
Detail::InternalParseResult
|
|
Parser::parse( std::string const& exeName,
|
|
Detail::TokenStream tokens ) const {
|
|
|
|
struct ParserInfo {
|
|
ParserBase const* parser = nullptr;
|
|
size_t count = 0;
|
|
};
|
|
std::vector<ParserInfo> parseInfos;
|
|
parseInfos.reserve( m_options.size() + m_args.size() );
|
|
for ( auto const& opt : m_options ) {
|
|
parseInfos.push_back( { &opt, 0 } );
|
|
}
|
|
for ( auto const& arg : m_args ) {
|
|
parseInfos.push_back( { &arg, 0 } );
|
|
}
|
|
|
|
m_exeName.set( exeName );
|
|
|
|
auto result = Detail::InternalParseResult::ok(
|
|
Detail::ParseState( ParseResultType::NoMatch, CATCH_MOVE(tokens) ) );
|
|
while ( result.value().remainingTokens() ) {
|
|
bool tokenParsed = false;
|
|
|
|
for ( auto& parseInfo : parseInfos ) {
|
|
if ( parseInfo.parser->cardinality() == 0 ||
|
|
parseInfo.count < parseInfo.parser->cardinality() ) {
|
|
result = parseInfo.parser->parse(
|
|
exeName, CATCH_MOVE(result).value().remainingTokens() );
|
|
if ( !result )
|
|
return result;
|
|
if ( result.value().type() !=
|
|
ParseResultType::NoMatch ) {
|
|
tokenParsed = true;
|
|
++parseInfo.count;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( result.value().type() == ParseResultType::ShortCircuitAll )
|
|
return result;
|
|
if ( !tokenParsed )
|
|
return Detail::InternalParseResult::runtimeError(
|
|
"Unrecognised token: " +
|
|
result.value().remainingTokens()->token );
|
|
}
|
|
// !TBD Check missing required options
|
|
return result;
|
|
}
|
|
|
|
Args::Args(int argc, char const* const* argv) :
|
|
m_exeName(argv[0]), m_args(argv + 1, argv + argc) {}
|
|
|
|
Args::Args(std::initializer_list<StringRef> args) :
|
|
m_exeName(*args.begin()),
|
|
m_args(args.begin() + 1, args.end()) {}
|
|
|
|
|
|
Help::Help( bool& showHelpFlag ):
|
|
Opt( [&]( bool flag ) {
|
|
showHelpFlag = flag;
|
|
return ParserResult::ok( ParseResultType::ShortCircuitAll );
|
|
} ) {
|
|
static_cast<Opt&> ( *this )(
|
|
"display usage information" )["-?"]["-h"]["--help"]
|
|
.optional();
|
|
}
|
|
|
|
} // namespace Clara
|
|
} // namespace Catch
|
|
|
|
|
|
|
|
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
namespace Catch {
|
|
|
|
Clara::Parser makeCommandLineParser( ConfigData& config ) {
|
|
|
|
using namespace Clara;
|
|
|
|
auto const setWarning = [&]( std::string const& warning ) {
|
|
if ( warning == "NoAssertions" ) {
|
|
config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::NoAssertions);
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
} else if ( warning == "UnmatchedTestSpec" ) {
|
|
config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec);
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
}
|
|
|
|
return ParserResult ::runtimeError(
|
|
"Unrecognised warning option: '" + warning + '\'' );
|
|
};
|
|
auto const loadTestNamesFromFile = [&]( std::string const& filename ) {
|
|
std::ifstream f( filename.c_str() );
|
|
if( !f.is_open() )
|
|
return ParserResult::runtimeError( "Unable to load input file: '" + filename + '\'' );
|
|
|
|
std::string line;
|
|
while( std::getline( f, line ) ) {
|
|
line = trim(line);
|
|
if( !line.empty() && !startsWith( line, '#' ) ) {
|
|
if( !startsWith( line, '"' ) )
|
|
line = '"' + CATCH_MOVE(line) + '"';
|
|
config.testsOrTags.push_back( line );
|
|
config.testsOrTags.emplace_back( "," );
|
|
}
|
|
}
|
|
//Remove comma in the end
|
|
if(!config.testsOrTags.empty())
|
|
config.testsOrTags.erase( config.testsOrTags.end()-1 );
|
|
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
auto const setTestOrder = [&]( std::string const& order ) {
|
|
if( startsWith( "declared", order ) )
|
|
config.runOrder = TestRunOrder::Declared;
|
|
else if( startsWith( "lexical", order ) )
|
|
config.runOrder = TestRunOrder::LexicographicallySorted;
|
|
else if( startsWith( "random", order ) )
|
|
config.runOrder = TestRunOrder::Randomized;
|
|
else
|
|
return ParserResult::runtimeError( "Unrecognised ordering: '" + order + '\'' );
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
auto const setRngSeed = [&]( std::string const& seed ) {
|
|
if( seed == "time" ) {
|
|
config.rngSeed = generateRandomSeed(GenerateFrom::Time);
|
|
return ParserResult::ok(ParseResultType::Matched);
|
|
} else if (seed == "random-device") {
|
|
config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice);
|
|
return ParserResult::ok(ParseResultType::Matched);
|
|
}
|
|
|
|
// TODO: ideally we should be parsing uint32_t directly
|
|
// fix this later when we add new parse overload
|
|
auto parsedSeed = parseUInt( seed, 0 );
|
|
if ( !parsedSeed ) {
|
|
return ParserResult::runtimeError( "Could not parse '" + seed + "' as seed" );
|
|
}
|
|
config.rngSeed = *parsedSeed;
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
auto const setDefaultColourMode = [&]( std::string const& colourMode ) {
|
|
Optional<ColourMode> maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode ));
|
|
if ( !maybeMode ) {
|
|
return ParserResult::runtimeError(
|
|
"colour mode must be one of: default, ansi, win32, "
|
|
"or none. '" +
|
|
colourMode + "' is not recognised" );
|
|
}
|
|
auto mode = *maybeMode;
|
|
if ( !isColourImplAvailable( mode ) ) {
|
|
return ParserResult::runtimeError(
|
|
"colour mode '" + colourMode +
|
|
"' is not supported in this binary" );
|
|
}
|
|
config.defaultColourMode = mode;
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
auto const setWaitForKeypress = [&]( std::string const& keypress ) {
|
|
auto keypressLc = toLower( keypress );
|
|
if (keypressLc == "never")
|
|
config.waitForKeypress = WaitForKeypress::Never;
|
|
else if( keypressLc == "start" )
|
|
config.waitForKeypress = WaitForKeypress::BeforeStart;
|
|
else if( keypressLc == "exit" )
|
|
config.waitForKeypress = WaitForKeypress::BeforeExit;
|
|
else if( keypressLc == "both" )
|
|
config.waitForKeypress = WaitForKeypress::BeforeStartAndExit;
|
|
else
|
|
return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" );
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
auto const setVerbosity = [&]( std::string const& verbosity ) {
|
|
auto lcVerbosity = toLower( verbosity );
|
|
if( lcVerbosity == "quiet" )
|
|
config.verbosity = Verbosity::Quiet;
|
|
else if( lcVerbosity == "normal" )
|
|
config.verbosity = Verbosity::Normal;
|
|
else if( lcVerbosity == "high" )
|
|
config.verbosity = Verbosity::High;
|
|
else
|
|
return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' );
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
auto const setReporter = [&]( std::string const& userReporterSpec ) {
|
|
if ( userReporterSpec.empty() ) {
|
|
return ParserResult::runtimeError( "Received empty reporter spec." );
|
|
}
|
|
|
|
Optional<ReporterSpec> parsed =
|
|
parseReporterSpec( userReporterSpec );
|
|
if ( !parsed ) {
|
|
return ParserResult::runtimeError(
|
|
"Could not parse reporter spec '" + userReporterSpec +
|
|
"'" );
|
|
}
|
|
|
|
auto const& reporterSpec = *parsed;
|
|
|
|
auto const& factories =
|
|
getRegistryHub().getReporterRegistry().getFactories();
|
|
auto result = factories.find( reporterSpec.name() );
|
|
|
|
if ( result == factories.end() ) {
|
|
return ParserResult::runtimeError(
|
|
"Unrecognized reporter, '" + reporterSpec.name() +
|
|
"'. Check available with --list-reporters" );
|
|
}
|
|
|
|
|
|
const bool hadOutputFile = reporterSpec.outputFile().some();
|
|
config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) );
|
|
// It would be enough to check this only once at the very end, but
|
|
// there is not a place where we could call this check, so do it
|
|
// every time it could fail. For valid inputs, this is still called
|
|
// at most once.
|
|
if (!hadOutputFile) {
|
|
int n_reporters_without_file = 0;
|
|
for (auto const& spec : config.reporterSpecifications) {
|
|
if (spec.outputFile().none()) {
|
|
n_reporters_without_file++;
|
|
}
|
|
}
|
|
if (n_reporters_without_file > 1) {
|
|
return ParserResult::runtimeError( "Only one reporter may have unspecified output file." );
|
|
}
|
|
}
|
|
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
auto const setShardCount = [&]( std::string const& shardCount ) {
|
|
auto parsedCount = parseUInt( shardCount );
|
|
if ( !parsedCount ) {
|
|
return ParserResult::runtimeError(
|
|
"Could not parse '" + shardCount + "' as shard count" );
|
|
}
|
|
if ( *parsedCount == 0 ) {
|
|
return ParserResult::runtimeError(
|
|
"Shard count must be positive" );
|
|
}
|
|
config.shardCount = *parsedCount;
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
|
|
auto const setShardIndex = [&](std::string const& shardIndex) {
|
|
auto parsedIndex = parseUInt( shardIndex );
|
|
if ( !parsedIndex ) {
|
|
return ParserResult::runtimeError(
|
|
"Could not parse '" + shardIndex + "' as shard index" );
|
|
}
|
|
config.shardIndex = *parsedIndex;
|
|
return ParserResult::ok( ParseResultType::Matched );
|
|
};
|
|
|
|
auto cli
|
|
= ExeName( config.processName )
|
|
| Help( config.showHelp )
|
|
| Opt( config.showSuccessfulTests )
|
|
["-s"]["--success"]
|
|
( "include successful tests in output" )
|
|
| Opt( config.shouldDebugBreak )
|
|
["-b"]["--break"]
|
|
( "break into debugger on failure" )
|
|
| Opt( config.noThrow )
|
|
["-e"]["--nothrow"]
|
|
( "skip exception tests" )
|
|
| Opt( config.showInvisibles )
|
|
["-i"]["--invisibles"]
|
|
( "show invisibles (tabs, newlines)" )
|
|
| Opt( config.defaultOutputFilename, "filename" )
|
|
["-o"]["--out"]
|
|
( "default output filename" )
|
|
| Opt( accept_many, setReporter, "name[::key=value]*" )
|
|
["-r"]["--reporter"]
|
|
( "reporter to use (defaults to console)" )
|
|
| Opt( config.name, "name" )
|
|
["-n"]["--name"]
|
|
( "suite name" )
|
|
| Opt( [&]( bool ){ config.abortAfter = 1; } )
|
|
["-a"]["--abort"]
|
|
( "abort at first failure" )
|
|
| Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" )
|
|
["-x"]["--abortx"]
|
|
( "abort after x failures" )
|
|
| Opt( accept_many, setWarning, "warning name" )
|
|
["-w"]["--warn"]
|
|
( "enable warnings" )
|
|
| Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" )
|
|
["-d"]["--durations"]
|
|
( "show test durations" )
|
|
| Opt( config.minDuration, "seconds" )
|
|
["-D"]["--min-duration"]
|
|
( "show test durations for tests taking at least the given number of seconds" )
|
|
| Opt( loadTestNamesFromFile, "filename" )
|
|
["-f"]["--input-file"]
|
|
( "load test names to run from a file" )
|
|
| Opt( config.filenamesAsTags )
|
|
["-#"]["--filenames-as-tags"]
|
|
( "adds a tag for the filename" )
|
|
| Opt( config.sectionsToRun, "section name" )
|
|
["-c"]["--section"]
|
|
( "specify section to run" )
|
|
| Opt( setVerbosity, "quiet|normal|high" )
|
|
["-v"]["--verbosity"]
|
|
( "set output verbosity" )
|
|
| Opt( config.listTests )
|
|
["--list-tests"]
|
|
( "list all/matching test cases" )
|
|
| Opt( config.listTags )
|
|
["--list-tags"]
|
|
( "list all/matching tags" )
|
|
| Opt( config.listReporters )
|
|
["--list-reporters"]
|
|
( "list all available reporters" )
|
|
| Opt( config.listListeners )
|
|
["--list-listeners"]
|
|
( "list all listeners" )
|
|
| Opt( setTestOrder, "decl|lex|rand" )
|
|
["--order"]
|
|
( "test case order (defaults to decl)" )
|
|
| Opt( setRngSeed, "'time'|'random-device'|number" )
|
|
["--rng-seed"]
|
|
( "set a specific seed for random numbers" )
|
|
| Opt( setDefaultColourMode, "ansi|win32|none|default" )
|
|
["--colour-mode"]
|
|
( "what color mode should be used as default" )
|
|
| Opt( config.libIdentify )
|
|
["--libidentify"]
|
|
( "report name and version according to libidentify standard" )
|
|
| Opt( setWaitForKeypress, "never|start|exit|both" )
|
|
["--wait-for-keypress"]
|
|
( "waits for a keypress before exiting" )
|
|
| Opt( config.skipBenchmarks)
|
|
["--skip-benchmarks"]
|
|
( "disable running benchmarks")
|
|
| Opt( config.benchmarkSamples, "samples" )
|
|
["--benchmark-samples"]
|
|
( "number of samples to collect (default: 100)" )
|
|
| Opt( config.benchmarkResamples, "resamples" )
|
|
["--benchmark-resamples"]
|
|
( "number of resamples for the bootstrap (default: 100000)" )
|
|
| Opt( config.benchmarkConfidenceInterval, "confidence interval" )
|
|
["--benchmark-confidence-interval"]
|
|
( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" )
|
|
| Opt( config.benchmarkNoAnalysis )
|
|
["--benchmark-no-analysis"]
|
|
( "perform only measurements; do not perform any analysis" )
|
|
| Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" )
|
|
["--benchmark-warmup-time"]
|
|
( "amount of time in milliseconds spent on warming up each test (default: 100)" )
|
|
| Opt( setShardCount, "shard count" )
|
|
["--shard-count"]
|
|
( "split the tests to execute into this many groups" )
|
|
| Opt( setShardIndex, "shard index" )
|
|
["--shard-index"]
|
|
( "index of the group of tests to execute (see --shard-count)" )
|
|
| Opt( config.allowZeroTests )
|
|
["--allow-running-no-tests"]
|
|
( "Treat 'No tests run' as a success" )
|
|
| Arg( config.testsOrTags, "test name|pattern|tags" )
|
|
( "which test or tests to use" );
|
|
|
|
return cli;
|
|
}
|
|
|
|
} // end namespace Catch
|
|
|
|
|
|
#if defined(__clang__)
|
|
# pragma clang diagnostic push
|
|
# pragma clang diagnostic ignored "-Wexit-time-destructors"
|
|
#endif
|
|
|
|
|
|
|
|
#include <cassert>
|
|
#include <ostream>
|
|
#include <utility>
|
|
|
|
|