Данный раздел посвящён исследованию инструментов сборки библиотек и тестированию нашего проекта
Сборка библиотек
При работе с проектами мы встречаемся с разными проблемами при подключении библиотек. В случае динамических библиотек, например, нам в обязательном порядке необходимо вручную указывать места их расположения через LD_PRELOAD и LD_LIBRARY_PATH. Более того, само создание библиотек также требует отдельной компиляции и компоновки её исходников, чем вручную заниматься тоже нет большого желания.
Разберём небольшой пример, в котором создадим собственную библиотеку и будем её использовать.
main.c - наша тестирующая программа библиотеки будет просто инкрементировать поступающее ей из аргументов командной строки число
#include <stdio.h>
#include <stdlib.h>
#include "lib.h"
int main(int argc, char *argv[]) {
int n;
if(argc < 2) {
fprintf(stderr, "Usage: %s NUMBER\n", argv[0]);
return 1;
}
n = atoi(argv[1]);
printf("%d\n", inc(n));
return 0;
}
lib.c - зададим функцию инкрементирования
#include "lib.h"
int inc(int var) {
return var + inc_var;
}
libg.c - укажем глобальную переменную для функции
#include "lib.h"
int inc_var = 1;
lib.h - конечно же добавим заголовочный файл
int inc(int);
extern int inc_var;
Напишем для нашей библиотеки Makefile, который будет собирать её и тестировать
Makefile
all: inc
libinc.so: lib.c libg.c
gcc -fPIC -shared $^ -o $@
inc: main.c libinc.so
gcc -L. $< -linc -o $@
check: inc
LD_LIBRARY_PATH=`pwd` ./inc 123
clean:
rm -f *.so inc
Попробуем скомпилировать и запустить тесты у библиотеки
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> make
gcc -fPIC -shared lib.c libg.c -o libinc.so
gcc -L. main.c -linc -o inc
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> make check
LD_LIBRARY_PATH=`pwd` ./inc 123
124
user@localhost:~/LinuxAppDev/10_LibTesting/Lection>
Всё отлично работает.
Таким созданием библиотек мы уже занимались, и, как мы вновь убедились, даже для такой небольшой и несложной иерархии файлов нам нужно провести немалое количество операций. Именно для этого нам и нужны инструменты, которые сделают всё за нас
libtool
libtool это очень мощный инструмент по созданию и тестированию библиотек на все случаи жизни.
Опишем Makefile с использованием специальных флагов и команд этой утилиты
Makefile
CFLAGS = -g -O
LTFLAGS = --tag=CC
all: inc
%.lo: %.c
libtool --mode=compile $(LTFLAGS) $(CC) -c $<
libinc.la: lib.lo libg.lo
libtool --mode=link $(LTFLAGS) $(CC) -o $@ $^ -rpath /usr/lib64
inc: main.o libinc.la
libtool --mode=link $(LTFLAGS) $(CC) -o $@ $^
check: inc
./$< 123
clean:
rm -rf *.so inc .libs *.l? *.o
Разберём добавленные нами команды:
- Не забываем про встроенные команды
make, благодаря которым нам можно не описывать правила компиляции файлов, а воспользоваться встроенными рецептами и флагамиCFLAGS - Так как
libtool- стандартная утилита работы с библиотеками, вmakeдля неё существует своя переменная окруженияLTFLAGS. При этом базовые флаги для работы с библиотеками (например,-fPIC) добавятся автоматически, мы же указываем специализированные, как, например, информацию о компиляторе libtoolдля работы создаёт не совсем обычные объектные файлы, а так называемыеlibtool objects(%.lo). Для этого указывается специальный режимcompileдля создания объектных файлов, а также%.lo-файлов- Далее
libtoolна основе указанных намиlo-файлов создаётlibtool archive(.la). Мы помним, что через.aу нас указывалась статическая библиотека, здесь же это основа сразу для статической и динамической библиотек. Для создания архива мы указываем режим компоновкиlink, а также стандартное место расположения библиотек (-rpath), куда будет помещены готовые библиотеки - Для создания бинарного файла мы также в режиме компоновки объединяем архив с о-файлом тестирующей функции. Это, кстати, даёт нам возможность в тестирующем рецепте убрать явное указание места загрузки динамической библиотеки -
libtoolсам знает, куда положил библиотеку и сам её подключит
Сейчас наш Makefile даже увеличился и будто вовсе не упростил нам жизнь и работу с библиотекой. Проверим, что он хотя бы не разрушил её работоспособность
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> ls
inc lib.c libg.c lib.h libinc.so main.c Makefile
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> make clean
rm -rf *.so inc .libs *.l? *.o
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> ls
lib.c libg.c lib.h main.c Makefile
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> make
cc -g -O -c -o main.o main.c
libtool --mode=compile --tag=CC cc -c lib.c
libtool: compile: cc -c lib.c -fPIC -DPIC -o .libs/lib.o
libtool: compile: cc -c lib.c -o lib.o >/dev/null 2>&1
libtool --mode=compile --tag=CC cc -c libg.c
libtool: compile: cc -c libg.c -fPIC -DPIC -o .libs/libg.o
libtool: compile: cc -c libg.c -o libg.o >/dev/null 2>&1
libtool --mode=link --tag=CC cc -o libinc.la lib.lo libg.lo -rpath /usr/lib64
libtool: link: gcc -shared -fPIC -DPIC .libs/lib.o .libs/libg.o -Wl,-soname -Wl,libinc.so.0 -o .libs/libinc.so.0.0.0
libtool: link: (cd ".libs" && rm -f "libinc.so.0" && ln -s "libinc.so.0.0.0" "libinc.so.0")
libtool: link: (cd ".libs" && rm -f "libinc.so" && ln -s "libinc.so.0.0.0" "libinc.so")
libtool: link: ar cr .libs/libinc.a lib.o libg.o
libtool: link: ranlib .libs/libinc.a
libtool: link: ( cd ".libs" && rm -f "libinc.la" && ln -s "../libinc.la" "libinc.la" )
libtool --mode=link --tag=CC cc -o inc main.o libinc.la
libtool: link: cc -o .libs/inc main.o ./.libs/libinc.so -Wl,-rpath -Wl,/usr/lib64
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> make check
./inc 123
124
user@localhost:~/LinuxAppDev/10_LibTesting/Lection>
Как мы видим, libtool создал нам и статическую (.a), и динамическую библиотеки (.so), разместив их где-то в директории .libs. Кроме того, в процессе работы мы создавали разные интересные объектные файлы, исследуем их составляющую.
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> ls -a
. inc libg.c libg.o libinc.la lib.o main.c Makefile
.. lib.c libg.lo lib.h lib.lo .libs main.o
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> ls .libs/
inc libinc.a libinc.lai libinc.so.0 lib.o
libg.o libinc.la libinc.so libinc.so.0.0.0
user@localhost:~/LinuxAppDev/10_LibTesting/Lection>
Два бинарника inc, что они из себя представляют?
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> diff inc .libs/inc
Binary files inc and .libs/inc differ
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> ./inc 123
124
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> ./.libs/inc 123
./.libs/inc: error while loading shared libraries: libinc.so.0: cannot open shared object file: No such file or directory
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> LD_LIBRARY_PATH=`pwd`/.libs ./.libs/inc 123
124
user@localhost:~/LinuxAppDev/10_LibTesting/Lection>
Внешний подтягивает библиотеку автоматически, внутренний - требует ручного подключения.
А что из себя представляют .lo-файлы и .la-файлы?
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> cat lib.lo
# lib.lo - a libtool object file
# Generated by libtool (GNU libtool) 2.4.7
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# Name of the PIC object.
pic_object='.libs/lib.o'
# Name of the non-PIC object
non_pic_object='lib.o'
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> cat libinc.la
# libinc.la - a libtool library file
# Generated by libtool (GNU libtool) 2.4.7
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# The name that we can dlopen(3).
dlname='libinc.so.0'
# Names of this library.
library_names='libinc.so.0.0.0 libinc.so.0 libinc.so'
# The name of the static archive.
old_library='libinc.a'
# Linker flags that cannot go in dependency_libs.
inherited_linker_flags=''
# Libraries that this one depends upon.
dependency_libs=''
# Names of additional weak libraries provided by this library
weak_library_names=''
# Version information for libinc.
current=0
age=0
revision=0
# Is this an already installed library?
installed=no
# Should we warn about portability when linking against -modules?
shouldnotlink=no
# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''
# Directory that this library needs to be installed in:
libdir='/usr/lib64'
user@localhost:~/LinuxAppDev/10_LibTesting/Lection>
Это, как и предполагалось, не обычные объектные файлы, а описания того, как строятся наши библиотеки - “старый” формат (статическая библиотека), информация о предзагрузках, расположение и версии библиотек. Сами же библиотеки хранятся в .libs и подгружаются оттуда.
Вообще говоря, и лежащий вне директориии inc это тоже непросто бинарный файл программы, а shell-скрипт, в котором и подключаются все зависимости:
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> cat inc
#! /usr/bin/bash
# inc - temporary wrapper script for .libs/inc
# Generated by libtool (GNU libtool) 2.4.7
#
# The inc program cannot be directly executed until all the libtool
# libraries that it depends on are installed.
#
# This wrapper script should never be moved out of the build directory.
# If it is, it will not operate correctly.
<...>
LD_LIBRARY_PATH="/home/user/LinuxAppDev/10_LibTesting/Lection/.libs:$LD_LIBRARY_PATH"
<...>
user@localhost:~/LinuxAppDev/10_LibTesting/Lection>
Версионирование
Как мы видели в .libs, libtool создал нам три версии нашей библиотеки - без чисел, с одним числом и с тремя числами.
user@localhost:~/LinuxAppDev/10_LibTesting/Lection> cd .libs/
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/.libs> la -A *.so*
lrwxrwxrwx 1 user user 15 Nov 25 07:55 libinc.so -> libinc.so.0.0.0
lrwxrwxrwx 1 user user 15 Nov 25 07:55 libinc.so.0 -> libinc.so.0.0.0
-rwxr-xr-x 1 user user 16624 Nov 25 07:55 libinc.so.0.0.0
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/.libs>
Две из них - символьные ссылки на третью. Бесчисловая версия используется для создания зависимостей на библиотеку при работе зависящих утилит, мажорная (“основная”, с одним числом) используется для указания на основную поддерживаемую версию, а последняя (единственная реальная библиотека) является самим рабочим объектом.
Что же означают числа в версии библиотеки? В случае с библиотеками версионирование описывается через изменение ABI - Application Binary Interface. Он задаёт низкоуровневые аспекты работы ПО на уровне машинного кода и инструкций процессора.
В соответствии с правилами версионирования библиотек их нумерация определяется полями current:revision:age
currentописывает измененияABI, влияющие на обратную совместимость библиотеки, и увеличивается при каждом вмешательстве в негоrevisionописывает изменения, не меняющиеABI, и увеличивается при внесении таких правок, устанавливаясь в 0 при каждом измененииcurrentageописывает измененияABI, не влияющие на обратную совместимость. Например, добавление нового поля в конец структуры при определённых дисциплинах программирования не будет ломать совместимость (ageвсегда должно быть не большеcurrent)
Версионирование библиотек можно переложить на стандартный формат version.minor.subminor для описания основной, и двух меньших нумераций версий, меняющихся в зависимости от глобальности изменений файла, который будет вычисляться как {current-age}.age.revision, однако полноценно считать это версий неправильно, как минимум, из-за неравномерности роста version. Зато такое версионирование библиотек позволяет нам поддерживать сразу диапазон допустимых интерфейсов - от current - age до current
Семантическое версионирование, к которому мы привыкли, не может похвастаться таким диапазоном. Оно описывается значениями MAJOR.MINOR.PATCH и меняется по следующим правилам:
- Обратно несовместимые изменения
ABI(удаление / изменение)—MAJOR++.MINOR=0.PATCH=0 - Обратно совместимые изменения
ABI(добавление) —MAJOR.MINOR++.PATCH=0 - Обратно совместимые багфиксы
ABIи изменения, не затрагивающиеABI—MAJOR.MINOR.PATCH++
Тестирование
Поговорим теперь про тестирование. То прекрасное время, когда все ошибки были только на уровне компиляции, к сожалению, бесповоротно прошло, компилятор проверяет лишь синтаксические ошибки кода. А для проверки семантики кода, а также того информационного пространства, которое этот код создаёт и с которым взаимодействует (глобальные переменные, макросы, переменные окружения) мы и создаём тесты. Их прохождение, конечно, не гарантирует правильность работы функций, однако с большой долей вероятности не даст нам неработающий код оставить незамеченным.
Выделяют 4 вида тестирования:
- Модульное - проверяет работу отдельных модулей написанного продукта
- Системное - проверяет работу всей системы изолированно от внешних зависимостей
- Интеграционное - проверяет работу в рамках среды, в которой она запускается, и на, вообще говоря, не нами задаваемых условиях среды
- Приёмочное тестирование - проверяет внешнее воздействие на проект, внесение невстроенных тестов и проверка поведения системы вообще на её взаимодействие с программами
Мы будем обсуждать модульное тестирование xUnit, задающее собственный путь тестирования системы.
В рамках Unit-тестов определён test Runner - основная программа, запускающая тесты. Он отвечает за проверку подключаемости тестирующего кода (библиотека это или какой-то другой исполняемый объект), фильтрацию тестов, сбор информации по запускам и т.д.
Все тесты (test) объединены в отдельные тестирующие блоки (case), описывающие работу какой-то конкретной функции, а множества блоков, описывающих один раздел работы с проектом (например, работа с файлами) объединены в test suite. Для более точного понимания можно запомнить, что suite описывает “Какая функциональность сломалась?”, case - “Какая функция сломалась?”, test - “На каких данных сломалась?”
Следующий важный блок любого тестирования - подготовка и ликвидация тестируемого окружения - фикстуры. Они могут создаваться для каждого из уровней тестирования и описывать все необходимые зависимости и данные для проверки работоспособности системы.
Также в рамках тестирования считается специальный параметр coverage, описывающий полноту тестирования программы. Это может быть проверка как процент количества отработанных строк, описывающий тесты, так и проверка путей исполняемых тестов.
Check
Одна из классических утилит для создания тестирования это [***check***](https://libcheck.github.io/check/). Он поддерживает все современные технологии тестирования, а также написан на Си, что нам очень на руку в рамках этого курса.
Для примера разберём генератор фэнтезийных имён и попробуем написать для него тесты
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> tree
.
└── src
├── example.c
├── Makefile
├── namegen.h
└── tablegen.py
2 directories, 4 files
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy>
example.c
#include "namegen.h"
#include <time.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
static unsigned long
hash32(unsigned long a)
{
a = a & 0xffffffffUL;
a = (a ^ 61UL) ^ (a >> 16);
a = (a + (a << 3)) & 0xffffffffUL;
a = a ^ (a >> 4);
a = (a * 0x27d4eb2dUL) & 0xffffffffUL;
a = a ^ (a >> 15);
return a;
}
#if _WIN32
# define C_RED(s) s
# define C_GREEN(s) s
#else
# define C_RED(s) "\033[31;1m" s "\033[0m"
# define C_GREEN(s) "\033[32;1m" s "\033[0m"
#endif
#define TEST(s, x) \
do { \
if (x) { \
printf(C_GREEN("PASS") " %s\n", s); \
count_pass++; \
} else { \
printf(C_RED("FAIL") " %s\n", s); \
count_fail++; \
} \
} while (0)
static void
tests(void)
{
int r;
char buf[256];
int count_pass = 0;
int count_fail = 0;
unsigned long rng[] = {0xd22bfb3b};
{
int i;
int mismatch = 0;
int seen[3] = {0, 0, 0};
for (i = 0; i < 100; i++) {
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), "0|1|2", rng);
if (r != NAMEGEN_SUCCESS || buf[1] || !strchr("012", buf[0])) {
mismatch++;
} else {
seen[buf[0] - '0']++;
}
}
TEST("alternation", !mismatch && seen[0] && seen[1] && seen[2]);
}
{
int i;
int mismatch = 0;
int seen[6] = {0, 0, 0};
for (i = 0; i < 100; i++) {
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), "(v)v", rng);
if (r != NAMEGEN_SUCCESS || buf[0] != 'v' || !buf[1] || buf[2]) {
mismatch++;
} else {
char *p = "aeiouy";
char *s = strchr(p, buf[1]);
if (!s)
mismatch++;
else
seen[s - p]++;
}
}
for (i = 0; i < 6; i++)
mismatch += !seen[i];
TEST("substitution", !mismatch);
}
{
r = namegen(buf, sizeof(buf), "a>", rng);
TEST("closing bracket", r == NAMEGEN_INVALID);
}
{
r = namegen(buf, sizeof(buf), "a)", rng);
TEST("closing paren", r == NAMEGEN_INVALID);
}
{
r = namegen(buf, sizeof(buf), "<a)", rng);
TEST("mismatch paren", r == NAMEGEN_INVALID);
}
{
r = namegen(buf, sizeof(buf), "(a>", rng);
TEST("mismatch bracket", r == NAMEGEN_INVALID);
}
{
r = namegen(buf, sizeof(buf), "<a", rng);
TEST("unclosed bracket", r == NAMEGEN_INVALID);
}
{
r = namegen(buf, sizeof(buf), "(a", rng);
TEST("unclosed paren", r == NAMEGEN_INVALID);
}
{
char *p = "(((((<(((((((((((((((a)))))))))))))))>)))))";
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), p, rng);
TEST("deep nesting", r == NAMEGEN_SUCCESS && !strcmp(buf, "a"));
}
{
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, 4, "(abcdefg)", rng);
TEST("truncation 1",
r == NAMEGEN_TRUNCATED && !strcmp(buf, "abc") && buf[4] == 0x5a);
}
{
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, 2, "i", rng);
TEST("truncation 2",
r == NAMEGEN_TRUNCATED && buf[0] && !buf[1] && buf[2] == 0x5a);
}
{
r = namegen(buf, sizeof(buf), "!(foo)", rng);
TEST("capitalization 1", r == NAMEGEN_SUCCESS && !strcmp(buf, "Foo"));
}
{
r = namegen(buf, sizeof(buf), "(!foo)", rng);
TEST("capitalization 2", r == NAMEGEN_SUCCESS && !strcmp(buf, "Foo"));
}
{
r = namegen(buf, sizeof(buf), "(f!oo)", rng);
TEST("capitalization 3", r == NAMEGEN_SUCCESS && !strcmp(buf, "fOo"));
}
{
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), "", rng);
TEST("empty", r == NAMEGEN_SUCCESS && !buf[0] && buf[1] == 0x5a);
}
if (count_fail)
exit(EXIT_FAILURE);
printf("All %d tests passed\n", count_pass);
exit(EXIT_SUCCESS);
}
int
main(int argc, char **argv)
{
int i;
int count = 1;
FILE *urandom;
unsigned char randbuf[4];
unsigned long seed[] = {0x8af611acUL};
/* Parse command line arguments */
if (argc < 2 || argc > 3) {
printf("Usage: %s <pattern> [num]\n", argv[0]);
printf(" pattern template for names to generate\n");
printf(" num number of names to generate\n");
exit(EXIT_FAILURE);
} else if (argc == 3) {
count = atoi(argv[2]);
}
if (!strcmp(argv[1], "--test"))
tests();
/* Shuffle up the seed a bit */
urandom = fopen("/dev/urandom", "rb");
if (urandom && fread(randbuf, sizeof(randbuf), 1, urandom)) {
unsigned long rnd =
(unsigned long)randbuf[0] << 0 |
(unsigned long)randbuf[1] << 8 |
(unsigned long)randbuf[2] << 16 |
(unsigned long)randbuf[3] << 24;
*seed ^= rnd;
fclose(urandom);
} else {
void *p = malloc(4UL * 1024 * 1024);
*seed ^= hash32(time(0)); /* Current time */
*seed ^= hash32((ptrdiff_t)main); /* ASLR entopy */
*seed ^= hash32((ptrdiff_t)seed); /* Stack gap entropy */
*seed ^= hash32((ptrdiff_t)p); /* Allocator entropy */
*seed ^= hash32((ptrdiff_t)malloc); /* C library ASLR */
free(p);
}
/* Generate some names */
for (i = 0; i < count; i++) {
char buf[256];
int r = namegen(buf, sizeof(buf), argv[1], seed);
if (r == NAMEGEN_INVALID) {
fprintf(stderr, "%s: invalid pattern\n", argv[0]);
exit(EXIT_FAILURE);
}
printf("%s\n", buf);
}
return 0;
}
namegen.h
/* Fantasy name generator ANSI C header library
* This is free and unencumbered software released into the public domain.
*
* The compile() function creates a name generator based on an input
* pattern. The letters s, v, V, c, B, C, i, m, M, D, and d represent
* different types of random replacements. Everything else is emitted
* literally.
*
* s - generic syllable
* v - vowel
* V - vowel or vowel combination
* c - consonant
* B - consonant or consonant combination suitable for beginning a word
* C - consonant or consonant combination suitable anywhere in a word
* i - insult
* m - mushy name
* M - mushy name ending
* D - consonant suited for a stupid person's name
* d - syllable suited for a stupid person's name (begins with a vowel)
*
* All characters between parenthesis () are emitted literally. For
* example, the pattern "s(dim)", emits a random generic syllable
* followed by "dim".
*
* Characters between angle brackets <> emit patterns from the table
* above. Imagine the entire pattern is wrapped in one of these.
*
* In both types of groupings, a vertical bar | denotes a random choice.
* Empty groups are allowed. For example, "(foo|bar)" emits either "foo"
* or "bar". The pattern "<c|v|>" emits a constant, vowel, or nothing at
* all.
*
* An exclamation point ! means to capitalize the component that follows
* it. For example, "!(foo)" will emit "Foo" and "v!s" will emit a
* lowercase vowel followed by a capitalized syllable, like "eRod".
*
* This library is based on the RinkWorks Fantasy Name Generator.
* http://www.rinkworks.com/namegen/
*/
#ifndef NAMEGEN_H
#define NAMEGEN_H
#define NAMEGEN_MAX_DEPTH 32 /* Cannot exceed bits in a long */
/* Return codes */
#define NAMEGEN_SUCCESS 0
#define NAMEGEN_TRUNCATED 1 /* Output was truncated */
#define NAMEGEN_INVALID 2 /* Pattern is invalid */
#define NAMEGEN_TOO_DEEP 3 /* Pattern exceeds maximum nesting depth */
/* Generate a name into DST of LEN bytes from PATTERN and using SEED.
*
* The length must be non-zero. For best results, the lower 32 bits of
* the seed should be thoroughly initialized. A particular seed will
* produce the same results on all platforms.
*
* The return value is one of the above codes, indicating success or
* that something went wrong. Truncation occurs when DST was too short.
* Pattern is validated even when the output has been truncated.
*/
static int
namegen(char *dst, unsigned long len, const char *pattern, unsigned long *seed);
/* Implementation */
/* Rather than compile the pattern into some internal representation,
* the name is generated directly from the pattern in a single pass
* using reservoir sampling. If an alternate option is selected, the
* output pointer is reset to "undo" the output for the previous group.
* This means the output buffer may be written beyond the final output
* length (but never beyond the buffer length).
*
* The substitution templates are stored in an efficient, packed form
* that contains no pointers. This is to avoid cluttering up the
* relocation table, but without any additional run-time overhead.
*/
/* Return offsets table offset for C, or -1 if not special.
* The return value is suitable for namegen_offsets().
*/
static int
namegen_special(int c)
{
static const signed char special[] = {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, 0x00, 0x01, 0x02, -1, -1, -1,
-1, -1, -1, -1, -1, 0x03, -1, -1,
-1, -1, -1, -1, -1, -1, 0x04, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 0x05, 0x06, -1, -1, -1,
-1, 0x07, -1, -1, -1, 0x08, -1, -1,
-1, -1, -1, 0x09, -1, -1, 0x0a, -1,
-1, -1, -1, -1, -1, -1, -1, -1
};
if (c >= 0 && c < 128)
return special[c];
return -1;
}
/* Return offsets and number of offsets for special N.
* Offsets point into namegen_argz.
*/
static int
namegen_offsets(int n, const short **offsets)
{
static const short offsets_table[] = {
0x0000, 0x0002, 0x0005, 0x0008, 0x000a, 0x000d, 0x0011, 0x0014,
0x0017, 0x0019, 0x001c, 0x001e, 0x0020, 0x0022, 0x0024, 0x0026,
0x0028, 0x002b, 0x002d, 0x002f, 0x0031, 0x0034, 0x0037, 0x0039,
0x003c, 0x003e, 0x0042, 0x0045, 0x0048, 0x004b, 0x004e, 0x0051,
0x0055, 0x0058, 0x005a, 0x005d, 0x0061, 0x0064, 0x0066, 0x0068,
0x006b, 0x006d, 0x006f, 0x0072, 0x0074, 0x0076, 0x0079, 0x007c,
0x007e, 0x0080, 0x0082, 0x0085, 0x0087, 0x0089, 0x008b, 0x008e,
0x0091, 0x0094, 0x0096, 0x0098, 0x009b, 0x009e, 0x00a1, 0x00a3,
0x00a6, 0x00a8, 0x00aa, 0x00ad, 0x00b0, 0x00b3, 0x00b5, 0x00b8,
0x00bb, 0x00be, 0x00c0, 0x00c3, 0x00c5, 0x00c7, 0x00c9, 0x00cb,
0x00cd, 0x00d0, 0x00d3, 0x00d6, 0x00d8, 0x00da, 0x00dd, 0x00e0,
0x00e2, 0x00e5, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f1, 0x00f4,
0x00f6, 0x00f8, 0x00fa, 0x00fd, 0x00ff, 0x0103, 0x0109, 0x010f,
0x0114, 0x011a, 0x011f, 0x0127, 0x0130, 0x013a, 0x013f, 0x0144,
0x0148, 0x014d, 0x0151, 0x0156, 0x015b, 0x0160, 0x0165, 0x0169,
0x016d, 0x0172, 0x0177, 0x017c, 0x017e, 0x0180, 0x0182, 0x0184,
0x0186, 0x0188, 0x018b, 0x018e, 0x0191, 0x0194, 0x0197, 0x019a,
0x019d, 0x01a0, 0x01a3, 0x01a6, 0x01a9, 0x01ac, 0x01af, 0x01b2,
0x01b5, 0x01b8, 0x01ba, 0x01bc, 0x01be, 0x01c0, 0x01c2, 0x01c4,
0x01c6, 0x01c8, 0x01ca, 0x01cc, 0x01ce, 0x01d0, 0x01d2, 0x01d4,
0x01d6, 0x01d8, 0x01da, 0x01dc, 0x01de, 0x01e0, 0x01e2, 0x01e7,
0x01ed, 0x01f0, 0x01f3, 0x01f6, 0x01fb, 0x01ff, 0x0204, 0x0208,
0x020c, 0x020f, 0x0213, 0x0217, 0x021b, 0x021f, 0x0223, 0x0227,
0x022b, 0x022f, 0x0234, 0x0237, 0x023b, 0x023e, 0x0242, 0x0246,
0x0249, 0x024d, 0x0251, 0x0256, 0x0259, 0x025d, 0x0261, 0x0265,
0x026a, 0x026e, 0x0272, 0x0276, 0x027c, 0x0281, 0x0286, 0x028b,
0x028f, 0x0296, 0x029b, 0x02a2, 0x02a7, 0x02ac, 0x02b2, 0x02b7,
0x02bb, 0x02c0, 0x02c5, 0x02ca, 0x02cf, 0x02d4, 0x02db, 0x02e0,
0x02e7, 0x02ec, 0x02f4, 0x02f9, 0x02ff, 0x0307, 0x030f, 0x0314,
0x0319, 0x031e, 0x0323, 0x0328, 0x032e, 0x0332, 0x0337, 0x033b,
0x0340, 0x0346, 0x034c, 0x0353, 0x035b, 0x0361, 0x0366, 0x036a,
0x036f, 0x0374, 0x0379, 0x0380, 0x0387, 0x038e, 0x0395, 0x039b,
0x03a2, 0x03a9, 0x03b0, 0x03b6, 0x03bd, 0x03c3, 0x03c9, 0x03d0,
0x03d8, 0x03df, 0x03e7, 0x03ee, 0x03f6, 0x03fb, 0x0402, 0x0409,
0x0413, 0x041d, 0x0427, 0x0431, 0x0438, 0x0441, 0x0448, 0x0450,
0x0457, 0x045f, 0x0467, 0x046e, 0x0476, 0x047d, 0x0483, 0x048a,
0x0491, 0x0498, 0x049f, 0x04a5, 0x04ab, 0x04af, 0x04b3, 0x04b6,
0x04ba, 0x04be, 0x04c2, 0x04c5, 0x04c9, 0x04cc, 0x04d0, 0x04d3,
0x04d7, 0x04da, 0x04de, 0x04e3, 0x04e6, 0x04ea, 0x04ee, 0x04f2,
0x04f6, 0x04fa, 0x04fe, 0x0502, 0x0506, 0x050a, 0x050e, 0x0512,
0x0516, 0x051a, 0x051e, 0x0522, 0x0525, 0x0528, 0x052c, 0x0530,
0x0535, 0x0538, 0x053c, 0x0540, 0x0543, 0x0547, 0x054b, 0x054f,
0x0553, 0x0557, 0x055a, 0x055f, 0x0563, 0x0566, 0x056a, 0x056e,
0x0572, 0x0575, 0x0578, 0x057c, 0x057f, 0x0583, 0x0587, 0x058b,
0x058f, 0x0593, 0x0597, 0x059b, 0x059f, 0x05a3, 0x05a7, 0x05aa,
0x05ae, 0x05b2, 0x05b5, 0x05b8, 0x05bb, 0x05bf, 0x05c2, 0x05c7,
0x05cb, 0x05cf, 0x05d3, 0x05d7, 0x05db, 0x05df, 0x05e3, 0x05e7,
0x05eb, 0x05ef, 0x05f3, 0x05f8, 0x05fc, 0x0600, 0x0604, 0x0608,
0x060c, 0x0611, 0x0615, 0x0619, 0x061d, 0x0621, 0x0626, 0x062a,
0x062e, 0x0632, 0x0636, 0x063a, 0x063d, 0x0641, 0x0645, 0x0649,
0x064d, 0x0651, 0x0655, 0x0659, 0x065d, 0x0661, 0x0665, 0x0669,
0x066b, 0x066d, 0x066f, 0x0671, 0x0673
};
static const short off_len[] = {
0x0000, 0x002b, 0x002b, 0x0024, 0x004f, 0x0015, 0x0064, 0x0017,
0x007b, 0x0016, 0x0091, 0x0015, 0x00a6, 0x0024, 0x00ca, 0x002f,
0x00f9, 0x002b, 0x0124, 0x0073, 0x0197, 0x0006
};
*offsets = offsets_table + off_len[n * 2 + 0];
return off_len[n * 2 + 1];
}
static const char namegen_argz[] = {
'b', 0 , 'b', 'l', 0 , 'b', 'r', 0 , 'c', 0 , 'c', 'h', 0 , 'c', 'h',
'r', 0 , 'c', 'l', 0 , 'c', 'r', 0 , 'd', 0 , 'd', 'r', 0 , 'f', 0 ,
'g', 0 , 'h', 0 , 'j', 0 , 'k', 0 , 'l', 0 , 'l', 'l', 0 , 'm', 0 ,
'n', 0 , 'p', 0 , 'p', 'h', 0 , 'q', 'u', 0 , 'r', 0 , 'r', 'h', 0 ,
's', 0 , 's', 'c', 'h', 0 , 's', 'h', 0 , 's', 'l', 0 , 's', 'm', 0 ,
's', 'n', 0 , 's', 't', 0 , 's', 't', 'r', 0 , 's', 'w', 0 , 't', 0 ,
't', 'h', 0 , 't', 'h', 'r', 0 , 't', 'r', 0 , 'v', 0 , 'w', 0 , 'w',
'h', 0 , 'y', 0 , 'z', 0 , 'z', 'h', 0 , 'b', 0 , 'c', 0 , 'c', 'h',
0 , 'c', 'k', 0 , 'd', 0 , 'f', 0 , 'g', 0 , 'g', 'h', 0 , 'h', 0 ,
'k', 0 , 'l', 0 , 'l', 'd', 0 , 'l', 'l', 0 , 'l', 't', 0 , 'm', 0 ,
'n', 0 , 'n', 'd', 0 , 'n', 'n', 0 , 'n', 't', 0 , 'p', 0 , 'p', 'h',
0 , 'q', 0 , 'r', 0 , 'r', 'd', 0 , 'r', 'r', 0 , 'r', 't', 0 , 's',
0 , 's', 'h', 0 , 's', 's', 0 , 's', 't', 0 , 't', 0 , 't', 'h', 0 ,
'v', 0 , 'w', 0 , 'y', 0 , 'z', 0 , 'b', 0 , 'b', 'l', 0 , 'b', 'r',
0 , 'c', 'l', 0 , 'd', 0 , 'f', 0 , 'f', 'l', 0 , 'f', 'r', 0 , 'g',
0 , 'g', 'h', 0 , 'g', 'l', 0 , 'g', 'r', 0 , 'h', 0 , 'j', 0 , 'k',
0 , 'k', 'l', 0 , 'm', 0 , 'n', 0 , 'p', 0 , 't', 'h', 0 , 'w', 0 ,
'b', 'o', 'o', 0 , 'b', 'u', 'n', 'c', 'h', 0 , 'b', 'u', 'n', 'n', 'y',
0 , 'c', 'a', 'k', 'e', 0 , 'c', 'a', 'k', 'e', 's', 0 , 'c', 'u', 't',
'e', 0 , 'd', 'a', 'r', 'l', 'i', 'n', 'g', 0 , 'd', 'u', 'm', 'p', 'l',
'i', 'n', 'g', 0 , 'd', 'u', 'm', 'p', 'l', 'i', 'n', 'g', 's', 0 , 'f',
'a', 'c', 'e', 0 , 'f', 'o', 'o', 'f', 0 , 'g', 'o', 'o', 0 , 'h', 'e',
'a', 'd', 0 , 'k', 'i', 'n', 0 , 'k', 'i', 'n', 's', 0 , 'l', 'i', 'p',
's', 0 , 'l', 'o', 'v', 'e', 0 , 'm', 'u', 's', 'h', 0 , 'p', 'i', 'e',
0 , 'p', 'o', 'o', 0 , 'p', 'o', 'o', 'h', 0 , 'p', 'o', 'o', 'k', 0 ,
'p', 'u', 'm', 's', 0 , 'a', 0 , 'e', 0 , 'i', 0 , 'o', 0 , 'u', 0 ,
'y', 0 , 'a', 'e', 0 , 'a', 'i', 0 , 'a', 'u', 0 , 'a', 'y', 0 , 'e',
'a', 0 , 'e', 'e', 0 , 'e', 'i', 0 , 'e', 'u', 0 , 'e', 'y', 0 , 'i',
'a', 0 , 'i', 'e', 0 , 'o', 'e', 0 , 'o', 'i', 0 , 'o', 'o', 0 , 'o',
'u', 0 , 'u', 'i', 0 , 'b', 0 , 'c', 0 , 'd', 0 , 'f', 0 , 'g', 0 ,
'h', 0 , 'j', 0 , 'k', 0 , 'l', 0 , 'm', 0 , 'n', 0 , 'p', 0 , 'q',
0 , 'r', 0 , 's', 0 , 't', 0 , 'v', 0 , 'w', 0 , 'x', 0 , 'y', 0 ,
'z', 0 , 'e', 'l', 'c', 'h', 0 , 'i', 'd', 'i', 'o', 't', 0 , 'o', 'b',
0 , 'o', 'g', 0 , 'o', 'k', 0 , 'o', 'l', 'p', 'h', 0 , 'o', 'l', 't',
0 , 'o', 'm', 'p', 'h', 0 , 'o', 'n', 'g', 0 , 'o', 'n', 'k', 0 , 'o',
'o', 0 , 'o', 'o', 'b', 0 , 'o', 'o', 'f', 0 , 'o', 'o', 'g', 0 , 'o',
'o', 'k', 0 , 'o', 'o', 'z', 0 , 'o', 'r', 'g', 0 , 'o', 'r', 'k', 0 ,
'o', 'r', 'm', 0 , 'o', 'r', 'o', 'n', 0 , 'u', 'b', 0 , 'u', 'c', 'k',
0 , 'u', 'g', 0 , 'u', 'l', 'f', 0 , 'u', 'l', 't', 0 , 'u', 'm', 0 ,
'u', 'm', 'b', 0 , 'u', 'm', 'p', 0 , 'u', 'm', 'p', 'h', 0 , 'u', 'n',
0 , 'u', 'n', 'b', 0 , 'u', 'n', 'g', 0 , 'u', 'n', 'k', 0 , 'u', 'n',
'p', 'h', 0 , 'u', 'n', 't', 0 , 'u', 'z', 'z', 0 , 'a', 'i', 'r', 0 ,
'a', 'n', 'k', 'l', 'e', 0 , 'b', 'a', 'l', 'l', 0 , 'b', 'e', 'e', 'f',
0 , 'b', 'o', 'n', 'e', 0 , 'b', 'u', 'm', 0 , 'b', 'u', 'm', 'b', 'l',
'e', 0 , 'b', 'u', 'm', 'p', 0 , 'c', 'h', 'e', 'e', 's', 'e', 0 , 'c',
'l', 'o', 'd', 0 , 'c', 'l', 'o', 't', 0 , 'c', 'l', 'o', 'w', 'n', 0 ,
'c', 'o', 'r', 'n', 0 , 'd', 'i', 'p', 0 , 'd', 'o', 'l', 't', 0 , 'd',
'o', 'o', 'f', 0 , 'd', 'o', 'r', 'k', 0 , 'd', 'u', 'm', 'b', 0 , 'f',
'a', 'c', 'e', 0 , 'f', 'i', 'n', 'g', 'e', 'r', 0 , 'f', 'o', 'o', 't',
0 , 'f', 'u', 'm', 'b', 'l', 'e', 0 , 'g', 'o', 'o', 'f', 0 , 'g', 'r',
'u', 'm', 'b', 'l', 'e', 0 , 'h', 'e', 'a', 'd', 0 , 'k', 'n', 'o', 'c',
'k', 0 , 'k', 'n', 'o', 'c', 'k', 'e', 'r', 0 , 'k', 'n', 'u', 'c', 'k',
'l', 'e', 0 , 'l', 'o', 'a', 'f', 0 , 'l', 'u', 'm', 'p', 0 , 'l', 'u',
'n', 'k', 0 , 'm', 'e', 'a', 't', 0 , 'm', 'u', 'c', 'k', 0 , 'm', 'u',
'n', 'c', 'h', 0 , 'n', 'i', 't', 0 , 'n', 'u', 'm', 'b', 0 , 'p', 'i',
'n', 0 , 'p', 'u', 'f', 'f', 0 , 's', 'k', 'u', 'l', 'l', 0 , 's', 'n',
'a', 'r', 'k', 0 , 's', 'n', 'e', 'e', 'z', 'e', 0 , 't', 'h', 'i', 'm',
'b', 'l', 'e', 0 , 't', 'w', 'e', 'r', 'p', 0 , 't', 'w', 'i', 't', 0 ,
'w', 'a', 'd', 0 , 'w', 'i', 'm', 'p', 0 , 'w', 'i', 'p', 'e', 0 , 'b',
'a', 'b', 'y', 0 , 'b', 'o', 'o', 'b', 'l', 'e', 0 , 'b', 'u', 'n', 'k',
'e', 'r', 0 , 'c', 'u', 'd', 'd', 'l', 'e', 0 , 'c', 'u', 'd', 'd', 'l',
'y', 0 , 'c', 'u', 't', 'i', 'e', 0 , 'd', 'o', 'o', 'd', 'l', 'e', 0 ,
'f', 'o', 'o', 'f', 'i', 'e', 0 , 'g', 'o', 'o', 'b', 'l', 'e', 0 , 'h',
'o', 'n', 'e', 'y', 0 , 'k', 'i', 's', 's', 'i', 'e', 0 , 'l', 'o', 'v',
'e', 'r', 0 , 'l', 'o', 'v', 'e', 'y', 0 , 'm', 'o', 'o', 'f', 'i', 'e',
0 , 'm', 'o', 'o', 'g', 'l', 'i', 'e', 0 , 'm', 'o', 'o', 'p', 'i', 'e',
0 , 'm', 'o', 'o', 'p', 's', 'i', 'e', 0 , 'n', 'o', 'o', 'k', 'u', 'm',
0 , 'p', 'o', 'o', 'c', 'h', 'i', 'e', 0 , 'p', 'o', 'o', 'f', 0 , 'p',
'o', 'o', 'f', 'i', 'e', 0 , 'p', 'o', 'o', 'k', 'i', 'e', 0 , 's', 'c',
'h', 'm', 'o', 'o', 'p', 'i', 'e', 0 , 's', 'c', 'h', 'n', 'o', 'o', 'g',
'l', 'e', 0 , 's', 'c', 'h', 'n', 'o', 'o', 'k', 'i', 'e', 0 , 's', 'c',
'h', 'n', 'o', 'o', 'k', 'u', 'm', 0 , 's', 'm', 'o', 'o', 'c', 'h', 0 ,
's', 'm', 'o', 'o', 'c', 'h', 'i', 'e', 0 , 's', 'm', 'o', 'o', 's', 'h',
0 , 's', 'n', 'o', 'o', 'g', 'l', 'e', 0 , 's', 'n', 'o', 'o', 'g', 'y',
0 , 's', 'n', 'o', 'o', 'k', 'i', 'e', 0 , 's', 'n', 'o', 'o', 'k', 'u',
'm', 0 , 's', 'n', 'u', 'g', 'g', 'y', 0 , 's', 'w', 'e', 'e', 't', 'i',
'e', 0 , 'w', 'o', 'o', 'g', 'l', 'e', 0 , 'w', 'o', 'o', 'g', 'y', 0 ,
'w', 'o', 'o', 'k', 'i', 'e', 0 , 'w', 'o', 'o', 'k', 'u', 'm', 0 , 'w',
'u', 'd', 'd', 'l', 'e', 0 , 'w', 'u', 'd', 'd', 'l', 'y', 0 , 'w', 'u',
'g', 'g', 'y', 0 , 'w', 'u', 'n', 'n', 'y', 0 , 'a', 'c', 'h', 0 , 'a',
'c', 'k', 0 , 'a', 'd', 0 , 'a', 'g', 'e', 0 , 'a', 'l', 'd', 0 , 'a',
'l', 'e', 0 , 'a', 'n', 0 , 'a', 'n', 'g', 0 , 'a', 'r', 0 , 'a', 'r',
'd', 0 , 'a', 's', 0 , 'a', 's', 'h', 0 , 'a', 't', 0 , 'a', 't', 'h',
0 , 'a', 'u', 'g', 'h', 0 , 'a', 'w', 0 , 'b', 'a', 'n', 0 , 'b', 'e',
'l', 0 , 'b', 'u', 'r', 0 , 'c', 'e', 'r', 0 , 'c', 'h', 'a', 0 , 'c',
'h', 'e', 0 , 'd', 'a', 'n', 0 , 'd', 'a', 'r', 0 , 'd', 'e', 'l', 0 ,
'd', 'e', 'n', 0 , 'd', 'r', 'a', 0 , 'd', 'y', 'n', 0 , 'e', 'c', 'h',
0 , 'e', 'l', 'd', 0 , 'e', 'l', 'm', 0 , 'e', 'm', 0 , 'e', 'n', 0 ,
'e', 'n', 'd', 0 , 'e', 'n', 'g', 0 , 'e', 'n', 't', 'h', 0 , 'e', 'r',
0 , 'e', 's', 's', 0 , 'e', 's', 't', 0 , 'e', 't', 0 , 'g', 'a', 'r',
0 , 'g', 'h', 'a', 0 , 'h', 'a', 't', 0 , 'h', 'i', 'n', 0 , 'h', 'o',
'n', 0 , 'i', 'a', 0 , 'i', 'g', 'h', 't', 0 , 'i', 'l', 'd', 0 , 'i',
'm', 0 , 'i', 'n', 'a', 0 , 'i', 'n', 'e', 0 , 'i', 'n', 'g', 0 , 'i',
'r', 0 , 'i', 's', 0 , 'i', 's', 's', 0 , 'i', 't', 0 , 'k', 'a', 'l',
0 , 'k', 'e', 'l', 0 , 'k', 'i', 'm', 0 , 'k', 'i', 'n', 0 , 'l', 'e',
'r', 0 , 'l', 'o', 'r', 0 , 'l', 'y', 'e', 0 , 'm', 'o', 'r', 0 , 'm',
'o', 's', 0 , 'n', 'a', 'l', 0 , 'n', 'y', 0 , 'n', 'y', 's', 0 , 'o',
'l', 'd', 0 , 'o', 'm', 0 , 'o', 'n', 0 , 'o', 'r', 0 , 'o', 'r', 'm',
0 , 'o', 's', 0 , 'o', 'u', 'g', 'h', 0 , 'p', 'e', 'r', 0 , 'p', 'o',
'l', 0 , 'q', 'u', 'a', 0 , 'q', 'u', 'e', 0 , 'r', 'a', 'd', 0 , 'r',
'a', 'k', 0 , 'r', 'a', 'n', 0 , 'r', 'a', 'y', 0 , 'r', 'i', 'l', 0 ,
'r', 'i', 's', 0 , 'r', 'o', 'd', 0 , 'r', 'o', 't', 'h', 0 , 'r', 'y',
'n', 0 , 's', 'a', 'm', 0 , 's', 'a', 'y', 0 , 's', 'e', 'r', 0 , 's',
'h', 'y', 0 , 's', 'k', 'e', 'l', 0 , 's', 'u', 'l', 0 , 't', 'a', 'i',
0 , 't', 'a', 'n', 0 , 't', 'a', 's', 0 , 't', 'h', 'e', 'r', 0 , 't',
'i', 'a', 0 , 't', 'i', 'n', 0 , 't', 'o', 'n', 0 , 't', 'o', 'r', 0 ,
't', 'u', 'r', 0 , 'u', 'm', 0 , 'u', 'n', 'd', 0 , 'u', 'n', 't', 0 ,
'u', 'r', 'n', 0 , 'u', 's', 'k', 0 , 'u', 's', 't', 0 , 'v', 'e', 'r',
0 , 'v', 'e', 's', 0 , 'v', 'o', 'r', 0 , 'w', 'a', 'r', 0 , 'w', 'o',
'r', 0 , 'y', 'e', 'r', 0 , 'a', 0 , 'e', 0 , 'i', 0 , 'o', 0 , 'u',
0 , 'y', 0
};
static unsigned long
namegen_rand32(unsigned long *s)
{
unsigned long x = *s;
x ^= x << 13;
x ^= (x & 0xffffffffUL) >> 17;
x ^= x << 5;
return (*s = x) & 0xffffffffUL;
}
static int
namegen_cap(int c, int capitalize)
{
return capitalize && c >= 'a' && c <= 'z' ? c & ~0x20 : c;
}
/* Copy a random substitution for template C into P, but only before E.
*/
static char *
namegen_copy(char *p, char *e, int c, unsigned long *seed, int capitalize)
{
int n = namegen_special(c);
if (n == -1) {
if (p != e)
*p++ = namegen_cap(c, capitalize);
} else {
const short *offsets;
int count = namegen_offsets(n, &offsets);
int select = namegen_rand32(seed) % count;
const char *s = namegen_argz + offsets[select];
while (*s) {
int r = *s++;
if (p != e)
*p++ = namegen_cap(r, capitalize);
capitalize = 0;
}
}
return p;
}
static int
namegen(char *dst, unsigned long len, const char *pattern, unsigned long *seed)
{
int depth = 0; /* Current nesting depth */
char *p = dst; /* Current output pointer */
char *e = dst + len; /* Maxiumum output pointer */
int capitalize = 0; /* Capitalize next item */
/* Stacks */
char *reset[NAMEGEN_MAX_DEPTH]; /* Reset pointer (undo generate) */
unsigned long n[NAMEGEN_MAX_DEPTH]; /* Number of groups */
unsigned long silent = 0; /* Actively generating? */
unsigned long literal = 0; /* Current "mode" */
unsigned long capstack = 0; /* Initial capitalization state */
n[0] = 1;
reset[0] = dst;
for (; *pattern; pattern++) {
unsigned long bit; /* Bit for current depth */
int c = *pattern;
switch (c) {
case '<':
if (++depth == NAMEGEN_MAX_DEPTH) {
*dst = 0;
return NAMEGEN_TOO_DEEP;
}
bit = 1UL << depth;
n[depth] = 1;
reset[depth] = p;
literal &= ~bit;
silent &= ~bit;
silent |= (silent << 1) & bit;
capstack &= ~bit;
capstack |= (unsigned long)capitalize << depth;
break;
case '(':
if (++depth == NAMEGEN_MAX_DEPTH) {
*dst = 0;
return NAMEGEN_TOO_DEEP;
}
bit = 1UL << depth;
n[depth] = 1;
reset[depth] = p;
literal |= bit;
silent &= ~bit;
silent |= (silent << 1) & bit;
capstack &= ~bit;
capstack |= (unsigned long)capitalize << depth;
break;
case '>':
if (depth == 0) {
*dst = 0;
return NAMEGEN_INVALID;
}
bit = 1UL << depth--;
if (literal & bit) {
*dst = 0;
return NAMEGEN_INVALID;
}
break;
case ')':
if (depth == 0) {
*dst = 0;
return NAMEGEN_INVALID;
}
bit = 1UL << depth--;
if (!(literal & bit)) {
*dst = 0;
return NAMEGEN_INVALID;
}
break;
case '|':
bit = 1UL << depth;
/* Stay silent if parent group is silent */
if (!(silent & (bit >> 1))) {
if (namegen_rand32(seed) < 0xffffffffUL / ++n[depth]) {
/* Switch to this option */
p = reset[depth];
silent &= ~bit;
capitalize = !!(capstack & bit);
} else {
/* Skip this option */
silent |= bit;
}
}
break;
case '!':
capitalize = 1;
break;
default:
bit = 1UL << depth;
if (!(silent & bit)) {
if (literal & bit) {
/* Copy value literally */
if (p != e)
*p++ = namegen_cap(c, capitalize);
} else {
/* Copy a substitution */
p = namegen_copy(p, e, c, seed, capitalize);
}
}
capitalize = 0;
}
}
if (depth) {
*dst = 0;
return NAMEGEN_INVALID;
} else if (p == e) {
p[-1] = 0;
return NAMEGEN_TRUNCATED;
} else {
*p = 0;
return NAMEGEN_SUCCESS;
}
}
#endif
Makefile
.POSIX:
CC = cc
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3 -fpie
LDFLAGS = -pie
namegen: example.c namegen.h
$(CC) $(LDFLAGS) $(CFLAGS) -o $@ example.c $(LDLIBS)
test: check
check: namegen
@./namegen --test
clean:
rm -f namegen
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> cd src/
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy/src> make
cc -pie -ansi -pedantic -Wall -Wextra -O3 -g3 -fpie -o namegen example.c
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy/src> make check
PASS alternation
PASS substitution
PASS closing bracket
PASS closing paren
PASS mismatch paren
PASS mismatch bracket
PASS unclosed bracket
PASS unclosed paren
PASS deep nesting
PASS truncation 1
PASS truncation 2
PASS capitalization 1
PASS capitalization 2
PASS capitalization 3
PASS empty
All 15 tests passed
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy/src>
Для начала организуем стандартный autotools-проект:
Makefile.am
SUBDIRS = src
demo: all
./src/namegen '!BVCs !DdM'
configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([namegen], [0.0.1], [george@altlinux.org])
AM_INIT_AUTOMAKE([foreign])
AC_CONFIG_SRCDIR([src/example.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stddef.h stdlib.h string.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_TYPES([ptrdiff_t])
# Checks for library functions.
AC_FUNC_MALLOC
AC_CHECK_FUNCS([memset strchr])
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
Makefile -> src/Makefile.in
.POSIX:
CC = cc
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3 -fpie
LDFLAGS = -pie
all: namegen
namegen: example.c namegen.h
$(CC) $(LDFLAGS) $(CFLAGS) -o $@ example.c $(LDLIBS)
test: check
check: namegen
@./namegen --test
clean:
rm -f namegen
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> tree
.
├── configure.ac
├── Makefile.am
└── src
├── example.c
├── Makefile.in
├── namegen
├── namegen.h
└── tablegen.py
2 directories, 7 files
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> autoreconf -fisv
<...>
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> ./configure
<...>
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> make demo
make all-recursive
<...>
./src/namegen '!BVCs !DdM'
Thestit Thulfboo
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> make demo
make all-recursive
<...>
./src/namegen '!BVCs !DdM'
Craiwar Gongpie
Вынесем огромные таблицы из заголовочного файла в генераты
namegen.h
/* Fantasy name generator ANSI C header library
* This is free and unencumbered software released into the public domain.
*
* The compile() function creates a name generator based on an input
* pattern. The letters s, v, V, c, B, C, i, m, M, D, and d represent
* different types of random replacements. Everything else is emitted
* literally.
*
* s - generic syllable
* v - vowel
* V - vowel or vowel combination
* c - consonant
* B - consonant or consonant combination suitable for beginning a word
* C - consonant or consonant combination suitable anywhere in a word
* i - insult
* m - mushy name
* M - mushy name ending
* D - consonant suited for a stupid person's name
* d - syllable suited for a stupid person's name (begins with a vowel)
*
* All characters between parenthesis () are emitted literally. For
* example, the pattern "s(dim)", emits a random generic syllable
* followed by "dim".
*
* Characters between angle brackets <> emit patterns from the table
* above. Imagine the entire pattern is wrapped in one of these.
*
* In both types of groupings, a vertical bar | denotes a random choice.
* Empty groups are allowed. For example, "(foo|bar)" emits either "foo"
* or "bar". The pattern "<c|v|>" emits a constant, vowel, or nothing at
* all.
*
* An exclamation point ! means to capitalize the component that follows
* it. For example, "!(foo)" will emit "Foo" and "v!s" will emit a
* lowercase vowel followed by a capitalized syllable, like "eRod".
*
* This library is based on the RinkWorks Fantasy Name Generator.
* http://www.rinkworks.com/namegen/
*/
#ifndef NAMEGEN_H
#define NAMEGEN_H
#define NAMEGEN_MAX_DEPTH 32 /* Cannot exceed bits in a long */
/* Return codes */
#define NAMEGEN_SUCCESS 0
#define NAMEGEN_TRUNCATED 1 /* Output was truncated */
#define NAMEGEN_INVALID 2 /* Pattern is invalid */
#define NAMEGEN_TOO_DEEP 3 /* Pattern exceeds maximum nesting depth */
#include "const.h"
/* Generate a name into DST of LEN bytes from PATTERN and using SEED.
*
* The length must be non-zero. For best results, the lower 32 bits of
* the seed should be thoroughly initialized. A particular seed will
* produce the same results on all platforms.
*
* The return value is one of the above codes, indicating success or
* that something went wrong. Truncation occurs when DST was too short.
* Pattern is validated even when the output has been truncated.
*/
static int
namegen(char *dst, unsigned long len, const char *pattern, unsigned long *seed);
/* Implementation */
/* Rather than compile the pattern into some internal representation,
* the name is generated directly from the pattern in a single pass
* using reservoir sampling. If an alternate option is selected, the
* output pointer is reset to "undo" the output for the previous group.
* This means the output buffer may be written beyond the final output
* length (but never beyond the buffer length).
*
* The substitution templates are stored in an efficient, packed form
* that contains no pointers. This is to avoid cluttering up the
* relocation table, but without any additional run-time overhead.
*/
/* Return offsets table offset for C, or -1 if not special.
* The return value is suitable for namegen_offsets().
*/
static int
namegen_special(int c)
{
if (c >= 0 && c < 128)
return special[c];
return -1;
}
/* Return offsets and number of offsets for special N.
* Offsets point into namegen_argz.
*/
static int
namegen_offsets(int n, const short **offsets)
{
*offsets = offsets_table + off_len[n * 2 + 0];
return off_len[n * 2 + 1];
}
static unsigned long
namegen_rand32(unsigned long *s)
{
unsigned long x = *s;
x ^= x << 13;
x ^= (x & 0xffffffffUL) >> 17;
x ^= x << 5;
return (*s = x) & 0xffffffffUL;
}
static int
namegen_cap(int c, int capitalize)
{
return capitalize && c >= 'a' && c <= 'z' ? c & ~0x20 : c;
}
/* Copy a random substitution for template C into P, but only before E.
*/
static char *
namegen_copy(char *p, char *e, int c, unsigned long *seed, int capitalize)
{
int n = namegen_special(c);
if (n == -1) {
if (p != e)
*p++ = namegen_cap(c, capitalize);
} else {
const short *offsets;
int count = namegen_offsets(n, &offsets);
int select = namegen_rand32(seed) % count;
const char *s = namegen_argz + offsets[select];
while (*s) {
int r = *s++;
if (p != e)
*p++ = namegen_cap(r, capitalize);
capitalize = 0;
}
}
return p;
}
static int
namegen(char *dst, unsigned long len, const char *pattern, unsigned long *seed)
{
int depth = 0; /* Current nesting depth */
char *p = dst; /* Current output pointer */
char *e = dst + len; /* Maxiumum output pointer */
int capitalize = 0; /* Capitalize next item */
/* Stacks */
char *reset[NAMEGEN_MAX_DEPTH]; /* Reset pointer (undo generate) */
unsigned long n[NAMEGEN_MAX_DEPTH]; /* Number of groups */
unsigned long silent = 0; /* Actively generating? */
unsigned long literal = 0; /* Current "mode" */
unsigned long capstack = 0; /* Initial capitalization state */
n[0] = 1;
reset[0] = dst;
for (; *pattern; pattern++) {
unsigned long bit; /* Bit for current depth */
int c = *pattern;
switch (c) {
case '<':
if (++depth == NAMEGEN_MAX_DEPTH) {
*dst = 0;
return NAMEGEN_TOO_DEEP;
}
bit = 1UL << depth;
n[depth] = 1;
reset[depth] = p;
literal &= ~bit;
silent &= ~bit;
silent |= (silent << 1) & bit;
capstack &= ~bit;
capstack |= (unsigned long)capitalize << depth;
break;
case '(':
if (++depth == NAMEGEN_MAX_DEPTH) {
*dst = 0;
return NAMEGEN_TOO_DEEP;
}
bit = 1UL << depth;
n[depth] = 1;
reset[depth] = p;
literal |= bit;
silent &= ~bit;
silent |= (silent << 1) & bit;
capstack &= ~bit;
capstack |= (unsigned long)capitalize << depth;
break;
case '>':
if (depth == 0) {
*dst = 0;
return NAMEGEN_INVALID;
}
bit = 1UL << depth--;
if (literal & bit) {
*dst = 0;
return NAMEGEN_INVALID;
}
break;
case ')':
if (depth == 0) {
*dst = 0;
return NAMEGEN_INVALID;
}
bit = 1UL << depth--;
if (!(literal & bit)) {
*dst = 0;
return NAMEGEN_INVALID;
}
break;
case '|':
bit = 1UL << depth;
/* Stay silent if parent group is silent */
if (!(silent & (bit >> 1))) {
if (namegen_rand32(seed) < 0xffffffffUL / ++n[depth]) {
/* Switch to this option */
p = reset[depth];
silent &= ~bit;
capitalize = !!(capstack & bit);
} else {
/* Skip this option */
silent |= bit;
}
}
break;
case '!':
capitalize = 1;
break;
default:
bit = 1UL << depth;
if (!(silent & bit)) {
if (literal & bit) {
/* Copy value literally */
if (p != e)
*p++ = namegen_cap(c, capitalize);
} else {
/* Copy a substitution */
p = namegen_copy(p, e, c, seed, capitalize);
}
}
capitalize = 0;
}
}
if (depth) {
*dst = 0;
return NAMEGEN_INVALID;
} else if (p == e) {
p[-1] = 0;
return NAMEGEN_TRUNCATED;
} else {
*p = 0;
return NAMEGEN_SUCCESS;
}
}
#endif
Makefile.in
.POSIX:
CC = cc
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3 -fpie
LDFLAGS = -pie
all: namegen
const.h: tablegen.py
python3 $< > $@
namegen: example.c namegen.h const.h
$(CC) $(LDFLAGS) $(CFLAGS) -o $@ example.c $(LDLIBS)
test: check
check: namegen
@./namegen --test
clean:
rm -f namegen
Сделаем automake для src
src/example.c -> src/namegen.c
configure.ac
@@ 4,7 4,7 @@
AC_PREREQ([2.69])
AC_INIT([namegen], [0.0.1], [george@altlinux.org])
AM_INIT_AUTOMAKE([foreign])
-AC_CONFIG_SRCDIR([src/example.c])
+AC_CONFIG_SRCDIR([src/namegen.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
src/Makefile.am
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3
bin_PROGRAMS=namegen
namegen.o: const.h
const.h: tablegen.py
python3 $< > $@
check: namegen
@./namegen --test
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy/src> tree
.
├── Makefile.am
├── namegen.c
├── namegen.h
└── tablegen.py
1 directory, 4 files
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy/src>
Начнём готовить тестирующую библиотеку. Пока что просто разобьём тесты и заголовочные данные из header-файла
src/Makefile.am
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3
bin_PROGRAMS=namegen
namegen_SOURCES=namegen.c libnamegen.c
libnamegen.c: const.h
const.h: tablegen.py
python3 $< > $@
check: namegen
@./namegen --test
src/libnamegen.c
/* Fantasy name generator ANSI C header library
* This is free and unencumbered software released into the public domain.
*
* The compile() function creates a name generator based on an input
* pattern. The letters s, v, V, c, B, C, i, m, M, D, and d represent
* different types of random replacements. Everything else is emitted
* literally.
*
* s - generic syllable
* v - vowel
* V - vowel or vowel combination
* c - consonant
* B - consonant or consonant combination suitable for beginning a word
* C - consonant or consonant combination suitable anywhere in a word
* i - insult
* m - mushy name
* M - mushy name ending
* D - consonant suited for a stupid person's name
* d - syllable suited for a stupid person's name (begins with a vowel)
*
* All characters between parenthesis () are emitted literally. For
* example, the pattern "s(dim)", emits a random generic syllable
* followed by "dim".
*
* Characters between angle brackets <> emit patterns from the table
* above. Imagine the entire pattern is wrapped in one of these.
*
* In both types of groupings, a vertical bar | denotes a random choice.
* Empty groups are allowed. For example, "(foo|bar)" emits either "foo"
* or "bar". The pattern "<c|v|>" emits a constant, vowel, or nothing at
* all.
*
* An exclamation point ! means to capitalize the component that follows
* it. For example, "!(foo)" will emit "Foo" and "v!s" will emit a
* lowercase vowel followed by a capitalized syllable, like "eRod".
*
* This library is based on the RinkWorks Fantasy Name Generator.
* http://www.rinkworks.com/namegen/
*/
#include "namegen.h"
#include "const.h"
/* Generate a name into DST of LEN bytes from PATTERN and using SEED.
*
* The length must be non-zero. For best results, the lower 32 bits of
* the seed should be thoroughly initialized. A particular seed will
* produce the same results on all platforms.
*
* The return value is one of the above codes, indicating success or
* that something went wrong. Truncation occurs when DST was too short.
* Pattern is validated even when the output has been truncated.
*/
/* Implementation */
/* Rather than compile the pattern into some internal representation,
* the name is generated directly from the pattern in a single pass
* using reservoir sampling. If an alternate option is selected, the
* output pointer is reset to "undo" the output for the previous group.
* This means the output buffer may be written beyond the final output
* length (but never beyond the buffer length).
*
* The substitution templates are stored in an efficient, packed form
* that contains no pointers. This is to avoid cluttering up the
* relocation table, but without any additional run-time overhead.
*/
/* Return offsets table offset for C, or -1 if not special.
* The return value is suitable for namegen_offsets().
*/
static int
namegen_special(int c)
{
if (c >= 0 && c < 128)
return special[c];
return -1;
}
/* Return offsets and number of offsets for special N.
* Offsets point into namegen_argz.
*/
static int
namegen_offsets(int n, const short **offsets)
{
*offsets = offsets_table + off_len[n * 2 + 0];
return off_len[n * 2 + 1];
}
static unsigned long
namegen_rand32(unsigned long *s)
{
unsigned long x = *s;
x ^= x << 13;
x ^= (x & 0xffffffffUL) >> 17;
x ^= x << 5;
return (*s = x) & 0xffffffffUL;
}
static int
namegen_cap(int c, int capitalize)
{
return capitalize && c >= 'a' && c <= 'z' ? c & ~0x20 : c;
}
/* Copy a random substitution for template C into P, but only before E.
*/
static char *
namegen_copy(char *p, char *e, int c, unsigned long *seed, int capitalize)
{
int n = namegen_special(c);
if (n == -1) {
if (p != e)
*p++ = namegen_cap(c, capitalize);
} else {
const short *offsets;
int count = namegen_offsets(n, &offsets);
int select = namegen_rand32(seed) % count;
const char *s = namegen_argz + offsets[select];
while (*s) {
int r = *s++;
if (p != e)
*p++ = namegen_cap(r, capitalize);
capitalize = 0;
}
}
return p;
}
int
namegen(char *dst, unsigned long len, const char *pattern, unsigned long *seed)
{
int depth = 0; /* Current nesting depth */
char *p = dst; /* Current output pointer */
char *e = dst + len; /* Maxiumum output pointer */
int capitalize = 0; /* Capitalize next item */
/* Stacks */
char *reset[NAMEGEN_MAX_DEPTH]; /* Reset pointer (undo generate) */
unsigned long n[NAMEGEN_MAX_DEPTH]; /* Number of groups */
unsigned long silent = 0; /* Actively generating? */
unsigned long literal = 0; /* Current "mode" */
unsigned long capstack = 0; /* Initial capitalization state */
n[0] = 1;
reset[0] = dst;
for (; *pattern; pattern++) {
unsigned long bit; /* Bit for current depth */
int c = *pattern;
switch (c) {
case '<':
if (++depth == NAMEGEN_MAX_DEPTH) {
*dst = 0;
return NAMEGEN_TOO_DEEP;
}
bit = 1UL << depth;
n[depth] = 1;
reset[depth] = p;
literal &= ~bit;
silent &= ~bit;
silent |= (silent << 1) & bit;
capstack &= ~bit;
capstack |= (unsigned long)capitalize << depth;
break;
case '(':
if (++depth == NAMEGEN_MAX_DEPTH) {
*dst = 0;
return NAMEGEN_TOO_DEEP;
}
bit = 1UL << depth;
n[depth] = 1;
reset[depth] = p;
literal |= bit;
silent &= ~bit;
silent |= (silent << 1) & bit;
capstack &= ~bit;
capstack |= (unsigned long)capitalize << depth;
break;
case '>':
if (depth == 0) {
*dst = 0;
return NAMEGEN_INVALID;
}
bit = 1UL << depth--;
if (literal & bit) {
*dst = 0;
return NAMEGEN_INVALID;
}
break;
case ')':
if (depth == 0) {
*dst = 0;
return NAMEGEN_INVALID;
}
bit = 1UL << depth--;
if (!(literal & bit)) {
*dst = 0;
return NAMEGEN_INVALID;
}
break;
case '|':
bit = 1UL << depth;
/* Stay silent if parent group is silent */
if (!(silent & (bit >> 1))) {
if (namegen_rand32(seed) < 0xffffffffUL / ++n[depth]) {
/* Switch to this option */
p = reset[depth];
silent &= ~bit;
capitalize = !!(capstack & bit);
} else {
/* Skip this option */
silent |= bit;
}
}
break;
case '!':
capitalize = 1;
break;
default:
bit = 1UL << depth;
if (!(silent & bit)) {
if (literal & bit) {
/* Copy value literally */
if (p != e)
*p++ = namegen_cap(c, capitalize);
} else {
/* Copy a substitution */
p = namegen_copy(p, e, c, seed, capitalize);
}
}
capitalize = 0;
}
}
if (depth) {
*dst = 0;
return NAMEGEN_INVALID;
} else if (p == e) {
p[-1] = 0;
return NAMEGEN_TRUNCATED;
} else {
*p = 0;
return NAMEGEN_SUCCESS;
}
}
src/namegen.h
#ifndef NAMEGEN_H
#define NAMEGEN_H
#define NAMEGEN_MAX_DEPTH 32 /* Cannot exceed bits in a long */
/* Return codes */
#define NAMEGEN_SUCCESS 0
#define NAMEGEN_TRUNCATED 1 /* Output was truncated */
#define NAMEGEN_INVALID 2 /* Pattern is invalid */
#define NAMEGEN_TOO_DEEP 3 /* Pattern exceeds maximum nesting depth */
int namegen(char *, unsigned long, const char *, unsigned long *);
#endif
src/namegen.c
@@ 1,3 1,4 @@
+#include "config.h"
#include "namegen.h"
#include <time.h>
#include <stdio.h>
Теперь непосредственно подключаем в проект libtool - из libnamegen.c теперь собирается динамическая библиотека, configure.ac теперь использует libtool (дополнительно мы запрещаем сборку статической библиотеки, она нам не нужна).
src/Makefile.am теперь настроен на использование .la-файлов и, как мы уже видели в начале, генерируемый в src/namegen теперь не бинарник, а сценарий на shell, автоматически притягивающий LD_PRELOAD для запуска, а библиотека и бинарник собираются в src/.libs/
configure.ac
@@ 4,6 4,7 @@
AC_PREREQ([2.69])
AC_INIT([namegen], [0.0.1], [george@altlinux.org])
AM_INIT_AUTOMAKE([foreign])
+LT_INIT([disable-static])
AC_CONFIG_SRCDIR([src/namegen.c])
AC_CONFIG_HEADERS([config.h])
src/Makefile.am
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3
lib_LTLIBRARIES = libnamegen.la
bin_PROGRAMS = namegen
namegen_LDADD = libnamegen.la
libnamegen.c: const.h
const.h: tablegen.py
python3 $< > $@
check: namegen
@./namegen --test
Заметим, что всё, что мы сделали в начальном примере работы с libtool, уложилось в буквально три строчки Makefile. Действительно, это сильно упрощает работу по сборке библиотеки
Добавим теперь простейший тест, проверяющий подключение библиотеки
Makefile.am
@@ 1,4 1,4 @@
-SUBDIRS = src
+SUBDIRS = src tests
demo: all
./src/namegen '!BVCs !DdM'
configure.ac
@@ 24,5 24,6 @@ AC_FUNC_MALLOC
AC_CHECK_FUNCS([memset strchr])
AC_CONFIG_FILES([Makefile
+ tests/Makefile
src/Makefile])
AC_OUTPUT
tests/Makefile.am
TESTS = dummy
bin_PROGRAMS = dummy
AM_CFLAGS = -I$(top_builddir)/src
LDADD = $(top_builddir)/src/libnamegen.la
tests/dummy.c
#include <stdio.h>
#include <assert.h>
#include "namegen.h"
int main(int argc, char *argv[]) {
assert(namegen != NULL);
return 0;
}
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> make check
Making check in src
make[1]: Entering directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/src'
PASS alternation
PASS substitution
PASS closing bracket
PASS closing paren
PASS mismatch paren
PASS mismatch bracket
PASS unclosed bracket
PASS unclosed paren
PASS deep nesting
PASS truncation 1
PASS truncation 2
PASS capitalization 1
PASS capitalization 2
PASS capitalization 3
PASS empty
All 15 tests passed
make[1]: Leaving directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/src'
Making check in tests
make[1]: Entering directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/tests'
make check-TESTS
make[2]: Entering directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/tests'
make[3]: Entering directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/tests'
PASS: dummy
============================================================================
Testsuite summary for namegen 0.0.1
============================================================================
# TOTAL: 1
# PASS: 1
# SKIP: 0
# XFAIL: 0
# FAIL: 0
# XPASS: 0
# ERROR: 0
============================================================================
make[3]: Leaving directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/tests'
make[2]: Leaving directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/tests'
make[1]: Leaving directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy/tests'
make[1]: Entering directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy'
make[1]: Leaving directory '/home/user/LinuxAppDev/10_LibTesting/Lection/fantasy'
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy> ^C
user@localhost:~/LinuxAppDev/10_LibTesting/Lection/fantasy>
Теперь со встроенных make-тестов, наконец, переходим на check: добавим модуль check в конфиг, а наш тестовый файл перепишем.
В нём укажем макрос START_TEST, описывающий функцию для нашего фреймворка. В main мы указываем suite, case и runner, над которыми опишем логику обработки теста
configure.ac
@@ 11,6 11,9 @@ AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
+# Joint pkgconfig library/include check and variable definition.
+PKG_CHECK_MODULES([CHECK],[check])
+
# Checks for libraries.
# Checks for header files.
tests/Makefile.am
TESTS = dummy
check_PROGRAMS = dummy
AM_CFLAGS = -I$(top_builddir)/src @CHECK_CFLAGS@
LDADD = $(top_builddir)/src/libnamegen.la @CHECK_LIBS@
tests/dummy.c
#include <stdio.h>
#include <assert.h>
#include <check.h>
#include "namegen.h"
START_TEST(test_dummy) {
assert(namegen != NULL);
}
END_TEST
int main(int argc, char *argv[]) {
Suite *suite = suite_create("Dummy"); // создаём suite
TCase *testcase = tcase_create("Dummy"); // создаём case
SRunner *runner = srunner_create(suite); // для сьюта делаем runner
int ret;
suite_add_tcase(suite, testcase); // добавляем case в suite
tcase_add_test(testcase, test_dummy); // добавляем test в case
srunner_run_all(runner, CK_ENV); // запускаем все сьюты
ret = srunner_ntests_failed(runner); // собираем информацию о тестах
srunner_free(runner);
return ret != 0;
}
Как мы видим, для описания теста нам нужно довольно много описаний, при этом тест очень шаблонный - проверка наличия данных в namegen. Заменим такой длинный тест на который генерат с помощью утилиты checkmk
tests/Makefile.am
TESTS = dummy
check_PROGRAMS = dummy
.ts.c:
checkmk $< > $@
AM_CFLAGS = -I$(top_builddir)/src @CHECK_CFLAGS@
LDADD = $(top_builddir)/src/libnamegen.la @CHECK_LIBS@
tests/dummy.ts
#include <check.h>
#include "namegen.h"
#test dummy
ck_assert_ptr_nonnull(namegen);
Теперь перенесём авторские тесты из namegen в тестировочные заготовки
src/Makefila.am
CFLAGS = -ansi -pedantic -Wall -Wextra -O3 -g3
lib_LTLIBRARIES = libnamegen.la
bin_PROGRAMS = namegen
namegen_LDADD = libnamegen.la
libnamegen.c: const.h
const.h: tablegen.py
python3 $< > $@
src/namegen.c
#include "config.h"
#include "namegen.h"
#include <time.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
static unsigned long
hash32(unsigned long a)
{
a = a & 0xffffffffUL;
a = (a ^ 61UL) ^ (a >> 16);
a = (a + (a << 3)) & 0xffffffffUL;
a = a ^ (a >> 4);
a = (a * 0x27d4eb2dUL) & 0xffffffffUL;
a = a ^ (a >> 15);
return a;
}
int
main(int argc, char **argv)
{
int i;
int count = 1;
FILE *urandom;
unsigned char randbuf[4];
unsigned long seed[] = {0x8af611acUL};
/* Parse command line arguments */
if (argc < 2 || argc > 3) {
printf("Usage: %s <pattern> [num]\n", argv[0]);
printf(" pattern template for names to generate\n");
printf(" num number of names to generate\n");
exit(EXIT_FAILURE);
} else if (argc == 3) {
count = atoi(argv[2]);
}
/* Shuffle up the seed a bit */
urandom = fopen("/dev/urandom", "rb");
if (urandom && fread(randbuf, sizeof(randbuf), 1, urandom)) {
unsigned long rnd =
(unsigned long)randbuf[0] << 0 |
(unsigned long)randbuf[1] << 8 |
(unsigned long)randbuf[2] << 16 |
(unsigned long)randbuf[3] << 24;
*seed ^= rnd;
fclose(urandom);
} else {
void *p = malloc(4UL * 1024 * 1024);
*seed ^= hash32(time(0)); /* Current time */
*seed ^= hash32((ptrdiff_t)main); /* ASLR entopy */
*seed ^= hash32((ptrdiff_t)seed); /* Stack gap entropy */
*seed ^= hash32((ptrdiff_t)p); /* Allocator entropy */
*seed ^= hash32((ptrdiff_t)malloc); /* C library ASLR */
free(p);
}
/* Generate some names */
for (i = 0; i < count; i++) {
char buf[256];
int r = namegen(buf, sizeof(buf), argv[1], seed);
if (r == NAMEGEN_INVALID) {
fprintf(stderr, "%s: invalid pattern\n", argv[0]);
exit(EXIT_FAILURE);
}
printf("%s\n", buf);
}
return 0;
}
tests/Makefile.am
TESTS = dummy upstream
check_PROGRAMS = dummy upstream
.ts.c:
checkmk $< > $@
AM_CFLAGS = -I$(top_builddir)/src @CHECK_CFLAGS@
LDADD = $(top_builddir)/src/libnamegen.la @CHECK_LIBS@
tests/upstream.ts
#include <check.h>
#include "namegen.h"
int r;
char buf[256];
int count_pass = 0;
int count_fail = 0;
unsigned long rng[] = {0xd22bfb3b};
#suite Tools
#tcase Alternation
#test alternation_test
int i;
int mismatch = 0;
int seen[3] = {0, 0, 0};
for (i = 0; i < 100; i++) {
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), "0|1|2", rng);
if (r != NAMEGEN_SUCCESS || buf[1] || !strchr("012", buf[0])) {
mismatch++;
} else {
seen[buf[0] - '0']++;
}
}
ck_assert_int_eq(mismatch, 0);
ck_assert_int_ne(seen[0], 0);
ck_assert_int_ne(seen[1], 0);
ck_assert_int_ne(seen[2], 0);
#tcase Substitution
#test substitution_test
int i;
int mismatch = 0;
int seen[6] = {0, 0, 0};
for (i = 0; i < 100; i++) {
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), "(v)v", rng);
if (r != NAMEGEN_SUCCESS || buf[0] != 'v' || !buf[1] || buf[2]) {
mismatch++;
} else {
char *p = "aeiouy";
char *s = strchr(p, buf[1]);
if (!s)
mismatch++;
else
seen[s - p]++;
}
}
for (i = 0; i < 6; i++)
mismatch += !seen[i];
ck_assert_int_eq(mismatch, 0);
#suite Errors
#tcase bracket
#test closing_bracket_test
r = namegen(buf, sizeof(buf), "a>", rng);
ck_assert_int_eq(r, NAMEGEN_INVALID);
#test mismatch_bracket_test
r = namegen(buf, sizeof(buf), "(a>", rng);
ck_assert_int_eq(r, NAMEGEN_INVALID);
#test unclosed_bracket_test
r = namegen(buf, sizeof(buf), "<a", rng);
ck_assert_int_eq(r, NAMEGEN_INVALID);
#tcase paren
#test closing_paren_test
r = namegen(buf, sizeof(buf), "a)", rng);
ck_assert_int_eq(r, NAMEGEN_INVALID);
#test mismatch_paren_test
r = namegen(buf, sizeof(buf), "<a)", rng);
ck_assert_int_eq(r, NAMEGEN_INVALID);
#test unclosed_paren_test
r = namegen(buf, sizeof(buf), "(a", rng);
ck_assert_int_eq(r, NAMEGEN_INVALID);
#suite Tools2
#tcase Deep_nesting
#test deep_nesting_test
char *p = "(((((<(((((((((((((((a)))))))))))))))>)))))";
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), p, rng);
ck_assert_int_eq(r, NAMEGEN_SUCCESS);
ck_assert_str_eq(buf, "a");
#tcase Truncation
#test truncation_1_test
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, 4, "(abcdefg)", rng);
ck_assert_int_eq(r, NAMEGEN_TRUNCATED);
ck_assert_str_eq(buf, "abc");
ck_assert_uint_eq(buf[4], 0x5a);
#test truncation_2_test
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, 2, "i", rng);
ck_assert_int_eq(r, NAMEGEN_TRUNCATED);
ck_assert_uint_ne(buf[0], 0);
ck_assert_uint_eq(buf[2], 0x5a);
#tcase Capitalization
#test capitalization_1_test
r = namegen(buf, sizeof(buf), "!(foo)", rng);
ck_assert_int_eq(r, NAMEGEN_SUCCESS);
ck_assert_str_eq(buf, "Foo");
#test capitalization_2_test
r = namegen(buf, sizeof(buf), "(!foo)", rng);
ck_assert_int_eq(r, NAMEGEN_SUCCESS);
ck_assert_str_eq(buf, "Foo");
#test capitalization_3_test
r = namegen(buf, sizeof(buf), "(f!oo)", rng);
ck_assert_int_eq(r, NAMEGEN_SUCCESS);
ck_assert_str_eq(buf, "fOo");
#tcase Empty
#test epmty_test
memset(buf, 0x5a, sizeof(buf));
r = namegen(buf, sizeof(buf), "", rng);
ck_assert_int_eq(r, NAMEGEN_SUCCESS);
ck_assert_uint_eq(buf[0], 0);
ck_assert_uint_eq(buf[1], 0x5a);
Всё работает, однако мы сталкиваемся с проблемой неинформативности и неразделяемости тестов. Как минимум, нам нужен тот самый параметр coverage, информирующий нас о покрываемости тестами нашей программы. Для этого добавим поддержку gcov, которая опишет нам результаты тестирования.
Makefile.am
SUBDIRS = src tests
demo: all
./src/namegen '!BVCs !DdM'
checklog: check
cat tests/*.log
gcov: check
$(MAKE) $(MAKEFLAGS) -C src gcov
configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([namegen], [0.0.1], [george@altlinux.org])
AM_INIT_AUTOMAKE([foreign])
LT_INIT([disable-static])
AC_CONFIG_SRCDIR([src/namegen.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Joint pkgconfig library/include tests
PKG_CHECK_MODULES([CHECK],[check])
# 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 test the test suite])],
[], [enable_gcov=no])
AM_CONDITIONAL([COND_GCOV],[test '!' "$enable_gcov" = no])
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stddef.h stdlib.h string.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_TYPES([ptrdiff_t])
# Checks for library functions.
AC_FUNC_MALLOC
AC_CHECK_FUNCS([memset strchr])
AC_CONFIG_FILES([Makefile
tests/Makefile
src/Makefile])
AC_OUTPUT
src/Makefile.am
CFLAGS = -ansi -pedantic -Wall -Wextra
if COND_GCOV
CFLAGS += -O0 -g -fprofile-arcs -ftest-coverage
else
CFLAGS += -O3 -g3
endif
lib_LTLIBRARIES = libnamegen.la
bin_PROGRAMS = namegen
namegen_LDADD = libnamegen.la
libnamegen.c: const.h
const.h: tablegen.py
python3 $< > $@
gcov:
gcov -o .libs libnamegen -t -k -b | egrep -v '[[:digit:]]:[ /][*]'
Домашнее задание
- Изучить выбранный вами фреймворк тестов для Си
- Например, check, autotools и пример из лекции
- Взять за основу Growable Memory Buffers for C99
- Превратить в библиотеку
- Функция в ней всего одна, остальное макросы, и переделывать это не надо
- Все макросы уезжают в .h-файл
- Приложение-пример можно не писать
- Тесты взять из авторского файла tests.c
- Тесты на память с setjmp() можно выкинуть.
- Оформить их сообразно правилам фреймворка в несколько отдельных тестов
- (необязательно) попробовать добиться как можно более полного покрытия (предельные тексты, например, с помощью prlimit)
- Сделать примитивную поддержку проверки покрытия (чтобы проценты показывало)
- Превратить в библиотеку