C ++ - Возможно ли реализовать тестирование утечки памяти в модульном тесте? - PullRequest
16 голосов
/ 05 июня 2010

Я пытаюсь реализовать модульное тестирование для своего кода, и мне трудно это делать.

В идеале я хотел бы протестировать некоторые классы не только на предмет хорошей функциональности, но и на предмет правильного распределения / освобождения памяти. Интересно, можно ли сделать эту проверку с помощью фреймворка для юнит-тестирования? Я использую Visual Assert кстати. Я хотел бы увидеть пример кода, если это возможно!

Ответы [ 4 ]

14 голосов
/ 05 июня 2010

Вы можете использовать функциональность отладки прямо в dev studio для проверки утечек - до тех пор, пока ваши модульные тесты будут выполняться с использованием среды отладки c-runtime.

Простой пример будет выглядеть примерно так:

#include <crtdbg.h>
struct CrtCheckMemory
{
  _CrtMemState state1;
  _CrtMemState state2;
  _CrtMemState state3;
  CrtCheckMemory()
  {
    _CrtMemCheckpoint(&state1);
  }
  ~CrtCheckMemory()
  {
    _CrtMemCheckpoint(&state2);
    // using google test you can just do this.
    EXPECT_EQ(0,_CrtMemDifference( &state3, &state1, &state2));
    // else just do this to dump the leaked blocks to stdout.
    if( _CrtMemDifference( &state3, &state1, &state2) )
      _CrtMemDumpStatistics( &state3 );
  }
};

И чтобы использовать его в модульном тесте:

UNIT_TEST(blah)
{
  CrtCheckMemory check;

  // TODO: add the unit test here

}

Некоторые инфраструктуры модульных тестов имеют свои собственные распределения - например, Google выделяет блоки при неудачном модульном тесте, поэтому любой тестовый блок, в котором произошел сбой по любой другой причине, всегда также имеет ложноположительную «утечку».

5 голосов
/ 05 июня 2010

Вы можете использовать библиотеку выделения tcmalloc от Google, которая предоставляет heapchecker .

(Обратите внимание, что хэпчекинг может значительно увеличить производительность вашей программы, поэтому вы , вероятно, хотите включить его только при отладочных сборках или модульных тестах.)

И вы запросили пример кода, так что здесь это .

1 голос
/ 24 июня 2017

1) После некоторого моего исследования, основанного на очень хорошем решении (для Windows) Криса Бекке, я сделал очень похожее решение для ОС Linux.

2) Мои цели обнаружения утечки памяти:

Совершенно ясно - обнаруживать утечки, а также:

2.1) Идеально точно - указать, сколько именно байтов было выделено, ДА, не освобождено.

2.2) Лучшее усилие- если не совсем, укажите «ложно-положительным» образом (сообщите нам об утечке, даже если она не обязательно одна, и в то же время НЕ пропускайте обнаружение утечки).Лучше быть более суровым с самим собой.

2.3) Поскольку я пишу свои модульные тесты в рамках GTest - проверяйте каждый модульный тест GTest как «атомарный объект».

2.4) Примите во внимание также распределения в стиле «С» (освобождение) с использованием malloc / free.

2.5) В идеале - принять во внимание «выделенные места» в C ++.

2.6) Легко использовать и интегрировать в существующий код (классы на основе GTest для модульного тестирования).

2.7) Возможность «настраивать» основные параметры проверки (включать / отключать проверку памяти и т. Д.) Для каждого теста и / или всего класса теста.

3) Архитектура решения:

Мое решение использует унаследованные возможности использования инфраструктуры GTest, поэтому оно определяет «базовый» класс для каждого класса модульного теста, который мы добавим в будущем.В основном, основные функции базового класса можно разделить на следующие:

3.1) Запустите «первый» тест стиля GTest, чтобы понять количество «дополнительной памяти», выделенной в куче в случаепровал теста. Как отметил Крис Бек в последнем предложении своего ответа выше.

3.2) Легко интегрировать - просто унаследуйте от этого базового класса и напишите функции модульных тестов в стиле TEST_F.

3.3.1) Для каждого теста мы можем решить, выполнять или нет проверку утечки памяти. Это делается с помощью метода SetIgnoreMemoryLeakCheckForThisTest ().Примечание: нет необходимости «сбрасывать» его снова - это произойдет автоматически для следующего теста из-за того, как работают модульные тесты GTest (они вызывают Ctor ранее для каждого вызова функции).

3.3.2) Кроме того, если по какой-то причине вы заранее знаете, что ваш тест «пропустит» некоторые освобождения памяти, и знаете количество - вы можете воспользоваться двумя функциями, чтобы воспользоваться этимФактически в соображения после выполнения проверки памяти (которая, кстати, выполняется «просто», вычитая объем используемой памяти в начале теста из объема памяти, используемой в конце теста).

Ниже приведен базовый класс заголовка:

// memoryLeakDetector.h:
#include "gtest/gtest.h"
extern int g_numOfExtraBytesAllocatedByGtestUponTestFailure;

// The fixture for testing class Foo.
class MemoryLeakDetectorBase : public ::testing::Test 
{
// methods:
// -------
public:
    void SetIgnoreMemoryLeakCheckForThisTest() { m_ignoreMemoryLeakCheckForThisTest= true; } 
    void SetIsFirstCheckRun() { m_isFirstTestRun = true; }

protected:

    // You can do set-up work for each test here.
    MemoryLeakDetectorBase();

    // You can do clean-up work that doesn't throw exceptions here.
    virtual ~MemoryLeakDetectorBase();

    // If the constructor and destructor are not enough for setting up
    // and cleaning up each test, you can define the following methods:

    // Code here will be called immediately after the constructor (right
    // before each test).
    virtual void SetUp();

    // Code here will be called immediately after each test (right
    // before the destructor).
    virtual void TearDown();

private:
    void getSmartDiff(int naiveDiff);
    // Add the extra memory check logic according to our 
    // settings for each test (this method is invoked right
    // after the Dtor).
    virtual void PerformMemoryCheckLogic();

// members:
// -------
private:
    bool m_ignoreMemoryLeakCheckForThisTest;
    bool m_isFirstTestRun;
    bool m_getSmartDiff;
    size_t m_numOfBytesNotToConsiderAsMemoryLeakForThisTest;
    int m_firstCheck;
    int m_secondCheck;
};

И вот источник этого базового класса:

// memoryLeakDetectorBase.cpp
#include <iostream>
#include <malloc.h>

#include "memoryLeakDetectorBase.h"

int g_numOfExtraBytesAllocatedByGtestUponTestFailure = 0;

static int display_mallinfo_and_return_uordblks()
{
    struct mallinfo mi;

    mi = mallinfo();
    std::cout << "========================================" << std::endl;
    std::cout << "========================================" << std::endl;
    std::cout << "Total non-mmapped bytes (arena):" << mi.arena << std::endl;
    std::cout << "# of free chunks (ordblks):" << mi.ordblks << std::endl;
    std::cout << "# of free fastbin blocks (smblks):" << mi.smblks << std::endl;
    std::cout << "# of mapped regions (hblks):" << mi.hblks << std::endl;
    std::cout << "Bytes in mapped regions (hblkhd):"<< mi.hblkhd << std::endl;
    std::cout << "Max. total allocated space (usmblks):"<< mi.usmblks << std::endl;
    std::cout << "Free bytes held in fastbins (fsmblks):"<< mi.fsmblks << std::endl;
    std::cout << "Total allocated space (uordblks):"<< mi.uordblks << std::endl;
    std::cout << "Total free space (fordblks):"<< mi.fordblks << std::endl;
    std::cout << "Topmost releasable block (keepcost):" << mi.keepcost << std::endl;
    std::cout << "========================================" << std::endl;
    std::cout << "========================================" << std::endl;
    std::cout << std::endl;
    std::cout << std::endl;

    return mi.uordblks;
}

MemoryLeakDetectorBase::MemoryLeakDetectorBase() 
    : m_ignoreMemoryLeakCheckForThisTest(false)
    , m_isFirstTestRun(false)
    , m_getSmartDiff(false)
    , m_numOfBytesNotToConsiderAsMemoryLeakForThisTest(0)
{
    std::cout << "MemoryLeakDetectorBase::MemoryLeakDetectorBase" << std::endl;
    m_firstCheck = display_mallinfo_and_return_uordblks();
}

MemoryLeakDetectorBase::~MemoryLeakDetectorBase() 
{
    std::cout << "MemoryLeakDetectorBase::~MemoryLeakDetectorBase" << std::endl;
    m_secondCheck = display_mallinfo_and_return_uordblks();
    PerformMemoryCheckLogic();
}

void MemoryLeakDetectorBase::PerformMemoryCheckLogic()
{
    if (m_isFirstTestRun) {
        std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - after the first test" << std::endl;
        int diff = m_secondCheck - m_firstCheck;
        if ( diff > 0) {
            std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - setting g_numOfExtraBytesAllocatedByGtestUponTestFailure to:" << diff << std::endl;
            g_numOfExtraBytesAllocatedByGtestUponTestFailure = diff;
        }
        return;
    }

    if (m_ignoreMemoryLeakCheckForThisTest) {
        return;
    }
    std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic" << std::endl;

    int naiveDiff = m_secondCheck - m_firstCheck;

    // in case you wish for "more accurate" difference calculation call this method
    if (m_getSmartDiff) {
        getSmartDiff(naiveDiff);
    }

    EXPECT_EQ(m_firstCheck,m_secondCheck);
    std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - the difference is:" << naiveDiff << std::endl;
}

void MemoryLeakDetectorBase::getSmartDiff(int naiveDiff)
{
    // according to some invastigations and assumemptions, it seems like once there is at least one 
    // allocation which is not handled - GTest allocates 32 bytes on the heap, so in case the difference
    // prior for any further substrcutions is less than 32 - we will assume that the test does not need to 
    // go over memory leak check...
    std::cout << "MemoryLeakDetectorBase::getMoreAccurateAmountOfBytesToSubstructFromSecondMemoryCheck - start" << std::endl; 
    if (naiveDiff <= 32) {
        std::cout << "MemoryLeakDetectorBase::getSmartDiff - the naive diff <= 32 - ignoring..." << std::endl;
        return;
    }

    size_t numOfBytesToReduceFromTheSecondMemoryCheck = m_numOfBytesNotToConsiderAsMemoryLeakForThisTest + g_numOfExtraBytesAllocatedByGtestUponTestFailure;
    m_secondCheck -= numOfBytesToReduceFromTheSecondMemoryCheck;
    std::cout << "MemoryLeakDetectorBase::getSmartDiff - substructing " << numOfBytesToReduceFromTheSecondMemoryCheck << std::endl;
}

void MemoryLeakDetectorBase::SetUp() 
{
    std::cout << "MemoryLeakDetectorBase::SetUp" << std::endl;
}

void MemoryLeakDetectorBase::TearDown() 
{
    std::cout << "MemoryLeakDetectorBase::TearDown" << std::endl;
}

// The actual test of this module:


TEST_F(MemoryLeakDetectorBase, getNumOfExtraBytesGTestAllocatesUponTestFailureTest) 
{
    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - START" << std::endl;

    // Allocate some bytes on the heap and DO NOT delete them so we can find out the amount 
    // of extra bytes GTest framework allocates upon a failure of a test.
    // This way, upon our legit test failure, we will be able to determine of many bytes were NOT
    // deleted EXACTLY by our test.

    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - size of char:" << sizeof(char) << std::endl;
    char* pChar = new char('g');
    SetIsFirstCheckRun();
    std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - END" << std::endl;
}

Наконец, пример "на основе GTest"класс модульного теста, который использует этот базовый класс и иллюстрирует способы использования и несколько различных POC (подтверждение концепции) для всех видов различных распределений и проверки, если мы можем (или нет) обнаружить пропущенные отмены распределения.

// memoryLeakDetectorPocTest.cpp
#include "memoryLeakDetectorPocTest.h"
#include <cstdlib>  // for malloc

class MyObject 
{

public:
    MyObject(int a, int b) : m_a(a), m_b(b) { std::cout << "MyObject::MyObject" << std::endl; }
    ~MyObject() { std::cout << "MyObject::~MyObject" << std::endl; }  
private:
    int m_a;
    int m_b;
};

MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest() 
{
    std::cout << "MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest" << std::endl;
}

MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest() 
{
    std::cout << "MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest" << std::endl;
}

void MemoryLeakDetectorPocTest::SetUp() 
{
    std::cout << "MemoryLeakDetectorPocTest::SetUp" << std::endl;
}

void MemoryLeakDetectorPocTest::TearDown() 
{
    std::cout << "MemoryLeakDetectorPocTest::TearDown" << std::endl;
}

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeType) 
{

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - START" << std::endl;

    // allocate some bytes on the heap and intentially DONT release them...
    const size_t numOfCharsOnHeap = 23;
    std::cout << "size of char is:" << sizeof(char)  << " bytes" << std::endl;
    std::cout << "allocating " << sizeof(char) * numOfCharsOnHeap << " bytes on the heap using new []" << std::endl;
    char* arr = new char[numOfCharsOnHeap];

    // DO NOT delete it on purpose...
    //delete [] arr;
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - END" << std::endl;
}

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedType) 
{
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - START" << std::endl;

    std::cout << "size of MyObject is:" << sizeof(MyObject)  << " bytes" << std::endl;
    std::cout << "allocating MyObject on the heap using new" << std::endl;
    MyObject* myObj1 = new MyObject(12, 17);

    delete myObj1;
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - END" << std::endl;
}

TEST_F(MemoryLeakDetectorPocTest, verifyMallocAllocationForNativeType) 
{
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - START" << std::endl;
    size_t numOfDoublesOnTheHeap = 3;
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - sizeof double is " << sizeof(double) << std::endl; 
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - allocaitng " << sizeof(double) * numOfDoublesOnTheHeap << " bytes on the heap" << std::endl;
    double* arr = static_cast<double*>(malloc(sizeof(double) * numOfDoublesOnTheHeap));

    // NOT free-ing them on purpose !!
    // free(arr);
    std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - END" << std::endl;
}

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeSTLVectorType) 
{
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - START" << std::endl;
    std::vector<int> vecInt;
    vecInt.push_back(12);
    vecInt.push_back(15);
    vecInt.push_back(17);

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - END" << std::endl;
}

TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedSTLVectorType) 
{
    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - START" << std::endl;
    std::vector<MyObject*> vecMyObj;
    vecMyObj.push_back(new MyObject(7,8));
    vecMyObj.push_back(new MyObject(9,10));

    size_t vecSize = vecMyObj.size();
    for (int i = 0; i < vecSize; ++i) {
        delete vecMyObj[i];
    }

    std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - END" << std::endl;
}

TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationAndDeAllocationForUserDefinedType) 
{
     std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - START" << std::endl;
    void* p1 = malloc(sizeof(MyObject));
    MyObject *p2 = new (p1) MyObject(12,13);

    p2->~MyObject();
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - END" << std::endl;
}

TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationForUserDefinedType) 
{
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - START" << std::endl;
    void* p1 = malloc(sizeof(MyObject));
    MyObject *p2 = new (p1) MyObject(12,13);

    // Dont delete the object on purpose !!
    //p2->~MyObject();
    std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - END" << std::endl;
}

Заголовочный файл этого класса:

// memoryLeakDetectorPocTest.h
#include "gtest/gtest.h"
#include "memoryLeakDetectorBase.h"

// The fixture for testing class Foo.
class MemoryLeakDetectorPocTest : public MemoryLeakDetectorBase
{
protected:

    // You can do set-up work for each test here.
    MemoryLeakDetectorPocTest();

    // You can do clean-up work that doesn't throw exceptions here.
    virtual ~MemoryLeakDetectorPocTest();

    // Code here will be called immediately after the constructor (right
    // before each test).
    virtual void SetUp();

    // Code here will be called immediately after each test (right
    // before the destructor).
    virtual void TearDown();
};

Надеюсь, что это полезно, и, пожалуйста, дайте мне знать, если есть что-то непонятное.

Приветствия,

Гай.

1 голос
/ 05 июня 2010

Возможно, вы сможете обнаружить утечку памяти в тестах, предоставив собственную реализацию новых, delete, malloc и free функций, добавив информацию отслеживания памяти при распределении.

...