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 struct Diag_t {
116 std::string location;
117 std::string message;
118 bool matchFullString = true;
119 bool optional = false;
121 };
122
123 /// Message handler that hands over all diagnostics to the currently active instance.
124 static void callback(int severity, bool abort, const char * location, const char * msg) {
125 if (sActiveInstance) {
126 sActiveInstance->checkDiag(severity, location, msg);
127 } else {
128 throw std::logic_error("ExpectedDiagRAII::callback called without an active message handler.");
129 }
130
131 if (abort) {
132 std::cerr << "ROOT::TestSupport::CheckDiagsRAII: Forced to abort because of diagnostic with severity "
133 << severity << " in '" << location << "' reading '" << msg << "'\n";
134 ::abort();
135 }
136 }
137
138 /// Check all received diags against list of expected ones.
139 void checkDiag(int severity, const char * location, const char * msg);
140 /// Print the diags in `diags` to the terminal.
141 void printDiags(std::vector<Diag_t> const & diags) const;
142
143 std::vector<Diag_t> fExpectedDiags;
144 std::vector<Diag_t> fUnexpectedDiags;
145
146 CheckDiagsRAII * const fOldInstance; /// Last active handler in case handlers are nested.
147 ErrorHandlerFunc_t const fOldErrorHandler; /// Last active error handler function.
148
149 static CheckDiagsRAII * sActiveInstance; /// Instance that will receive ROOT's callbacks.
150};
151
152} } // namespace ROOT::TestSupport
153
154#define ROOT_EXPECT_ERROR(expression, where, expected_diag ) \
155{ \
156 using namespace ROOT::TestSupport; \
157 CheckDiagsRAII EE(kError, where, \
158 expected_diag); \
159 expression; \
160}
161
162#define ROOT_EXPECT_WARNING(expression, where, expected_diag) \
163{ \
164 using namespace ROOT::TestSupport; \
165 CheckDiagsRAII EE(kWarning, where, \
166 expected_diag); \
167 expression; \
168}
169
170#define ROOT_EXPECT_INFO(expression, where, expected_diag) \
171{ \
172 using namespace ROOT::TestSupport; \
173 CheckDiagsRAII EE(kInfo, where, \
174 expected_diag); \
175 expression; \
176}
177
178#define ROOT_EXPECT_NODIAG(expression) \
179{ \
180 using namespace ROOT::TestSupport; \
181 CheckDiagsRAII EE{}; \
182 expression; \
183}
184
185#define ROOT_EXPECT_SYSERROR(expression, where, expected_diag) \
186{ \
187 using namespace ROOT::TestSupport; \
188 CheckDiagsRAII EE(kSysError, where, \
189 expected_diag); \
190 expression; \
191}
192
193#endif // ROOT_UNITTESTSUPPORT_H
const Int_t kError
Definition TError.cxx:38
const Int_t kSysError
Definition TError.cxx:40
ErrorHandlerFunc_t GetErrorHandler()
Returns the current error handler function.
Definition TError.cxx:112
const Int_t kWarning
Definition TError.cxx:37
void(* ErrorHandlerFunc_t)(int level, Bool_t abort, const char *location, const char *msg)
Definition TError.h:69
ErrorHandlerFunc_t SetErrorHandler(ErrorHandlerFunc_t newhandler)
Set an errorhandler function. Returns the old handler.
Definition TError.cxx:102
const Int_t kInfo
Definition TError.cxx:36
#define gInterpreter
Install a ROOT diagnostic handler to analyse diagnostics.
void printDiags(std::vector< Diag_t > const &diags) const
Print the diags in diags to the terminal.
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
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)
This file contains a specialised ROOT message handler to test for diagnostic in unit tests.