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///
156/// An RAII wrapper around an open temporary file on disk. It cleans up the guarded file when the wrapper object
157/// goes out of scope.
158///
160private:
161 std::string fPath;
162 bool fPreserveFile = false;
163
164public:
165 explicit FileRaii(const std::string &path) : fPath(path) {}
166 FileRaii(FileRaii &&) = default;
167 FileRaii(const FileRaii &) = delete;
169 FileRaii &operator=(const FileRaii &) = delete;
171 {
172 if (!fPreserveFile)
173 std::remove(fPath.c_str());
174 }
175 std::string GetPath() const { return fPath; }
176
177 // Useful if you want to keep a test file after the test has finished running
178 // for debugging purposes. Should only be used locally and never pushed.
179 void PreserveFile() { fPreserveFile = true; }
180};
181
182} // namespace TestSupport
183} // namespace ROOT
184
185#define ROOT_EXPECT_DIAG(diag_class, expression, where, expected_diag, match_full) \
186 { \
187 using namespace ROOT::TestSupport; \
188 CheckDiagsRAII EE(diag_class, where, expected_diag, match_full); \
189 expression; \
190 }
191
192#define ROOT_EXPECT_NODIAG(expression) \
193 { \
194 using namespace ROOT::TestSupport; \
195 CheckDiagsRAII EE{}; \
196 expression; \
197 }
198
199#define ROOT_EXPECT_ERROR(expression, where, expected_diag) \
200 ROOT_EXPECT_DIAG(kError, expression, where, expected_diag, true)
201
202#define ROOT_EXPECT_ERROR_PARTIAL(expression, where, expected_diag) \
203 ROOT_EXPECT_DIAG(kError, expression, where, expected_diag, false)
204
205#define ROOT_EXPECT_WARNING(expression, where, expected_diag) \
206 ROOT_EXPECT_DIAG(kWarning, expression, where, expected_diag, true)
207
208#define ROOT_EXPECT_WARNING_PARTIAL(expression, where, expected_diag) \
209 ROOT_EXPECT_DIAG(kWarning, expression, where, expected_diag, false)
210
211#define ROOT_EXPECT_INFO(expression, where, expected_diag) \
212 ROOT_EXPECT_DIAG(kInfo, expression, where, expected_diag, true)
213
214#define ROOT_EXPECT_INFO_PARTIAL(expression, where, expected_diag) \
215 ROOT_EXPECT_DIAG(kInfo, expression, where, expected_diag, false)
216
217#define ROOT_EXPECT_SYSERROR(expression, where, expected_diag) \
218 ROOT_EXPECT_DIAG(kSysError, expression, where, expected_diag, true)
219
220#define ROOT_EXPECT_SYSERROR_PARTIAL(expression, where, expected_diag) \
221 ROOT_EXPECT_DIAG(kSysError, expression, where, expected_diag, false)
222
223#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.
An RAII wrapper around an open temporary file on disk.
FileRaii(const FileRaii &)=delete
std::string GetPath() const
FileRaii(const std::string &path)
FileRaii & operator=(const FileRaii &)=delete
FileRaii(FileRaii &&)=default
FileRaii & operator=(FileRaii &&)=default
Allows a user function to catch and filter/analyse ROOT and cling diagnostics, e.g.
FilterDiagsRAII(ErrorHandlerFunc_t fn)
@ kInfo
Informational messages; used for instance for tracing.
@ kError
An error.
@ kWarning
Warnings about likely unexpected behavior.