Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TestSupport.hxx
Go to the documentation of this file.
1/// \file TestSupport.hxx
2///
3/// \brief The file contains facilities allowing easier writing of in-tree unit tests
4///
5/// \author Pratyush Das <reikdas@gmail.com>
6/// \author Vassil Vassilev <vvasilev@cern.ch>
7/// \author Stephan Hageboeck <stephan.hageboeck@cern.ch>
8///
9/// \date April, 2020
10
11/*************************************************************************
12 * Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. *
13 * All rights reserved. *
14 * *
15 * For the licensing terms see $ROOTSYS/LICENSE. *
16 * For the list of contributors see $ROOTSYS/README/CREDITS. *
17 *************************************************************************/
18
19#ifndef ROOT_UNITTESTSUPPORT_H
20#define ROOT_UNITTESTSUPPORT_H
21
22#include "TError.h"
23#include "TInterpreter.h"
24
25#include <iostream>
26#include <stdexcept>
27#include <vector>
28
29namespace ROOT {
30namespace TestSupport {
31
32/// \brief Allows a user function to catch and filter/analyse ROOT and cling diagnostics, e.g.
33/// ```c++
34/// FilterDiagsRAII RAII([] (int level, Bool_t abort,
35/// const char *location, const char *msg) {
36/// EXPECT_THAT(msg, Not(HasSubstr("-Wunused-result")));
37/// });
38/// ```
41
42public:
44 {
46 gInterpreter->ReportDiagnosticsToErrorHandler();
47 }
49 {
50 gInterpreter->ReportDiagnosticsToErrorHandler(/*enable=*/false);
52 }
53};
54
55/// Install a ROOT diagnostic handler to analyse diagnostics.
56/// It will record all diagnostics during its lifetime, and analyse them at destruction.
57/// Required and/or optional diagnostics need to be predefined with expected location and message.
58/// Unexpected or missing diagnostics will lead to gtest failures.
59///
60/// Example:
61/// ```c++
62/// CheckDiagsRAII diagRAII{CheckDiagsRAII::EDK_Error, "TFile::TFile", "<Expected message>"};
63/// diagRAII.optionalDiag(kInfo, "TFile::TFile", "Message that is issued only sometimes");
64/// <test code>
65///
66/// ```
68public:
69 /// Register this instance as diagnostic handler.
70 /// With no further action, any diagnostic will lead to a test failure.
72 {
73 sActiveInstance = this;
75 gInterpreter->ReportDiagnosticsToErrorHandler(/*enable=*/true);
76 }
77
78 /// Construct from ROOT's `kWarning, kError, ...` and strings specifying location and message.
79 CheckDiagsRAII(int severity, std::string inRoutine, std::string E, bool matchFullMessage = true) : CheckDiagsRAII()
80 {
82 }
83
85
86 /// Register a new diagnostic to check for.
87 /// \param severity One of kInfo kWarning kError kSysError.
88 /// \param location Function name where the diagnostic should be issued.
89 /// \param message Diagnostic message.
90 /// \param matchFullMessage If true, the message must be exactly identical.
91 /// If false, it's sufficient that `message` is a substring of the diagnostic message.
92 void requiredDiag(int severity, std::string location, std::string message, bool matchFullMessage = true)
93 {
94 if (severity != kInfo && severity != kWarning && severity != kError && severity != kSysError)
95 throw std::invalid_argument(
96 "ExpectedDiagRAII::requiredDiag(): severity is none of kInfo, kWarning, kError, kSysError");
97
98 fExpectedDiags.push_back({severity, std::move(location), std::move(message), matchFullMessage, false, 0});
99 }
100
101 /// Register a diagnostic that can, but need not necessarily be issued.
102 /// \param severity One of kInfo kWarning kError kSysError.
103 /// \param location Function name where the diagnostic should be issued.
104 /// \param message Diagnostic message.
105 /// \param matchFullMessage If true, the message must be exactly identical.
106 /// If false, it's sufficient that `message` is a substring of the diagnostic message.
107 void optionalDiag(int severity, std::string location, std::string message, bool matchFullMessage = true)
108 {
109 if (severity != kInfo && severity != kWarning && severity != kError && severity != kSysError)
110 throw std::invalid_argument(
111 "ExpectedDiagRAII::optionalDiag(): severity is none of kInfo, kWarning, kError, kSysError");
112
113 fExpectedDiags.push_back({severity, std::move(location), std::move(message), matchFullMessage, true, 0});
114 }
115
116private:
117 /// Message handler that hands over all diagnostics to the currently active instance.
118 static void callback(int severity, bool abort, const char *location, const char *msg)
119 {
120 if (sActiveInstance) {
121 sActiveInstance->checkDiag(severity, location, msg);
122 } else {
123 throw std::logic_error("ExpectedDiagRAII::callback called without an active message handler.");
124 }
125
126 if (abort) {
127 std::cerr << "ROOT::TestSupport::CheckDiagsRAII: Forced to abort because of diagnostic with severity "
128 << severity << " in '" << location << "' reading '" << msg << "'\n";
129 ::abort();
130 }
131 }
132
133 /// Check all received diags against list of expected ones.
134 void checkDiag(int severity, const char *location, const char *msg);
135
136 struct Diag_t {
138 std::string location;
139 std::string message;
140 bool matchFullString = true;
141 bool optional = false;
143 };
144 friend std::ostream &operator<<(std::ostream &stream, Diag_t const &diag);
145
146 std::vector<Diag_t> fExpectedDiags;
147 std::vector<Diag_t> fUnexpectedDiags;
148
149 CheckDiagsRAII *const fOldInstance; /// Last active handler in case handlers are nested.
150 ErrorHandlerFunc_t const fOldErrorHandler; /// Last active error handler function.
151
152 static CheckDiagsRAII *sActiveInstance; /// Instance that will receive ROOT's callbacks.
153};
154
155} // namespace TestSupport
156} // namespace ROOT
157
158#define ROOT_EXPECT_DIAG(diag_class, expression, where, expected_diag, match_full) \
159 { \
160 using namespace ROOT::TestSupport; \
161 CheckDiagsRAII EE(diag_class, where, expected_diag, match_full); \
162 expression; \
163 }
164
165#define ROOT_EXPECT_NODIAG(expression) \
166 { \
167 using namespace ROOT::TestSupport; \
168 CheckDiagsRAII EE{}; \
169 expression; \
170 }
171
172#define ROOT_EXPECT_ERROR(expression, where, expected_diag) \
173 ROOT_EXPECT_DIAG(kError, expression, where, expected_diag, true)
174
175#define ROOT_EXPECT_ERROR_PARTIAL(expression, where, expected_diag) \
176 ROOT_EXPECT_DIAG(kError, expression, where, expected_diag, false)
177
178#define ROOT_EXPECT_WARNING(expression, where, expected_diag) \
179 ROOT_EXPECT_DIAG(kWarning, expression, where, expected_diag, true)
180
181#define ROOT_EXPECT_WARNING_PARTIAL(expression, where, expected_diag) \
182 ROOT_EXPECT_DIAG(kWarning, expression, where, expected_diag, false)
183
184#define ROOT_EXPECT_INFO(expression, where, expected_diag) \
185 ROOT_EXPECT_DIAG(kInfo, expression, where, expected_diag, true)
186
187#define ROOT_EXPECT_INFO_PARTIAL(expression, where, expected_diag) \
188 ROOT_EXPECT_DIAG(kInfo, expression, where, expected_diag, false)
189
190#define ROOT_EXPECT_SYSERROR(expression, where, expected_diag) \
191 ROOT_EXPECT_DIAG(kSysError, expression, where, expected_diag, true)
192
193#define ROOT_EXPECT_SYSERROR_PARTIAL(expression, where, expected_diag) \
194 ROOT_EXPECT_DIAG(kSysError, expression, where, expected_diag, false)
195
196#endif // ROOT_UNITTESTSUPPORT_H
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
ErrorHandlerFunc_t GetErrorHandler()
Returns the current error handler function.
Definition TError.cxx:102
void(* ErrorHandlerFunc_t)(int level, Bool_t abort, const char *location, const char *msg)
Definition TError.h:71
constexpr Int_t kSysError
Definition TError.h:49
ErrorHandlerFunc_t SetErrorHandler(ErrorHandlerFunc_t newhandler)
Set an errorhandler function. Returns the old handler.
Definition TError.cxx:92
#define gInterpreter
Install a ROOT diagnostic handler to analyse diagnostics.
CheckDiagsRAII(int severity, std::string inRoutine, std::string E, bool matchFullMessage=true)
Construct from ROOT's kWarning, kError, ... and strings specifying location and message.
static void callback(int severity, bool abort, const char *location, const char *msg)
Message handler that hands over all diagnostics to the currently active instance.
ErrorHandlerFunc_t const fOldErrorHandler
Last active handler in case handlers are nested.
void optionalDiag(int severity, std::string location, std::string message, bool matchFullMessage=true)
Register a diagnostic that can, but need not necessarily be issued.
static CheckDiagsRAII * sActiveInstance
Last active error handler function.
std::vector< Diag_t > fUnexpectedDiags
std::vector< Diag_t > fExpectedDiags
void checkDiag(int severity, const char *location, const char *msg)
Check all received diags against list of expected ones.
CheckDiagsRAII *const fOldInstance
friend std::ostream & operator<<(std::ostream &stream, Diag_t const &diag)
void requiredDiag(int severity, std::string location, std::string message, bool matchFullMessage=true)
Register a new diagnostic to check for.
CheckDiagsRAII()
Register this instance as diagnostic handler.
Allows a user function to catch and filter/analyse ROOT and cling diagnostics, e.g.
FilterDiagsRAII(ErrorHandlerFunc_t fn)
Namespace for new ROOT classes and functions.
@ kInfo
Informational messages; used for instance for tracing.
@ kError
An error.
@ kWarning
Warnings about likely unexpected behavior.