the Compartmented Robust Posix C++ Unit Test system |
|
---|
Finding memory leaks can be tricky, and testing behaviour under out of memory situations even trickier still. With crpcut both are easy, since crpcut provides simple access to query and limit the heap.
Note | |
---|---|
For these to work, you must link your test program with
-lcrpcut instead of -lcrpcut_basic . |
The simplest way to assert that your code does not leak heap objects is
to use
ASSERT_SCOPE_HEAP_LEAK_FREE
or
VERIFY_SCOPE_HEAP_LEAK_FREE
.
With it, you check that any heap object allocated inside the code block
is also deallocated when code block ends.
Tip | |
---|---|
Use the command line flag
-b /
--backtrace-heap to display the
full stack backtrace for allocation of each object. |
A simple example. The test program:
#include <crpcut.hpp> #include <string> TEST(heap_in_balance) { std::string before("a"); ASSERT_SCOPE_HEAP_LEAK_FREE { std::string after("b"); } } TEST(heap_imbalance) { std::string before("a"); ASSERT_SCOPE_HEAP_LEAK_FREE { // This is not a memory leak, but it is reported std::string after("b"); // as such because the object allocated here is std::swap(before, after); // not released at the end of the block } } TEST(heap_leak) { char *p[5]; ASSERT_SCOPE_HEAP_LEAK_FREE { int i; for (i = 0; i < 5; ++i) { p[i] = new char[i+1]; } while (--i > 0) { delete[] p[i]; } } } int main(int argc, char *argv[]) { return crpcut::run(argc, argv); }
reports two failed test:
FAILED!: heap_imbalance phase="running" -------------------------------------------------------------- samples/assert_scope_leak_free.cpp:43 ASSERT_SCOPE_LEAK_FREE 1 object 26 bytes at 0x62ba20 allocated with new Allocated at: /var/tmp/build/lib64/libcrpcut.so.1(operator new(unsigned long)+0x1d) [0x7fca98f49984] /usr/lib/gcc/x86_64-pc-linux-gnu/5.3.0/libstdc++.so.6(std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&)+0x59) [0x384b4d8f29] ------------------------------------------------------------------------------- =============================================================================== FAILED!: heap_leak phase="running" -------------------------------------------------------------- samples/assert_scope_leak_free.cpp:53 ASSERT_SCOPE_LEAK_FREE 1 object 1 byte at 0x62b860 allocated with new[] Allocated at: /var/tmp/build/lib64/libcrpcut.so.1(operator new[](unsigned long)+0x1d) [0x7fca98f499e9] ../test/assert_scope_leak_free(heap_leak::test()+0x8f) [0x4088cf] ------------------------------------------------------------------------------- =============================================================================== 3 test cases selected Sum Critical Non-critical PASSED : 1 1 0 FAILED : 2 2 0
When more detailed control of the heap is needed, the function
size_t crpcut::heap::allocated_bytes
()
reports how much memory is allocated on the heap.
The number returned is the sum of bytes allocated, sans any overhead,
i.e. it is the sum of the size arguments to all calls to
malloc
(), calloc
(),
realloc
(), operator new
and
operator new[]
.
Tip | |
---|---|
Use a fixture that compares allocated bytes at destruction time with allocated bytes at construction time. If they aren't equal, the test has leaked, even if the exact objects at beginning and end aren't identical. |
The heap can also be limited with the function
size_t crpcut::heap::set_limit
(size_t bytes
).
The parameter bytes
is the maximum number of
bytes that is allowed to allocate from the heap. The return value is the
previous limit. There is a special value
crpcut::heap::system
, which removes
any artificial limitations.
Here's a function and helper class that casts between types by means of interpreting the stream representation.
#include <sstream> #include <ostream> #include <istream> #include <string> class cast_type { private: cast_type(const std::string str) : s(str) {} cast_type& operator=(const cast_type&); public: cast_type(const cast_type &other) : s(other.s) { } ~cast_type() { } template <typename T> operator T() const { std::istringstream is(s); T rv = T(); is >> rv; return rv; } private: std::string s; template <typename T> friend cast_type stream_cast(const T&); }; template <typename T> inline cast_type stream_cast(const T& t) { std::ostringstream os; os << t; return cast_type(os.str()); }
Below is a small test program to verify its correctness. It includes a memory leak check and a low memory situation:
#include "stream-cast.hpp" #include <crpcut.hpp> class leak_detect { protected: leak_detect() : pre(crpcut::heap::allocated_bytes()) {} ~leak_detect() { size_t post = crpcut::heap::allocated_bytes(); if (post != pre) { FAIL << "Memory leak detected\n" << pre << " Bytes allocated at construction time\n" << post << " Bytes allocated at destruction time"; } } private: size_t pre; }; TEST(simple_string_to_int, leak_detect) { const char s[] = "123"; int n = stream_cast(s); ASSERT_EQ(n, 123); } TEST(constrained_string_to_int, leak_detect) { crpcut::heap::set_limit(crpcut::heap::allocated_bytes() + 10); const char s[] = "1234567"; int n = stream_cast(s); ASSERT_EQ(1234567, n); } int main(int argc, char *argv[]) { return crpcut::run(argc, argv); }
Running the test yields:
FAILED!: constrained_string_to_int phase="running" -------------------------------------------------------------- samples/heap-check.cpp:60 ASSERT_EQ(1234567, n) where n = 0 ------------------------------------------------------------------------------- =============================================================================== 2 test cases selected Sum Critical Non-critical PASSED : 1 1 0 FAILED : 1 1 0
No memory leaks, that's the good news, but that it silently provides the wrong answer under low-memory situations, that is very bad. Can you see why?