gtestify debugallocation_test

As part of that we eliminate use of shell script and rely on gtest's
death testing facility instead (which this test was originally using).
This commit is contained in:
Aliaksey Kandratsenka 2024-03-14 12:39:19 -04:00
parent 14598dd4ac
commit 8cb41da4f9
4 changed files with 32 additions and 197 deletions

View File

@ -723,10 +723,8 @@ if(GPERFTOOLS_BUILD_DEBUGALLOC)
if(WITH_STACK_TRACE)
add_executable(debugallocation_test src/tests/debugallocation_test.cc)
target_link_libraries(debugallocation_test tcmalloc_minimal_debug)
add_test(NAME debugallocation_test
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/src/tests/debugallocation_test.sh)
target_link_libraries(debugallocation_test tcmalloc_debug gtest)
add_test(debugallocation_test debugallocation_test)
endif()
endif()
endif()
@ -803,6 +801,7 @@ if(GPERFTOOLS_BUILD_HEAP_CHECKER OR GPERFTOOLS_BUILD_HEAP_PROFILER)
# sampler_test and sampling_test both require sampling to be turned
# on, which it's not by default. Use the "standard" value of 2^19.
list(APPEND TESTS_ENVIRONMENT TCMALLOC_SAMPLE_PARAMETER=524288)
list(APPEND TESTS_ENVIRONMENT PPROF_PATH=${CMAKE_CURRENT_SOURCE_DIR}/src/pprof)
add_executable(sampler_test src/tests/sampler_test.cc src/sampler.cc)
target_link_libraries(sampler_test gtest)

View File

@ -555,19 +555,12 @@ realloc_debug_unittest_LDADD = libtcmalloc_minimal_debug.la libgtest.la
# debugallocation_test checks that we print a proper stacktrace when
# debug-allocs fail, so we can't run it if we don't have stacktrace info.
if WITH_STACK_TRACE
TESTS += debugallocation_test.sh$(EXEEXT)
debugallocation_test_sh_SOURCES = src/tests/debugallocation_test.sh
noinst_SCRIPTS += $(debugallocation_test_sh_SOURCES)
debugallocation_test.sh$(EXEEXT): $(top_srcdir)/$(debugallocation_test_sh_SOURCES) \
debugallocation_test
rm -f $@
cp -p $(top_srcdir)/$(debugallocation_test_sh_SOURCES) $@
# This is the sub-program used by debugallocation_test.sh
noinst_PROGRAMS += debugallocation_test
TESTS += debugallocation_test
debugallocation_test_SOURCES = src/tests/debugallocation_test.cc
debugallocation_test_LDFLAGS = $(TCMALLOC_FLAGS) $(AM_LDFLAGS)
debugallocation_test_LDADD = libtcmalloc_debug.la
debugallocation_test_CPPFLAGS = $(gtest_CPPFLAGS)
debugallocation_test_LDADD = libtcmalloc_debug.la libgtest.la
endif WITH_STACK_TRACE
endif WITH_DEBUGALLOC

View File

@ -36,54 +36,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // for memcmp
#include <vector>
#include "gperftools/malloc_extension.h"
#include "gperftools/tcmalloc.h"
#include "base/logging.h"
#include "testing_portal.h"
#include "tests/testutil.h"
#include "tests/legacy_assertions.h"
#include "testing_portal.h"
#include "gtest/gtest.h"
using tcmalloc::TestingPortal;
using std::vector;
vector<void (*)()> g_testlist; // the tests to run
#define TEST(a, b) \
struct Test_##a##_##b { \
Test_##a##_##b() { g_testlist.push_back(&Run); } \
static void Run(); \
}; \
static Test_##a##_##b g_test_##a##_##b; \
void Test_##a##_##b::Run()
static int RUN_ALL_TESTS() {
vector<void (*)()>::const_iterator it;
for (it = g_testlist.begin(); it != g_testlist.end(); ++it) {
(*it)(); // The test will error-exit if there's a problem.
}
fprintf(stderr, "\nPassed %d tests\n\nPASS\n",
static_cast<int>(g_testlist.size()));
return 0;
}
// The death tests are meant to be run from a shell-script driver, which
// passes in an integer saying which death test to run. We store that
// test-to-run here, and in the macro use a counter to see when we get
// to that test, so we can run it.
static int test_to_run = 0; // set in main() based on argv
static int test_counter = 0; // incremented every time the macro is called
#define IF_DEBUG_EXPECT_DEATH(statement, regex) do { \
if (test_counter++ == test_to_run) { \
fprintf(stderr, "Expected regex:%s\n", regex); \
statement; \
} \
} while (false)
// Test match as well as mismatch rules. But do not test on OS X; on
// OS X the OS converts new/new[] to malloc before it gets to us, so
// we are unable to catch these mismatch errors.
@ -98,8 +62,8 @@ TEST(DebugAllocationTest, DeallocMismatch) {
// Allocate with malloc.
{
int* x = static_cast<int*>(noopt(malloc(sizeof(*x))));
IF_DEBUG_EXPECT_DEATH(delete x, "mismatch.*being dealloc.*delete");
IF_DEBUG_EXPECT_DEATH(delete [] x, "mismatch.*being dealloc.*delete *[[]");
EXPECT_DEATH(delete x, "mismatch.*being dealloc.*delete");
EXPECT_DEATH(delete [] x, "mismatch.*being dealloc.*delete *[[]");
// Should work fine.
free(x);
}
@ -108,8 +72,8 @@ TEST(DebugAllocationTest, DeallocMismatch) {
{
int* x = noopt(new int);
int* y = noopt(new int);
IF_DEBUG_EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
IF_DEBUG_EXPECT_DEATH(delete [] x, "mismatch.*being dealloc.*delete *[[]");
EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
EXPECT_DEATH(delete [] x, "mismatch.*being dealloc.*delete *[[]");
delete x;
::operator delete(y, std::nothrow);
}
@ -118,8 +82,8 @@ TEST(DebugAllocationTest, DeallocMismatch) {
{
int* x = noopt(new int[1]);
int* y = noopt(new int[1]);
IF_DEBUG_EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
IF_DEBUG_EXPECT_DEATH(delete x, "mismatch.*being dealloc.*delete");
EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
EXPECT_DEATH(delete x, "mismatch.*being dealloc.*delete");
delete [] x;
::operator delete[](y, std::nothrow);
}
@ -128,8 +92,8 @@ TEST(DebugAllocationTest, DeallocMismatch) {
{
int* x = noopt(new (std::nothrow) int);
int* y = noopt(new (std::nothrow) int);
IF_DEBUG_EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
IF_DEBUG_EXPECT_DEATH(delete [] x, "mismatch.*being dealloc.*delete *[[]");
EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
EXPECT_DEATH(delete [] x, "mismatch.*being dealloc.*delete *[[]");
delete x;
::operator delete(y, std::nothrow);
}
@ -138,8 +102,8 @@ TEST(DebugAllocationTest, DeallocMismatch) {
{
int* x = noopt(new (std::nothrow) int[1]);
int* y = noopt(new (std::nothrow) int[1]);
IF_DEBUG_EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
IF_DEBUG_EXPECT_DEATH(delete x, "mismatch.*being dealloc.*delete");
EXPECT_DEATH(free(x), "mismatch.*being dealloc.*free");
EXPECT_DEATH(delete x, "mismatch.*being dealloc.*delete");
delete [] x;
::operator delete[](y, std::nothrow);
}
@ -149,25 +113,21 @@ TEST(DebugAllocationTest, DeallocMismatch) {
TEST(DebugAllocationTest, DoubleFree) {
int* pint = noopt(new int);
delete pint;
IF_DEBUG_EXPECT_DEATH(delete pint, "has been already deallocated");
EXPECT_DEATH(delete pint, "has been already deallocated");
}
TEST(DebugAllocationTest, StompBefore) {
int* pint = noopt(new int);
(void)pint;
#ifndef NDEBUG // don't stomp memory if we're not in a position to detect it
pint[-1] = 5;
IF_DEBUG_EXPECT_DEATH(delete pint, "a word before object");
#endif
EXPECT_DEATH(delete pint, "a word before object");
}
TEST(DebugAllocationTest, StompAfter) {
int* pint = noopt(new int);
(void)pint;
#ifndef NDEBUG // don't stomp memory if we're not in a position to detect it
pint[1] = 5;
IF_DEBUG_EXPECT_DEATH(delete pint, "a word after object");
#endif
EXPECT_DEATH(delete pint, "a word after object");
}
TEST(DebugAllocationTest, FreeQueueTest) {
@ -188,7 +148,7 @@ TEST(DebugAllocationTest, FreeQueueTest) {
// it commented out.
// EXPECT_EQ(x, old_x);
#endif
old_x = NULL; // avoid breaking opt build with an unused variable warning.
old_x = nullptr; // avoid breaking opt build with an unused variable warning.
delete x;
}
@ -208,7 +168,7 @@ TEST(DebugAllocationTest, DanglingPointerWriteTest) {
// When we delete s, we push the storage that was previously allocated to x
// off the end of the free queue. At that point, the write to that memory
// will be detected.
IF_DEBUG_EXPECT_DEATH(delete [] s, "Memory was written to after being freed.");
EXPECT_DEATH(delete [] s, "Memory was written to after being freed.");
// restore the poisoned value of x so that we can delete s without causing a
// crash.
@ -224,7 +184,7 @@ TEST(DebugAllocationTest, DanglingWriteAtExitTest) {
*x = 1;
// verify that dangling writes are caught at program termination if the
// corrupted block never got pushed off of the end of the free queue.
IF_DEBUG_EXPECT_DEATH(exit(0), "Memory was written to after being freed.");
EXPECT_DEATH(exit(0), "Memory was written to after being freed.");
*x = old_x_value; // restore x so that the test can exit successfully.
}
@ -235,7 +195,7 @@ TEST(DebugAllocationTest, StackTraceWithDanglingWriteAtExitTest) {
*x = 1;
// verify that we also get a stack trace when we have a dangling write.
// The " @ " is part of the stack trace output.
IF_DEBUG_EXPECT_DEATH(exit(0), " @ .*main");
EXPECT_DEATH(exit(0), " @ .*main");
*x = old_x_value; // restore x so that the test can exit successfully.
}
@ -306,14 +266,13 @@ TEST(DebugAllocationTest, HugeAlloc) {
// integral-constant-expression which can be *statically* rejected by the
// compiler as too large for the allocation.
size_t kTooBig = ~static_cast<size_t>(0);
void* a = NULL;
void* a = nullptr;
(void)kTooBig;
(void)a;
#ifndef NDEBUG
a = noopt(malloc(noopt(kTooBig)));
EXPECT_EQ(NULL, a);
EXPECT_EQ(nullptr, a);
// kAlsoTooBig is small enough not to get caught by debugallocation's check,
// but will still fall through to tcmalloc's check. This must also be
@ -321,8 +280,7 @@ TEST(DebugAllocationTest, HugeAlloc) {
size_t kAlsoTooBig = kTooBig - 1024;
a = noopt(malloc(noopt(kAlsoTooBig)));
EXPECT_EQ(NULL, a);
#endif
EXPECT_EQ(nullptr, a);
}
// based on test program contributed by mikesart@gmail.com aka
@ -331,29 +289,12 @@ TEST(DebugAllocationTest, ReallocAfterMemalign) {
char stuff[50];
memset(stuff, 0x11, sizeof(stuff));
void *p = tc_memalign(16, sizeof(stuff));
EXPECT_NE(p, NULL);
EXPECT_NE(p, nullptr);
memcpy(stuff, p, sizeof(stuff));
p = noopt(realloc(p, sizeof(stuff) + 10));
EXPECT_NE(p, NULL);
EXPECT_NE(p, nullptr);
int rv = memcmp(stuff, p, sizeof(stuff));
EXPECT_EQ(rv, 0);
}
int main(int argc, char** argv) {
// If you run without args, we run the non-death parts of the test.
// Otherwise, argv[1] should be a number saying which death-test
// to run. We will output a regexp we expect the death-message
// to include, and then run the given death test (which hopefully
// will produce that error message). If argv[1] > the number of
// death tests, we will run only the non-death parts. One way to
// tell when you are done with all tests is when no 'expected
// regexp' message is printed for a given argv[1].
if (argc < 2) {
test_to_run = -1; // will never match
} else {
test_to_run = atoi(argv[1]);
}
return RUN_ALL_TESTS();
}

View File

@ -1,98 +0,0 @@
#!/bin/sh
# Copyright (c) 2009, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ---
# Author: Craig Silverstein
BINDIR="${BINDIR:-.}"
# We expect PPROF_PATH to be set in the environment.
# If not, we set it to some reasonable value
export PPROF_PATH="${PPROF_PATH:-$BINDIR/src/pprof}"
if [ "x$1" = "x-h" -o "x$1" = "x--help" ]; then
echo "USAGE: $0 [unittest dir]"
echo " By default, unittest_dir=$BINDIR"
exit 1
fi
DEBUGALLOCATION_TEST="${1:-$BINDIR/debugallocation_test}"
num_failures=0
# Run the i-th death test and make sure the test has the expected
# regexp. We can depend on the first line of the output being
# Expected regex:<regex>
# Evaluates to "done" if we are not actually a death-test (so $1 is
# too big a number, and we can stop). Evaluates to "" otherwise.
# Increments num_failures if the death test does not succeed.
OneDeathTest() {
"$DEBUGALLOCATION_TEST" "$1" 2>&1 | {
regex_line='dummy'
# Normally the regex_line is the first line of output, but not
# always (if tcmalloc itself does any logging to stderr).
while test -n "$regex_line"; do
read regex_line
regex=`expr "$regex_line" : "Expected regex:\(.*\)"`
test -n "$regex" && break # found the regex line
done
test -z "$regex" && echo "done" || grep "$regex" 2>&1
}
}
death_test_num=0 # which death test to run
while :; do # same as 'while true', but more portable
echo -n "Running death test $death_test_num..."
output="`OneDeathTest $death_test_num`"
case $output in
# Empty string means grep didn't find anything.
"") echo "FAILED"; num_failures=`expr $num_failures + 1`;;
"done"*) echo "done with death tests"; break;;
# Any other string means grep found something, like it ought to.
*) echo "OK";;
esac
death_test_num=`expr $death_test_num + 1`
done
# Test the non-death parts of the test too
echo -n "Running non-death tests..."
if "$DEBUGALLOCATION_TEST"; then
echo "OK"
else
echo "FAILED"
num_failures=`expr $num_failures + 1`
fi
if [ "$num_failures" = 0 ]; then
echo "PASS"
else
echo "Failed with $num_failures failures"
fi
exit $num_failures