aboutsummaryrefslogtreecommitdiffstats
path: root/src/catch2/catch_config.cpp
blob: 352c1f424a1ac7fa2eb0b5fb938304b862215d2e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//              Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
//   (See accompanying file LICENSE.txt or copy at
//        https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_config.hpp>
#include <catch2/catch_user_config.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_parse_numbers.hpp>
#include <catch2/internal/catch_stdstreams.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_string_manip.hpp>
#include <catch2/internal/catch_test_spec_parser.hpp>
#include <catch2/interfaces/catch_interfaces_tag_alias_registry.hpp>
#include <catch2/internal/catch_getenv.hpp>

#include <fstream>

namespace Catch {

    namespace {
        static bool enableBazelEnvSupport() {
#if defined( CATCH_CONFIG_BAZEL_SUPPORT )
            return true;
#else
            return Detail::getEnv( "BAZEL_TEST" ) != nullptr;
#endif
        }

        struct bazelShardingOptions {
            unsigned int shardIndex, shardCount;
            std::string shardFilePath;
        };

        static Optional<bazelShardingOptions> readBazelShardingOptions() {
            const auto bazelShardIndex = Detail::getEnv( "TEST_SHARD_INDEX" );
            const auto bazelShardTotal = Detail::getEnv( "TEST_TOTAL_SHARDS" );
            const auto bazelShardInfoFile = Detail::getEnv( "TEST_SHARD_STATUS_FILE" );


            const bool has_all =
                bazelShardIndex && bazelShardTotal && bazelShardInfoFile;
            if ( !has_all ) {
                // We provide nice warning message if the input is
                // misconfigured.
                auto warn = []( const char* env_var ) {
                    Catch::cerr()
                        << "Warning: Bazel shard configuration is missing '"
                        << env_var << "'. Shard configuration is skipped.\n";
                };
                if ( !bazelShardIndex ) {
                    warn( "TEST_SHARD_INDEX" );
                }
                if ( !bazelShardTotal ) {
                    warn( "TEST_TOTAL_SHARDS" );
                }
                if ( !bazelShardInfoFile ) {
                    warn( "TEST_SHARD_STATUS_FILE" );
                }
                return {};
            }

            auto shardIndex = parseUInt( bazelShardIndex );
            if ( !shardIndex ) {
                Catch::cerr()
                    << "Warning: could not parse 'TEST_SHARD_INDEX' ('" << bazelShardIndex
                    << "') as unsigned int.\n";
                return {};
            }
            auto shardTotal = parseUInt( bazelShardTotal );
            if ( !shardTotal ) {
                Catch::cerr()
                    << "Warning: could not parse 'TEST_TOTAL_SHARD' ('"
                    << bazelShardTotal << "') as unsigned int.\n";
                return {};
            }

            return bazelShardingOptions{
                *shardIndex, *shardTotal, bazelShardInfoFile };

        }
    } // end namespace


    bool operator==( ProcessedReporterSpec const& lhs,
                     ProcessedReporterSpec const& rhs ) {
        return lhs.name == rhs.name &&
               lhs.outputFilename == rhs.outputFilename &&
               lhs.colourMode == rhs.colourMode &&
               lhs.customOptions == rhs.customOptions;
    }

    Config::Config( ConfigData const& data ):
        m_data( data ) {
        // We need to trim filter specs to avoid trouble with superfluous
        // whitespace (esp. important for bdd macros, as those are manually
        // aligned with whitespace).

        for (auto& elem : m_data.testsOrTags) {
            elem = trim(elem);
        }
        for (auto& elem : m_data.sectionsToRun) {
            elem = trim(elem);
        }

        // Insert the default reporter if user hasn't asked for a specific one
        if ( m_data.reporterSpecifications.empty() ) {
#if defined( CATCH_CONFIG_DEFAULT_REPORTER )
            const auto default_spec = CATCH_CONFIG_DEFAULT_REPORTER;
#else
            const auto default_spec = "console";
#endif
            auto parsed = parseReporterSpec(default_spec);
            CATCH_ENFORCE( parsed,
                           "Cannot parse the provided default reporter spec: '"
                               << default_spec << '\'' );
            m_data.reporterSpecifications.push_back( std::move( *parsed ) );
        }

        if ( enableBazelEnvSupport() ) {
            readBazelEnvVars();
        }

        // Bazel support can modify the test specs, so parsing has to happen
        // after reading Bazel env vars.
        TestSpecParser parser( ITagAliasRegistry::get() );
        if ( !m_data.testsOrTags.empty() ) {
            m_hasTestFilters = true;
            for ( auto const& testOrTags : m_data.testsOrTags ) {
                parser.parse( testOrTags );
            }
        }
        m_testSpec = parser.testSpec();


        // We now fixup the reporter specs to handle default output spec,
        // default colour spec, etc
        bool defaultOutputUsed = false;
        for ( auto const& reporterSpec : m_data.reporterSpecifications ) {
            // We do the default-output check separately, while always
            // using the default output below to make the code simpler
            // and avoid superfluous copies.
            if ( reporterSpec.outputFile().none() ) {
                CATCH_ENFORCE( !defaultOutputUsed,
                               "Internal error: cannot use default output for "
                               "multiple reporters" );
                defaultOutputUsed = true;
            }

            m_processedReporterSpecs.push_back( ProcessedReporterSpec{
                reporterSpec.name(),
                reporterSpec.outputFile() ? *reporterSpec.outputFile()
                                          : data.defaultOutputFilename,
                reporterSpec.colourMode().valueOr( data.defaultColourMode ),
                reporterSpec.customOptions() } );
        }
    }

    Config::~Config() = default;


    bool Config::listTests() const          { return m_data.listTests; }
    bool Config::listTags() const           { return m_data.listTags; }
    bool Config::listReporters() const      { return m_data.listReporters; }
    bool Config::listListeners() const      { return m_data.listListeners; }

    std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
    std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }

    std::vector<ReporterSpec> const& Config::getReporterSpecs() const {
        return m_data.reporterSpecifications;
    }

    std::vector<ProcessedReporterSpec> const&
    Config::getProcessedReporterSpecs() const {
        return m_processedReporterSpecs;
    }

    TestSpec const& Config::testSpec() const { return m_testSpec; }
    bool Config::hasTestFilters() const { return m_hasTestFilters; }

    bool Config::showHelp() const { return m_data.showHelp; }

    // IConfig interface
    bool Config::allowThrows() const                   { return !m_data.noThrow; }
    StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
    bool Config::includeSuccessfulResults() const      { return m_data.showSuccessfulTests; }
    bool Config::warnAboutMissingAssertions() const {
        return !!( m_data.warnings & WarnAbout::NoAssertions );
    }
    bool Config::warnAboutUnmatchedTestSpecs() const {
        return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec );
    }
    bool Config::zeroTestsCountAsSuccess() const       { return m_data.allowZeroTests; }
    ShowDurations Config::showDurations() const        { return m_data.showDurations; }
    double Config::minDuration() const                 { return m_data.minDuration; }
    TestRunOrder Config::runOrder() const              { return m_data.runOrder; }
    uint32_t Config::rngSeed() const                   { return m_data.rngSeed; }
    unsigned int Config::shardCount() const            { return m_data.shardCount; }
    unsigned int Config::shardIndex() const            { return m_data.shardIndex; }
    ColourMode Config::defaultColourMode() const       { return m_data.defaultColourMode; }
    bool Config::shouldDebugBreak() const              { return m_data.shouldDebugBreak; }
    int Config::abortAfter() const                     { return m_data.abortAfter; }
    bool Config::showInvisibles() const                { return m_data.showInvisibles; }
    Verbosity Config::verbosity() const                { return m_data.verbosity; }

    bool Config::skipBenchmarks() const                           { return m_data.skipBenchmarks; }
    bool Config::benchmarkNoAnalysis() const                      { return m_data.benchmarkNoAnalysis; }
    unsigned int Config::benchmarkSamples() const                 { return m_data.benchmarkSamples; }
    double Config::benchmarkConfidenceInterval() const            { return m_data.benchmarkConfidenceInterval; }
    unsigned int Config::benchmarkResamples() const               { return m_data.benchmarkResamples; }
    std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); }

    void Config::readBazelEnvVars() {
        // Register a JUnit reporter for Bazel. Bazel sets an environment
        // variable with the path to XML output. If this file is written to
        // during test, Bazel will not generate a default XML output.
        // This allows the XML output file to contain higher level of detail
        // than what is possible otherwise.
        const auto bazelOutputFile = Detail::getEnv( "XML_OUTPUT_FILE" );

        if ( bazelOutputFile ) {
            m_data.reporterSpecifications.push_back(
                { "junit", std::string( bazelOutputFile ), {}, {} } );
        }

        const auto bazelTestSpec = Detail::getEnv( "TESTBRIDGE_TEST_ONLY" );
        if ( bazelTestSpec ) {
            // Presumably the test spec from environment should overwrite
            // the one we got from CLI (if we got any)
            m_data.testsOrTags.clear();
            m_data.testsOrTags.push_back( bazelTestSpec );
        }

        const auto bazelShardOptions = readBazelShardingOptions();
        if ( bazelShardOptions ) {
            std::ofstream f( bazelShardOptions->shardFilePath,
                             std::ios_base::out | std::ios_base::trunc );
            if ( f.is_open() ) {
                f << "";
                m_data.shardIndex = bazelShardOptions->shardIndex;
                m_data.shardCount = bazelShardOptions->shardCount;
            }
        }
    }

} // end namespace Catch