Skip to content

Commit eb15c43

Browse files
committed
Add Catch.hpp for unit testing, with appveyor hooks
1 parent af9b74f commit eb15c43

File tree

17 files changed

+12018
-3
lines changed

17 files changed

+12018
-3
lines changed

docs/credits_acknowledgements.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ The following libraries and components are incorporated into RenderDoc, listed h
9696

9797
Provides the ability to disassemble shaders from any API representation into compiled GCN ISA for lower level analysis.
9898

99+
* `Catch <https://github.com/philsquared/Catch>`_ - Copyright (c) 2012 Two Blue Cubes Ltd., distributed under the Boost Software License.
100+
101+
Implements unit testing during development.
102+
99103
Thanks
100104
------
101105

renderdoc/3rdparty/catch/LICENSE.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Boost Software License - Version 1.0 - August 17th, 2003
2+
3+
Permission is hereby granted, free of charge, to any person or organization
4+
obtaining a copy of the software and accompanying documentation covered by
5+
this license (the "Software") to use, reproduce, display, distribute,
6+
execute, and transmit the Software, and to prepare derivative works of the
7+
Software, and to permit third-parties to whom the Software is furnished to
8+
do so, all subject to the following:
9+
10+
The copyright notices in the Software and this entire statement, including
11+
the above license grant, this restriction and the following disclaimer,
12+
must be included in all copies of the Software, in whole or in part, and
13+
all derivative works of the Software, unless such copies or derivative
14+
works are solely in the form of machine-executable object code generated by
15+
a source language processor.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
20+
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
21+
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
22+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23+
DEALINGS IN THE SOFTWARE.

renderdoc/3rdparty/catch/catch.cpp

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
/******************************************************************************
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2017 Baldur Karlsson
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
******************************************************************************/
24+
25+
#define CATCH_CONFIG_RUNNER
26+
#define CATCH_CONFIG_NOSTDOUT
27+
#include "catch.hpp"
28+
#include "api/replay/renderdoc_replay.h"
29+
#include "serialise/serialiser.h"
30+
#include "serialise/string_utils.h"
31+
32+
struct AppVeyorListener : Catch::TestEventListenerBase
33+
{
34+
using TestEventListenerBase::TestEventListenerBase; // inherit constructor
35+
36+
bool enabled = false;
37+
std::string hostname;
38+
uint16_t port = 0;
39+
40+
virtual void testRunStarting(Catch::TestRunInfo const &testRunInfo)
41+
{
42+
const char *url = Process::GetEnvVariable("APPVEYOR_API_URL");
43+
44+
if(url)
45+
{
46+
if(strncmp(url, "http://", 7))
47+
return;
48+
49+
url += 7;
50+
51+
const char *sep = strchr(url, ':');
52+
53+
if(!sep)
54+
return;
55+
56+
hostname = std::string(url, sep);
57+
58+
url = sep + 1;
59+
60+
port = 0;
61+
while(*url >= '0' && *url <= '9')
62+
{
63+
port *= 10;
64+
port += int((*url) - '0');
65+
url++;
66+
}
67+
68+
Network::Socket *sock = Network::CreateClientSocket(hostname.c_str(), port, 10);
69+
70+
if(sock)
71+
enabled = true;
72+
73+
SAFE_DELETE(sock);
74+
}
75+
}
76+
77+
std::string curTest;
78+
std::vector<std::string> sectionStack;
79+
80+
virtual void testCaseStarting(Catch::TestCaseInfo const &testInfo) { curTest = testInfo.name; }
81+
virtual void sectionStarting(Catch::SectionInfo const &sectionInfo)
82+
{
83+
if(curTest == sectionInfo.name)
84+
return;
85+
86+
sectionStack.push_back(sectionInfo.name);
87+
88+
if(enabled)
89+
{
90+
Network::Socket *sock = Network::CreateClientSocket(hostname.c_str(), port, 10);
91+
92+
if(sock)
93+
{
94+
std::string req = MakeHTTPRequest();
95+
sock->SendDataBlocking(req.c_str(), (uint32_t)req.size());
96+
}
97+
98+
SAFE_DELETE(sock);
99+
}
100+
}
101+
102+
std::string errorList;
103+
104+
virtual bool assertionEnded(Catch::AssertionStats const &assertionStats)
105+
{
106+
using namespace Catch;
107+
108+
if(!assertionStats.assertionResult.isOk())
109+
{
110+
std::ostringstream msg;
111+
msg << assertionStats.assertionResult.getSourceInfo() << ": ";
112+
113+
switch(assertionStats.assertionResult.getResultType())
114+
{
115+
case ResultWas::ExpressionFailed: msg << "Failed"; break;
116+
case ResultWas::ThrewException: msg << "Threw exception"; break;
117+
case ResultWas::FatalErrorCondition: msg << "Fatal error'd"; break;
118+
case ResultWas::DidntThrowException: msg << "Didn't throw expected exception"; break;
119+
case ResultWas::ExplicitFailure: msg << "Explicitly failed"; break;
120+
121+
case ResultWas::Ok:
122+
case ResultWas::Info:
123+
case ResultWas::Warning:
124+
case ResultWas::Unknown:
125+
case ResultWas::FailureBit:
126+
case ResultWas::Exception: break;
127+
}
128+
129+
if(assertionStats.infoMessages.size() >= 1)
130+
msg << " with message(s):";
131+
for(auto it = assertionStats.infoMessages.begin(); it != assertionStats.infoMessages.end(); ++it)
132+
msg << "\n" << it->message;
133+
134+
if(assertionStats.assertionResult.hasExpression())
135+
{
136+
msg << "\n " << assertionStats.assertionResult.getExpressionInMacro()
137+
<< "\nwith expansion:\n " << assertionStats.assertionResult.getExpandedExpression()
138+
<< "\n";
139+
}
140+
141+
errorList += msg.str();
142+
}
143+
144+
return true;
145+
}
146+
147+
virtual void sectionEnded(Catch::SectionStats const &sectionStats)
148+
{
149+
if(curTest == sectionStats.sectionInfo.name)
150+
return;
151+
152+
if(enabled)
153+
{
154+
Network::Socket *sock = Network::CreateClientSocket(hostname.c_str(), port, 10);
155+
156+
if(sock)
157+
{
158+
std::string req = MakeHTTPRequest(sectionStats.durationInSeconds * 1000.0,
159+
sectionStats.assertions.allOk());
160+
sock->SendDataBlocking(req.c_str(), (uint32_t)req.size());
161+
}
162+
163+
errorList.clear();
164+
165+
SAFE_DELETE(sock);
166+
}
167+
168+
sectionStack.pop_back();
169+
}
170+
171+
private:
172+
std::string MakeHTTPRequest(double msDuration = -1.0, bool passed = false)
173+
{
174+
std::string json;
175+
176+
bool update = msDuration >= 0.0;
177+
178+
const char *outcome = "Running";
179+
180+
if(update)
181+
outcome = passed ? "Passed" : "Failed";
182+
183+
std::string testName;
184+
for(const std::string &section : sectionStack)
185+
{
186+
if(!testName.empty())
187+
testName += " > ";
188+
testName += section;
189+
}
190+
191+
json = StringFormat::Fmt(R"(
192+
{
193+
"testName": "%s",
194+
"testFramework": "Catch.hpp",
195+
"fileName": "%s",
196+
"outcome": "%s",
197+
"durationMilliseconds": "%.0f",
198+
"ErrorMessage": "%s",
199+
"ErrorStackTrace": "",
200+
"StdOut": "",
201+
"StdErr": ""
202+
})",
203+
testName.c_str(), curTest.c_str(), outcome,
204+
RDCMAX(msDuration * 1000.0, 0.0), escape(trim(errorList)).c_str());
205+
206+
std::string http;
207+
http += StringFormat::Fmt("%s /api/tests HTTP/1.1\r\n", update ? "PUT" : "POST");
208+
http += StringFormat::Fmt("Host: %s\r\n", hostname.c_str());
209+
http += "Connection: close\r\n";
210+
http += "Content-Type: application/json\r\n";
211+
http += StringFormat::Fmt("Content-Length: %zu\r\n", json.size());
212+
http += "User-Agent: Catch.hpp appveyor updater\r\n";
213+
http += "\r\n";
214+
return http + json;
215+
}
216+
217+
std::string escape(const std::string &input)
218+
{
219+
std::string ret = input;
220+
size_t i = ret.find_first_of("\"\n\\", 0);
221+
while(i != std::string::npos)
222+
{
223+
if(ret[i] == '"')
224+
ret.replace(i, 1, "\\\"");
225+
else if(ret[i] == '\\')
226+
ret.replace(i, 1, "\\\\");
227+
else if(ret[i] == '\n')
228+
ret.replace(i, 1, "\\n");
229+
230+
i = ret.find_first_of("\"\n\\", i + 2);
231+
}
232+
233+
return ret;
234+
}
235+
};
236+
CATCH_REGISTER_LISTENER(AppVeyorListener)
237+
238+
class LogOutputter : public std::stringbuf
239+
{
240+
public:
241+
LogOutputter() {}
242+
virtual int sync() override
243+
{
244+
std::string msg = this->str();
245+
OSUtility::WriteOutput(OSUtility::Output_DebugMon, msg.c_str());
246+
OSUtility::WriteOutput(OSUtility::Output_StdOut, msg.c_str());
247+
this->str("");
248+
return 0;
249+
}
250+
251+
// force a sync on every output
252+
virtual std::streamsize xsputn(const char *s, std::streamsize n) override
253+
{
254+
std::streamsize ret = std::stringbuf::xsputn(s, n);
255+
sync();
256+
return ret;
257+
}
258+
};
259+
260+
std::ostream *stream = NULL;
261+
262+
namespace Catch
263+
{
264+
std::ostream &cout()
265+
{
266+
return *stream;
267+
}
268+
std::ostream &cerr()
269+
{
270+
return *stream;
271+
}
272+
std::ostream &clog()
273+
{
274+
return *stream;
275+
}
276+
}
277+
278+
extern "C" RENDERDOC_API int RENDERDOC_CC
279+
RENDERDOC_RunUnitTests(const rdctype::str &command, const rdctype::array<rdctype::str> &args)
280+
{
281+
LogOutputter logbuf;
282+
std::ostream logstream(&logbuf);
283+
stream = &logstream;
284+
285+
Catch::Session session;
286+
287+
session.configData().name = "RenderDoc";
288+
session.configData().shouldDebugBreak = OSUtility::DebuggerPresent();
289+
290+
const char **argv = new const char *[args.count + 1];
291+
argv[0] = command.c_str();
292+
for(int i = 0; i < args.count; i++)
293+
argv[i + 1] = args.elems[i].c_str();
294+
295+
int ret = session.applyCommandLine(args.count + 1, argv);
296+
297+
delete[] argv;
298+
299+
// command line error
300+
if(ret != 0)
301+
return ret;
302+
303+
int numFailed = session.run();
304+
305+
// Note that on unices only the lower 8 bits are usually used, clamping
306+
// the return value to 255 prevents false negative when some multiple
307+
// of 256 tests has failed
308+
return (numFailed < 0xff ? numFailed : 0xff);
309+
}

0 commit comments

Comments
 (0)