summaryrefslogtreecommitdiffstats
path: root/tools/slang-test/test-reporter.h
blob: 9c13846158ad6c01bbe2688d60083bfcb376eeaf (plain)
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
// test-reporter.h

#ifndef TEST_REPORTER_H_INCLUDED
#define TEST_REPORTER_H_INCLUDED

#include "../../source/core/slang-dictionary.h"
#include "../../source/core/slang-platform.h"
#include "../../source/core/slang-std-writers.h"
#include "../../source/core/slang-string-util.h"
#include "unit-test/slang-unit-test.h"

#include <mutex>

// Forward declaration
enum class VerbosityLevel;

enum class TestOutputMode
{
    Default = 0, ///< Default mode is to write test results to the console
    AppVeyor,    ///< For AppVeyor continuous integration
    Travis,      ///< We currently don't specialize for Travis, but maybe we should.
    XUnit,    ///< xUnit original format  https://nose.readthedocs.io/en/latest/plugins/xunit.html
    XUnit2,   ///< https://xunit.github.io/docs/format-xml-v2
    TeamCity, ///< Output suitable for teamcity
};

class TestReporter : public ITestReporter
{
public:
    struct TestInfo
    {
        TestResult testResult = TestResult::Ignored;
        Slang::String name;
        Slang::String message;      ///< Message that is specific for the testResult
        double executionTime = 0.0; ///< <= 0.0 if not defined. Time is in seconds.
    };

    class TestScope
    {
    public:
        TestScope(TestReporter* reporter, const Slang::String& testName)
            : m_reporter(reporter)
        {
            reporter->startTest(testName.getBuffer());
        }
        ~TestScope() { m_reporter->endTest(); }

    protected:
        TestReporter* m_reporter;
    };

    class SuiteScope
    {
    public:
        SuiteScope(TestReporter* reporter, const Slang::String& suiteName)
            : m_reporter(reporter)
        {
            reporter->startSuite(suiteName);
        }
        ~SuiteScope() { m_reporter->endSuite(); }

    protected:
        TestReporter* m_reporter;
    };

    void startSuite(const Slang::String& name);
    void endSuite();

    TestResult adjustResult(Slang::UnownedStringSlice testName, TestResult result);

    virtual SLANG_NO_THROW void SLANG_MCALL startTest(const char* testName) override;
    virtual SLANG_NO_THROW void SLANG_MCALL addResult(TestResult result) override;
    virtual SLANG_NO_THROW void SLANG_MCALL addResultWithLocation(
        TestResult result,
        const char* testText,
        const char* file,
        int line) override;
    virtual SLANG_NO_THROW void SLANG_MCALL addResultWithLocation(
        bool testSucceeded,
        const char* testText,
        const char* file,
        int line) override;
    virtual SLANG_NO_THROW void SLANG_MCALL addExecutionTime(double time) override;
    virtual SLANG_NO_THROW void SLANG_MCALL endTest() override;

    /// Runs start/endTest and outputs the result
    TestResult addTest(const Slang::String& testName, bool isPass);
    /// Effectively runs start/endTest (so cannot be called inside start/endTest).
    void addTest(const Slang::String& testName, TestResult testResult);

    // Called for an error in the test-runner (not for an error involving a test itself).
    void message(TestMessageType type, const Slang::String& errorText);
    SLANG_ATTR_PRINTF(3, 4)
    void messageFormat(TestMessageType type, char const* message, ...);
    virtual SLANG_NO_THROW void SLANG_MCALL
    message(TestMessageType type, char const* message) override;

    void dumpOutputDifference(
        const Slang::String& expectedOutput,
        const Slang::String& actualOutput);

    void consolidateWith(TestReporter* other);

    /// True if can write output directly to stderr
    bool canWriteStdError() const;


    /// Returns true if all run tests succeeded
    bool didAllSucceed() const;

    /// Returns a result from the current test
    TestResult getResult() const;

    void outputSummary();

    SlangResult init(
        TestOutputMode outputMode,
        const Slang::HashSet<Slang::String>& expectedFailureList,
        bool isSubReporter = false);

    /// Ctor
    TestReporter();
    /// Dtor
    ~TestReporter();

    static TestResult combine(TestResult a, TestResult b) { return (a > b) ? a : b; }

    static TestReporter* get() { return s_reporter; }
    static void set(TestReporter* reporter) { s_reporter = reporter; }

    Slang::List<TestInfo> m_testInfos;

    Slang::List<Slang::String> m_suiteStack;

    int m_totalTestCount;
    int m_passedTestCount;
    int m_failedTestCount;
    int m_ignoredTestCount;
    int m_expectedFailedTestCount;

    int m_maxFailTestResults; ///< Maximum amount of results per test. If 0 it's infinite.

    TestOutputMode m_outputMode = TestOutputMode::Default;
    bool m_dumpOutputOnFailure;
    VerbosityLevel m_verbosity;
    bool m_hideIgnored = false;
    bool m_isSubReporter = false;
    Slang::HashSet<Slang::String> m_expectedFailureList;

protected:
    void _addResult(TestInfo info);

    Slang::StringBuilder m_currentMessage;
    TestInfo m_currentInfo;
    int m_numCurrentResults;
    int m_numFailResults;

    bool m_inTest;

    std::recursive_mutex m_mutex;

    static TestReporter* s_reporter;
};

#endif // TEST_REPORTER_H_INCLUDED