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 <stdexcept>
26#include <vector>
27
28#include "gtest/gtest.h"
29
30namespace ROOT {
31namespace TestSupport {
32
33/// \brief Allows a user function to catch and filter/analyse ROOT and cling diagnostics, e.g.
34/// ```c++
35/// FilterDiagsRAII RAII([] (int level, Bool_t abort,
36/// const char *location, const char *msg) {
37/// EXPECT_THAT(msg, Not(HasSubstr("-Wunused-result")));
38/// });
39/// ```
42 public:
45 gInterpreter->ReportDiagnosticsToErrorHandler();
46 }
48 gInterpreter->ReportDiagnosticsToErrorHandler(/*enable=*/false);
50 }
51};
52
53/// Install a ROOT diagnostic handler to analyse diagnostics.
54/// It will record all diagnostics during its lifetime, and analyse them at destruction.
55/// Required and/or optional diagnostics need to be predefined with expected location and message.
56/// Unexpected or missing diagnostics will lead to gtest failures.
57///
58/// Example:
59/// ```c++
60/// CheckDiagsRAII diagRAII{CheckDiagsRAII::EDK_Error, "TFile::TFile", "<Expected message>"};
61/// diagRAII.optionalDiag(kInfo, "TFile::TFile", "Message that is issued only sometimes");
62/// <test code>
63///
64/// ```
66 public:
67 /// Register this instance as diagnostic handler.
68 /// 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)
81 {
82 requiredDiag(severity, inRoutine, E);
83 }
84
86
87 /// Register a new diagnostic to check for.
88 /// \param severity One of kInfo kWarning kError kSysError.
89 /// \param location Function name where the diagnostic should be issued.
90 /// \param message Diagnostic message.
91 /// \param matchFullMessage If true, the message must be exactly identical.
92 /// If false, it's sufficient that `message` is a substring of the diagnostic message.
93 void requiredDiag(int severity, std::string location, std::string message, bool matchFullMessage = true) {
94 if (severity != kInfo && severity != kWarning && severity != kError && severity != kSysError)
95 throw std::invalid_argument("ExpectedDiagRAII::requiredDiag(): severity is none of kInfo, kWarning, kError, kSysError");
96
97 fExpectedDiags.push_back({severity, std::move(location), std::move(message), matchFullMessage, false, 0});
98 }
99
100 /// Register a diagnostic that can, but need not necessarily be issued.
101 /// \param severity One of kInfo kWarning kError kSysError.
102 /// \param location Function name where the diagnostic should be issued.
103 /// \param message Diagnostic message.
104 /// \param matchFullMessage If true, the message must be exactly identical.
105 /// If false, it's sufficient that `message` is a substring of the diagnostic message.
106 void optionalDiag(int severity, std::string location, std::string message, bool matchFullMessage = true) {
107 if (severity != kInfo && severity != kWarning && severity != kError && severity != kSysError)
108 throw std::invalid_argument("ExpectedDiagRAII::optionalDiag(): severity is none of kInfo, kWarning, kError, kSysError");
109
110 fExpectedDiags.push_back({severity, std::move(location), std::move(message), matchFullMessage, true, 0});
111 }
112
113 private:
114 /// Message handler that hands over all diagnostics to the currently active instance.
115 static void callback(int severity, bool abort, const char * location, const char * msg) {
116 if (sActiveInstance) {
117 sActiveInstance->checkDiag(severity, location, msg);
118 } else {
119 throw std::logic_error("ExpectedDiagRAII::callback called without an active message handler.");
120 }
121
122 if (abort) {
123 std::cerr << "ROOT::TestSupport::CheckDiagsRAII: Forced to abort because of diagnostic with severity "
124 << severity << " in '" << location << "' reading '" << msg << "'\n";
125 ::abort();
126 }
127 }
128
129 /// Check all received diags against list of expected ones.
130 void checkDiag(int severity, const char *location, const char *msg);
131
132 struct Diag_t {
134 std::string location;
135 std::string message;
136 bool matchFullString = true;
137 bool optional = false;
139 };
140 friend std::ostream &operator<<(std::ostream &stream, Diag_t const &diag);
141
142 std::vector<Diag_t> fExpectedDiags;
143 std::vector<Diag_t> fUnexpectedDiags;
144
145 CheckDiagsRAII * const fOldInstance; /// Last active handler in case handlers are nested.
146 ErrorHandlerFunc_t const fOldErrorHandler; /// Last active error handler function.
147
148 static CheckDiagsRAII * sActiveInstance; /// Instance that will receive ROOT's callbacks.
149};
150
151} } // namespace ROOT::TestSupport
152
153#define ROOT_EXPECT_ERROR(expression, where, expected_diag ) \
154{ \
155 using namespace ROOT::TestSupport; \
156 CheckDiagsRAII EE(kError, where, \
157 expected_diag); \
158 expression; \
159}
160
161#define ROOT_EXPECT_WARNING(expression, where, expected_diag) \
162{ \
163 using namespace ROOT::TestSupport; \
164 CheckDiagsRAII EE(kWarning, where, \
165 expected_diag); \
166 expression; \
167}
168
169#define ROOT_EXPECT_INFO(expression, where, expected_diag) \
170{ \
171 using namespace ROOT::TestSupport; \
172 CheckDiagsRAII EE(kInfo, where, \
173 expected_diag); \
174 expression; \
175}
176
177#define ROOT_EXPECT_NODIAG(expression) \
178{ \
179 using namespace ROOT::TestSupport; \
180 CheckDiagsRAII EE{}; \
181 expression; \
182}
183
184#define ROOT_EXPECT_SYSERROR(expression, where, expected_diag) \
185{ \
186 using namespace ROOT::TestSupport; \
187 CheckDiagsRAII EE(kSysError, where, \
188 expected_diag); \
189 expression; \
190}
191
192#endif // ROOT_UNITTESTSUPPORT_H
constexpr Int_t kError
Definition TError.h:46
ErrorHandlerFunc_t GetErrorHandler()
Returns the current error handler function.
Definition TError.cxx:100
constexpr Int_t kWarning
Definition TError.h:45
void(* ErrorHandlerFunc_t)(int level, Bool_t abort, const char *location, const char *msg)
Definition TError.h:70
constexpr Int_t kInfo
Definition TError.h:44
constexpr Int_t kSysError
Definition TError.h:48
ErrorHandlerFunc_t SetErrorHandler(ErrorHandlerFunc_t newhandler)
Set an errorhandler function. Returns the old handler.
Definition TError.cxx:90
#define gInterpreter
Install a ROOT diagnostic handler to analyse diagnostics.
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.
CheckDiagsRAII(int severity, std::string inRoutine, std::string E)
Construct from ROOT's kWarning, kError, ... and strings specifying location and message.
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)
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...