2012-01-22 20 views
11

Vorrei scrivere test per una libreria C, in C. Mi piacerebbe prendere in giro alcune funzioni per il test.Funzione di simulazione (per test) in C?

Suppongo che la mia libreria viene compilata della fonte seguente:

/* foo.h */ 
int myfunction(int x, int y); 

/* foo.c */ 
#include "foo.h" 

static int square(int x) { return x * x; } 

int myfunction(int x, int y) { 
    return square(x) + square(y); 
} 

voglio scrivere un test come questo:

/* foo_test.c */ 
#include "foo.h" 

static int square(int x) { return x + 1; } 

int main(void) { 
    assert(myfunction(0, 0) == 2); 
    return 0; 
} 

C'è un modo posso compilare in modo che myfunction utilizzerà il definizione di square in foo_test.c, anziché quella in foo.c, solo quando si collega l'eseguibile foo_test? Cioè, voglio compilare foo.c in una libreria (chiamiamolo libfoo.so), quindi compila foo_test.c con libfoo.so e un po 'di magia in modo da ottenere un eseguibile foo_test che utilizza la diversa implementazione di square.

Sarebbe utile sentire le soluzioni per quando square non è dichiarato static, ma risolvere il caso di cui sopra sarebbe ancora meglio. EDIT: Sembra senza speranza, ma ecco un'idea: Supponiamo che io compili con -O0 -g quindi è improbabile che square si inline e dovrei avere simboli che mostrano dove è stata risolta la chiamata. C'è un modo per intrufolarsi nel file oggetto e scambiare il riferimento risolto?

+0

'LD_PRELOAD' può darti questo, ma spero che qualcun altro abbia una risposta migliore. – sarnold

+3

Non per funzioni statiche – bdonlan

+1

Non penso che 'LD_PRELOAD' lo farà. Voglio essere in grado di cambiare una funzione (che potrebbe essere statica) all'interno di una libreria che è già stata compilata. Come ho capito (e forse questo è falso), il simbolo 'square' dentro' myfunction' sarà già risolto prima di provare a collegare 'foo_test'. – leif

risposta

4

Sembra che si sta utilizzando GCC, in modo da poter utilizzare l'attributo deboli:

L'attributo debole provoca la dichiarazione deve essere emessa come un debole simbolo piuttosto che a livello globale. Questo è principalmente utile per definire le funzioni della libreria che possono essere sovrascritte nel codice utente, sebbene sia possibile utilizzare anche con dichiarazioni senza funzione. I simboli deboli sono supportati per i target ELF e anche per gli obiettivi a.out quando si utilizza l'assemblatore e il linker GNU .

http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

+0

Grazie. Probabilmente potrei prenderlo da qui, ma puoi darmi qualche consiglio su come modificare un riferimento debole? – leif

+1

Basta definire una funzione con lo stesso nome che NON sia debole, quindi dovrebbe sovrascrivere la funzione debole nella libreria. –

4

No, non c'è soluzione per questo. Se esiste una funzione nell'ambito con un nome che corrisponde a una chiamata di funzione all'interno di un file sorgente, verrà utilizzata tale funzione. Nessun trucco di dichiarazione ne parlerà con il compilatore. Nel momento in cui il linker è attivo, il riferimento al nome sarà già stato risolto.

0

In casi come il vostro io uso Typemock Isolator++ API.

Consente di sostituire il comportamento originale del metodo con la propria implementazione. Ma, a causa delle specifiche del linguaggio, è necessario evitare gli stessi nomi per le funzioni sotto e per i test.

#include "foo.h" 

static int square_test(int x) { return x + 1; } 

TEST_CLASS(ArgumentTests) 
{ 
public: 
    TEST_METHOD_CLEANUP(TearDown) 
    { 
     ISOLATOR_CLEANUP(); 
    } 

    TEST_METHOD(TestStaticReplacedStatic) 
    { 
     PRIVATE_WHEN_CALLED(NULL, square).DoStaticOrGlobalInstead(square_test, NULL); 
     Assert::IsTrue(myfunction(0, 0) == 2); 
    } 
}; 

Spero che ti sia utile, buona fortuna!

4

Ho scritto Mimick, una libreria di derisione/stub per le funzioni C che risolvono questo problema.

Supponendo che il quadrato non sia statico né inline (perché altrimenti diventa legato all'unità di compilazione e alle funzioni che lo utilizzano) e che le tue funzioni sono compilate all'interno di una libreria condivisa denominata "libfoo.so" (o qualunque sia la tua convenzione di denominazione di piattaforma è), questo è quello che si potrebbe fare:

#include <stdlib.h> 
#include <assert.h> 
#include <mimick.h> 

/* Define the blueprint of a mock identified by `square_mock` 
    that returns an `int` and takes a `int` parameter. */ 
mmk_mock_define (square_mock, int, int); 

static int add_one(int x) { return x + 1; } 

int main(void) { 
    /* Mock the square function in the foo library using 
     the `square_mock` blueprint. */ 
    mmk_mock("[email protected]:foo", square_mock); 

    /* Tell the mock to return x + 1 whatever the given parameter is. */ 
    mmk_when(square(mmk_any(int)), .then_call = (mmk_fn) add_one); 

    /* Alternatively, tell the mock to return 1 if called with 0. */ 
    mmk_when(square(0), .then_return = &(int) { 1 }); 

    assert(myfunction(0, 0) == 2); 

    mmk_reset(square); 
} 

questa è una soluzione beffardo in piena regola, però, e se si desidera solo stub square (e non si cura di interazioni di test), si potrebbe fai qualcosa di simile:

#include <stdlib.h> 
#include <assert.h> 
#include <mimick.h> 

static int my_square(int x) { return x + 1; } 

int main(void) { 
    mmk_stub("[email protected]:foo", my_square); 

    assert(myfunction(0, 0) == 2); 

    mmk_reset(square); 
} 

Mimick utilizza una certa introspezione nell'eseguibile e avvelenamento in esecuzione nella tabella di offset globale per reindirizzare le funzioni allo stub di nostra scelta.