Product SiteDocumentation Site

13.3. Тестовое покрытие

Теперь для версии 1.2 добавим измерения покрытия программы тестами. Для этого добавим в Makefile.am дополнительный рецепт для запуска утилиты gcov, которая обрабатывает результаты тестирования:
@user: syscall-master/Makefile.am
SUBDIRS = src doc tests

checklog:       check
	cat tests/*.log

gcov:   check
	$(MAKE) -C src gcov
  • для ручного показа статистики по тестированию добавлен рецепт checklog;
  • рецепт make gcov в основном Makefile требует предварительного запуска тестов (check), после чего вызывает make gcov в каталоге src.
Собирать статистику о выполнении строк исходного текста можно только если программа особым образом скомпилирована: в её код добавлено постоянное обновление многочисленных счётчиков, проверка достижимости и т. п. Исполняемые файлы, скомпилированные для проверки покрытия, непригодны к эксплуатации: они работают гораздо медленнее, потребляют больше ресурсов и при запуске создают множество временных файлов с отчётами:
@user: syscall-master/src/Makefile.am
CFLAGS  = -Wall -O0 -g
if COND_GCOV
CFLAGS  += -fprofile-arcs -ftest-coverage
endif

lib_LTLIBRARIES=libsyscall.la
libsyscall_la_SOURCES=globals.c utility.c basic.c
BUILT_SOURCES = syscall.h systab.h

bin_PROGRAMS = syscall
syscall_SOURCES = syscall.c
syscall_LDADD = libsyscall.la

systab.h:
	sh lssyscalls | awk '{printf "{\"%s\", %d},\n", $$1, $$2}' > $@

gcov:
	gcov -o .libs basic -t -k -b | grep -v -E '[[:digit:]]:[ /][*]'
	gcov -o .libs utility -t -k -b | grep -v -E '[[:digit:]]:[ /][*]'
Утилита gcov принимает на вход имя файла со статистикой покрытия (без расширения) которое соответствует .c-файлу из директории исходных текстов. Файлы статистики формируются в рабочей директории .libs, её надо указать с помощью ключа -o. Эти файлы собирают информацию о количестве отработанных в процессе тестирования строк кода и другие статистические параметры. Из данной статистики нас будут интересовать выводы по количеству выполнения строк кода и, дополнительно (благодаря ключу -b), вариантах обработки условий (в условных операторах / switch-case конструкции и т. д.):
@user
[user@VM syscall-master]$ make COND_GCOV=1 gcov
Making check in src
<...>
make -C src gcov
gcov -o .libs basic -t -k -b | grep -v -E '[[:digit:]]:[ /][*]'
       -:    0:Colorization: profile count: zero coverage (exceptional) zero coverage (unexceptional) unexecuted block
       -:    0:Source:basic.c
       -:    0:Graph:.libs/basic.gcno
       -:    0:Data:.libs/basic.gcda
       -:    0:Runs:14
       -:    1:#include "syscall.h"
       -:    2:
function parse_arg called 6 returned 100% blocks executed 81%
       6:    3:unsigned long parse_arg(const char *syscall_name, char *arg)
       -:    4:{
       -:    5:  unsigned long num;
       6:    6:  char *endp = NULL;
       -:    7:
       -:    8:  /* #hello - length of a string */
       6:    9:  if (arg[0] == '#') {
branch  0 taken 33% (fallthrough)
branch  1 taken 67%
       2:   10:    return (unsigned long)strlen(arg + 1);
       -:   11:  }
       -:   12:
       -:   13:  /* $0 - return value of a previous syscall */
       4:   14:  if (arg[0] == '$') {
branch  0 taken 50% (fallthrough)
branch  1 taken 50%
       2:   15:    num = strtoul(arg + 1, &endp, 10);
call    0 returned 100%
       2:   16:    if (num < CMD_MAX - 1) {
branch  0 taken 100% (fallthrough)
branch  1 taken 0%
       2:   17:      return (unsigned long)ret_values[num];
       -:   18:    }
       -:   19:  }
       -:   20:
       -:   21:  /* Try to parse it as a number */
       2:   22:  endp = NULL;
       2:   23:  num = strtoul(arg, &endp, 0);
call    0 returned 100%
       2:   24:  if (errno == ERANGE) {
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
       0:   25:    errx(1, "%s: argument '%s' is out of range",
call    0 never executed
       -:   26:      syscall_name, arg);
       -:   27:  }
       2:   28:  if (endp && *endp == '\0') {
branch  0 taken 100% (fallthrough)
branch  1 taken 0%
branch  2 taken 100% (fallthrough)
branch  3 taken 0%
       -:   29:    /* strtoul succeeded */
       2:   30:    return num;
       -:   31:  }
       -:   32:
       -:   33:  /* assume it is a string */
       -:   34:  /* unescape any \n at the end of the string */
       0:   35:  unescape_nl(arg);
call    0 never executed
       0:   36:  return (unsigned long)arg;
       -:   37:}
       -:   38:
gcov -o .libs utility -t -k -b | grep -v -E '[[:digit:]]:[ /][*]'
<...>
[user@VM syscall-master]$ ls -la src/.libs/ src | grep -E "\.gc??"
-rw-r--r-- 1 builder builder  1668 Aug 11 22:58 syscall.gcno
-rw-r--r-- 1 builder builder    228 Aug 11 22:58 basic.gcda
-rw-r--r-- 1 builder builder   5392 Aug 11 22:58 basic.gcno
-rw-r--r-- 1 builder builder     61 Aug 11 22:58 globals.gcno
-rw-r--r-- 1 builder builder    208 Aug 11 22:58 utility.gcda
-rw-r--r-- 1 builder builder   1836 Aug 11 22:58 utility.gcno
Для удобства можно добавить задание COND_GCOV параметром configure:
@user: syscall-master/configure.ac
<...>

# Variable definitins
AC_SUBST(CK_VERBOSITY, verbose)
AC_ARG_VAR(CK_VERBOSITY, [Default: "verbose", can be "silent", "minimal" or "normal")])

# Enabe/disable things
AC_ARG_ENABLE([gcov],
             [AS_HELP_STRING([--enable-gcov], [use gcov to collect test suite coverage])],
             [], [enable_gcov=no])

AM_CONDITIONAL([COND_GCOV],[test '!' "$enable_gcov" = no])

# Checks for libraries.

# Checks for header files.
<...>
Поскольку пакет с исполняемыми файлами, скомпилированными для запуска gcov, был бы не пригоден для эксплуатации, необходимо проводить тестирование на независимой сборке. Она будет проводиться в отдельной вспомогательной директории, а запуск проверки будет контролироваться с помощью макросов %def_enable для отслеживания ключа запуска тестирования и %if_enabled / %endif для обработки случая тестирования. В сам пакет же будут добавлены скомпилированные обычным образом файлы:
@user: syscall-master/.gear/syscall.spec
%def_enable gcov

Name: syscall
Version: 1.2
Release: alt1

<...>

%build
%autoreconf

%if_enabled gcov
mkdir gcov_check && cd gcov_check
../configure --enable-gcov
%make_build
cd ..
%endif

%configure
%make_build

%install
%makeinstall_std

%check
%if_enabled gcov
  cd gcov_check
  make gcov
  cd ..
%else
  make check
%endif

%files
%_bindir/%name
%_libdir/*
%_man1dir/*

%changelog
* Mon Aug 11 2025 UsamG1t <usamg1t@altlinux.org> 1.2-alt1
- Add gcov

* Fri Aug 08 2025 UsamG1t <usamg1t@altlinux.org> 1.1-alt1
- Add xUnit check

* Fri Aug 08 2025 UsamG1t <usamg1t@altlinux.org> 1.0-alt1
- Initial Build
@user
[user@VM syscall-master]$ gear-hsh --lazy
<...>
Executing(%check): /bin/sh -e /usr/src/tmp/rpm-tmp.73347
+ umask 022
+ /bin/mkdir -p /usr/src/RPM/BUILD
+ cd /usr/src/RPM/BUILD
+ cd syscall-1.2
+ make check
<...>
checkmk include.ts > include.c
<...>
PASS: include
PASS: upstream
============================================================================
Testsuite summary for syscall 1.0
============================================================================
# TOTAL: 2
# PASS:  2
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
<...>
make -C src gcov
make[1]: Entering directory '/usr/src/RPM/BUILD/syscall-1.2/src'
gcov -o .libs basic -t -k -b | grep -v -E '[[:digit:]]:[ /][*]'
<...>