Skip to content

Commit e21a264

Browse files
authored
Merge pull request #4249 from roystgnr/dont_leak_test_suite
Avoid memory leak in unit test driver
2 parents c144a6e + 76de8d1 commit e21a264

File tree

1 file changed

+63
-16
lines changed

1 file changed

+63
-16
lines changed

tests/driver.C

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <cppunit/extensions/TestFactoryRegistry.h>
44
#include <cppunit/ui/text/TestRunner.h>
55
#include <cppunit/BriefTestProgressListener.h>
6+
#include <cppunit/TestPath.h>
67
#include <cppunit/TestResult.h>
78
#include <libmesh/restore_warnings.h>
89

@@ -17,14 +18,54 @@
1718
// C++ includes
1819
#include <regex>
1920

21+
// We want to be able to add a selection of tests from our full suite
22+
// to our test runner. But then the test runner will want to destruct
23+
// those tests afterward, so we can't destruct the suite without it
24+
// *also* trying to destruct all its tests (undefined behavior), and
25+
// we can't not destruct the suite without the suite and its
26+
// unselected tests being leaked (which would be fine since just exit
27+
// afterward, except valgrind sees the leaks and screams).
28+
//
29+
// So, instead of adding selected tests, we add shims of selected
30+
// tests that can be deleted without deleting the tests they shim.
31+
class TestShim : public CppUnit::Test
32+
{
33+
public:
34+
TestShim (CppUnit::Test & shimmed_test) : _shimmed_test(shimmed_test) {}
35+
36+
virtual void run(CppUnit::TestResult * result) override { _shimmed_test.run(result); }
37+
38+
virtual int countTestCases () const override { return _shimmed_test.countTestCases(); }
39+
40+
virtual int getChildTestCount () const override { return _shimmed_test.getChildTestCount(); }
41+
42+
virtual std::string getName () const override { return _shimmed_test.getName(); }
43+
44+
virtual bool findTestPath (const std::string & testName, CppUnit::TestPath & testPath) const override { return _shimmed_test.findTestPath(testName, testPath); }
45+
46+
virtual bool findTestPath (const CppUnit::Test * test, CppUnit::TestPath & testPath) const override { return _shimmed_test.findTestPath(test, testPath); }
47+
48+
virtual CppUnit::Test * findTest(const std::string & testName) const override { return _shimmed_test.findTest(testName); }
49+
50+
virtual CppUnit::TestPath resolveTestPath(const std::string & testPath) const override { return _shimmed_test.resolveTestPath(testPath); }
51+
52+
protected:
53+
virtual CppUnit::Test * doGetChildTestAt(int index) const override { return _shimmed_test.getChildTestAt(index); }
54+
55+
private:
56+
CppUnit::Test & _shimmed_test;
57+
};
58+
59+
// "Magic number" for when we're not doing allow or deny regex
60+
static constexpr int added_whole_suite = -12345;
61+
2062
// Add Tests to runner that match user-provided regex.
2163
int add_matching_tests_to_runner(CppUnit::Test * test,
2264
const std::string & allow_r_str,
2365
const std::regex & allow_r,
2466
const std::string & deny_r_str,
2567
const std::regex & deny_r,
26-
CppUnit::TextUi::TestRunner & runner,
27-
CppUnit::TestSuite & rejects)
68+
CppUnit::TextUi::TestRunner & runner)
2869
{
2970
int n_tests_added = 0;
3071

@@ -34,7 +75,7 @@ int add_matching_tests_to_runner(CppUnit::Test * test,
3475
{
3576
libMesh::out << test->getName() << std::endl;
3677
runner.addTest(test);
37-
return -12345;
78+
return added_whole_suite;
3879
}
3980

4081
if (test->getChildTestCount() == 0)
@@ -46,18 +87,17 @@ int add_matching_tests_to_runner(CppUnit::Test * test,
4687
{
4788
libMesh::out << test->getName() << std::endl;
4889
n_tests_added ++;
49-
runner.addTest(test);
90+
91+
// yes, explicit new; this is how CppUnit works
92+
runner.addTest(new TestShim(*test));
5093
}
51-
// Add the test to the rejects it can be cleaned up later
52-
else
53-
rejects.addTest(test);
5494
}
5595

5696
// Call this recursively on each of our children, if any.
5797
for (int i = 0; i < test->getChildTestCount(); i++)
5898
n_tests_added +=
5999
add_matching_tests_to_runner(test->getChildTestAt(i), allow_r_str, allow_r,
60-
deny_r_str, deny_r, runner, rejects);
100+
deny_r_str, deny_r, runner);
61101

62102
return n_tests_added;
63103
}
@@ -116,15 +156,17 @@ int main(int argc, char ** argv)
116156
std::string deny_regex_string = "^$";
117157
deny_regex_string = libMesh::command_line_next("--deny_re", deny_regex_string);
118158

159+
// We might have to delete the test suite ourselves, after the
160+
// runner has deleted whatever subtests it has.
161+
std::unique_ptr<CppUnit::Test> owned_suite;
162+
119163
// Recursively add tests matching the regex to the runner object.
120164
CppUnit::TextUi::TestRunner runner;
121165

122-
// The Cppunit registry object that knows about all the tests.
166+
// The Cppunit registry object that knows about all the tests, and
167+
// the test suite it creates.
123168
CppUnit::TestFactoryRegistry & registry = CppUnit::TestFactoryRegistry::getRegistry();
124-
125-
// A test suite container for holding tests not matching the regex. When main's
126-
// scope ends, this class's destructor will delete the rejected tests
127-
CppUnit::TestSuite rejects("rejects");
169+
CppUnit::Test * suite = registry.makeTest();
128170

129171
#ifdef LIBMESH_HAVE_CXX11_REGEX
130172
// Make regex objects from user's input.
@@ -134,15 +176,20 @@ int main(int argc, char ** argv)
134176
// Add all tests which match the re to the runner object.
135177
libMesh::out << "Will run the following tests:" << std::endl;
136178
const int n_tests_added =
137-
add_matching_tests_to_runner(registry.makeTest(),
179+
add_matching_tests_to_runner(suite,
138180
allow_regex_string, allow_regex,
139181
deny_regex_string, deny_regex,
140-
runner, rejects);
182+
runner);
141183
if (n_tests_added >= 0)
142184
libMesh::out << "--- Running " << n_tests_added << " tests in total." << std::endl;
185+
186+
// If we didn't add the whole suite to the runner, we need to clean
187+
// it up ourselves
188+
if (n_tests_added != added_whole_suite)
189+
owned_suite.reset(suite);
143190
#else
144191
// If no C++11 <regex> just run all the tests.
145-
runner.addTest(registry.makeTest());
192+
runner.addTest(suite);
146193
#endif
147194

148195
std::unique_ptr<CppUnit::TestResult> controller;

0 commit comments

Comments
 (0)