C++ - est-il possible d'implémenter un test de fuite de mémoire dans un test d'unité?

j'essaie d'implémenter des tests unitaires pour mon code et j'ai du mal à le faire.

Idéalement, je voudrais tester certaines classes, non seulement pour une bonne fonctionnalité, mais aussi pour une bonne allocation/libération de mémoire. Je me demande si cette vérification peut être effectuée à l'aide d'un cadre d'essai unitaire. Je suis à l'aide de Visual Assert btw. J'aimerais voir un exemple de code , si possible !

16
demandé sur sevaxx 2010-06-05 19:23:15

4 réponses

vous pouvez utiliser la fonctionnalité de débogage directement dans dev studio pour effectuer la vérification des fuites-aussi longtemps que les tests de votre unité fonctionnent en utilisant le C-runtime de débogage.

un exemple simple ressemblerait à quelque chose comme ceci:

#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

}

certains cadres de test d'unité font leurs propres allocations-Google par exemple attribue des blocs quand un test d'unité échoue, donc tout bloc de test qui a un échec pour toute autre raison a toujours aussi un faux positif "fuite".

14
répondu Chris Becke 2017-03-29 04:53:21

vous pouvez utiliser Google tcmalloc bibliothèque d'allocation, qui fournit un heapchecker.

(notez que le heapchecking peut ajouter des frais généraux notables à la performance de votre programme, donc vous probablement ne veulent l'activer que sur des constructions de débogage ou des tests unitaires.)

Et vous avez demandé un exemple de code, donc c'est ici.

5
répondu Stephen 2010-06-05 15:29:35

vous pourriez être en mesure de détecter la fuite de mémoire sur les tests en fournissant votre propre mise en œuvre de nouvelles, supprimer, malloc et fonctions libres, en ajoutant des informations de suivi de mémoire sur l'allocation.

1
répondu Klaim 2010-06-05 15:27:15

1) Après quelques recherches sur le mien et basé sur la solution très agréable (pour Windows) de Chris Becke, j'ai fait une solution très similaire pour Linux OS.

2)mes objectifs de détection de fuites de mémoire:

sont assez clairs - détectent les fuites tout en:

2.1) idéalement D'une manière précise - indiquez exactement combien d'octets ont été attribués mais pas encore attribués.

2.2) effort maximal-si ce n'est pas exactement le cas, indiquer de façon "faussement positive" (nous en dire plus sur une fuite, même si elle n'est pas nécessairement dans le même temps, NE manquez PAS de détection de fuite). Il vaut mieux être plus sévère avec nous-mêmes ici.

2.3) puisque j'écris mes tests unitaires dans le cadre GTest - testez chaque test unitaire GTest comme une "entité atomique".

2.4) prendre également en considération les allocations de type" C " (désallocations) en utilisant malloc/free.

2.5) idéalement - prendre en considération c++ "allocations en place".

2.6) Facile à utiliser et intégrer dans un code existant (classes basées sur GTest pour les tests unitaires).

2.7) ont la capacité de "configurer" les paramètres des vérifications principales (activer/désactiver la vérification de mémoire, etc...) pour chaque test et/ou classe de test entière.

3) architecture de Solution:

ma solution utilise les capacités héritées de l'utilisation du cadre GTest, donc il définit une classe" de base " pour chaque classe de test unitaire que nous allons ajouter dans le futur. Fondamentalement, les principales fonctionnalités de la classe de base peut être divisé en les suivant:

3.1) exécutez le" premier "test de style GTest afin de comprendre la quantité de" mémoire supplémentaire " allouée sur le tas en cas d'échec du test.Comme Chris Becke l'a mentionné dans la dernière phrase de sa réponse ci-dessus.

3.2) facile à intégrer-héritez simplement de cette classe de base et écrivez les fonctions "Style TEST_F" de vos tests unitaires.

3.3.1) pour chaque essai, nous pouvons décider s'il faut effectuer la fuite de mémoire autrement de vérifier ou non.Ceci est fait via le Setignorememoryleakcheck Forthist () metohd. Note: il N'est pas nécessaire de le "réinitialiser" à nouveau - cela se fera automatiquement pour le prochain test en raison de la façon dont les tests de l'unité GTest fonctionnent (ils appellent le Ctor prior pour chaque appel de fonction).

3.3.2) aussi, si pour une raison quelconque vous savez à l'avance que votre test "manquera" quelques désallocations de mémoire et vous savez le montant - vous pouvez profiter des deux fonctions afin de prendre ce fait en considération une fois effectué le contrôle mémoire (qui, soit dit en passant, est effectué en soustrayant "simplement" la quantité de mémoire utilisée au début du test de la quantité de mémoire utilisée à la fin du test).

ci-dessous se trouve la classe de base de l'en-tête:

// 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;
};

Et voici la source de cette classe de base:

// 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;
}

enfin, un exemple de classe de test unitaire "gtest-based" qui utilise cette classe de base et illustre les usages et plusieurs POC différents (validation de principe) pour toutes sortes de répartitions et de vérifications différentes si nous sommes en mesure (ou non) de détecter les dé-répartitions manquées.

// 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;
}

Le fichier d'en-tête de cette classe:

// 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();
};

J'espère que c'est utile et s'il y a quelque chose qui n'est pas clair, veuillez me le faire savoir.

Cheers,

Guy.

1
répondu Guy Avraham 2017-06-24 14:19:09