Модульное тестирование библиотеки C, управления памятью

Я работаю над довольно большой библиотекой C, в которой сейчас нет тестов. Поскольку API начинает быть окончательным, я хотел бы начать писать модульные тесты.

Почти все мои функции действуют на первый параметр (структуру).

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

Теперь это работает со структурой, состоящей из скалярных типов, но что касается выделенной памяти, мне было интересно, какой подход вы использовали.

Например, представьте себе структуру изображения, когда вы это сделаете:

CreateImage(&img, x, y); 

вы ожидаете, что img-> x будет x, img-> y равным y и img-> пикселям, чтобы быть указателем на что-то достаточно большое, чтобы удерживать x * y * sizeof(pixel) .

Проверка первых двух тривиальна, но как насчет img-> пикселей? Я не хочу проверять, был ли вызов malloc успешным, поскольку я могу перегрузить malloc, но я хочу знать, правильно ли был вызван malloc.

Это особенно важно в случае:

 CreateImage(*img, x, y) { img->x = x; img->y = y; /* do something, dhoo, that something is broken and modify x or y */ img->pixels = malloc(x * y * sizeof(pixel)); /* wrong allocation size */ if(!img->pixels) error("no memory"); } 

Надеюсь, мой вопрос ясен.

Благодарю.

В своем модульном тесте, ваша перегруженная функция malloc зарегистрирует значение, с которым он был вызван (например, alloc_size ).

Затем просто проверьте, что все это верно:

  • alloc_size % sizeof(pixel) == 0
  • alloc_size % x == 0
  • alloc_size % y == 0
  • ((alloc_size / sizeof(pixel)) / x ) / y == 1

Не просто копируйте код из функции, которую вы тестируете, т.е. умножение – потому что вы можете завершить репликацию ошибки.

В этом случае вы можете реплицировать ошибку – если нет тестов ограничений на x и y , то умножение в вызове malloc само по себе является ошибкой (рассмотрите, что произойдет в системе с 32-разрядным size_t , sizeof(pixel) == 4 , x == 40000 и y == 40000 ).

Вы можете перегрузить malloc, чтобы установить на него ряд ожиданий. Поскольку вы знаете, прежде чем сделать звонок, сколько памяти ему нужно выделить, вы могли бы что-то в этом роде.

 SetMallocExpectation(x * y * sizeof(pixel)); CreateImage(&img, x, y); if ( InvalidMalloc() ) { // The malloc call was with a different size } ResetMalloc(); 

Что касается реализации, я полагаю, что это будет выглядеть примерно так

 size_t mallocExpectations[SOME_SIZE]; int mallocCur = 0; int mallocError = 0; int numExpectations = 0; void* MyMalloc(size_t size) { if ( size != mallocExpectations[mallocCur++] ) { mallocError = 1; } return (*malloc)(size); } void SetMallocExpectation(size_t size) { mallocExpectations[numExpectations++] = size; } int InvalidMalloc() { return mallocError; } void ResetMalloc() { mallocCur = 0; mallocError = 0; curExpecations = 0; } 

Другая идея – попытаться заполнить буфер, созданный malloc до максимального размера, а затем запустить тестовый пакет с помощью Valgrind. Это должно иметь проблемы с распределением поверхности. Запуск его с Valgrind полезен и для других вещей, таких как обнаружение утечек памяти.