diff --git a/Makefile.am b/Makefile.am index 67ffc6b4..20544242 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = lib/libalpm src/util src/pacman scripts etc test/pacman test/util test/scripts +SUBDIRS = lib/libalpm src/util src/pacman scripts etc test/pacman test/util test/scripts test/alpm if WANT_DOC SUBDIRS += doc endif @@ -32,6 +32,7 @@ TESTS = test/scripts/parseopts_test.sh \ test/scripts/pacman-db-upgrade-v9.py \ test/util/vercmptest.sh include $(top_srcdir)/test/pacman/tests/TESTS +include $(top_srcdir)/test/alpm/tests/TESTS TEST_SUITE_LOG = test/test-suite.log TEST_EXTENSIONS = .py @@ -50,6 +51,10 @@ AM_PY_LOG_FLAGS = \ --ldconfig $(LDCONFIG) \ --bindir $(top_builddir)/src/pacman \ --bindir $(top_builddir)/scripts +T_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/build-aux/tap-driver.sh +T_LOG_COMPILER = $(top_srcdir)/test/alpm/ptrun +AM_T_LOG_FLAGS = -rc # create the pacman DB, cache, makepkg-template and system hook directories upon install install-data-local: diff --git a/configure.ac b/configure.ac index 57f068d3..514a8dd6 100644 --- a/configure.ac +++ b/configure.ac @@ -526,6 +526,8 @@ scripts/Makefile scripts/po/Makefile.in doc/Makefile etc/Makefile +test/alpm/Makefile +test/alpm/tests/Makefile test/pacman/Makefile test/pacman/tests/Makefile test/scripts/Makefile diff --git a/test/alpm/Makefile.am b/test/alpm/Makefile.am new file mode 100644 index 00000000..7f0a4ca0 --- /dev/null +++ b/test/alpm/Makefile.am @@ -0,0 +1,11 @@ +SUBDIRS = tests + +check_SCRIPTS = ptrun + +noinst_SCRIPTS = $(check_SCRIPTS) + +EXTRA_DIST = \ + README \ + $(check_SCRIPTS) + +# vim:set noet: diff --git a/test/alpm/README b/test/alpm/README new file mode 100644 index 00000000..3762545d --- /dev/null +++ b/test/alpm/README @@ -0,0 +1,108 @@ +Running Tests +============= + +Tests are normally run using `make check` from the project root. Individual +tests are located under 'tests/' and can be run directly. `ptrun` is a wrapper +script provided as a convenience for modifying how tests are run (using +fakeroot, fakechroot, valgrind, etc.). + +Requirements +------------ + +All requirements are optional unless otherwise noted. Tests that depend on any +missing requirements will be skipped. + +* pthreads +* fakechroot +* fakeroot + +Writing Tests +============= + +Libraries +--------- + +Tests should `#include "../alpmtest.h"`. This will bring in libalpm, +pactest.c, ptserve.c, tap.c, and stdio libraries. + +Test Naming +----------- + +Tests designed to check a particular function or data type under multiple +conditions should be named after the function or data type being tested (e.g. +`alpm_filelist.c`). Otherwise, tests should be named according to the +particular condition being tested, avoiding an `alpm` prefix if possible (e.g. +`cached_part_file.c`). + +Test Initialization +------------------- + +Any functions called during test setup that could fail should be wrapped in +`ASSERT`. + +Any calls that could fail should be wrapped in either an `ASSERT` or +a `tap_*` function. + +Do not rely on pactest.c default settings. If a test requires a particular +option, set it manually in the test. + +If a test program is being run in an environment where it is impossible for the +tests to properly run (e.g. due to insufficient permissions or missing +features) the test should be skipped using `tap_skip_all` and return 0. Tests +which should be able to run but fail to should bail out and return 99. In +particular, tests which rely on install scriptlets or ldconfig should check for +`chroot` permissions with the `CAN_CHROOT` macro and tests which rely on +setting file ownership should check `CAN_CHOWN` before running. + +Test Cleanup +------------ + +Tests should ensure that all memory is freed after a successful run. Memory +leaks are acceptable if the test fails, but any `pt_env_t` or `pt_serve_t` +resources must be freed regardless of test failure. The easiest way to +accomplish this is to register a `cleanup` function using `atexit`. + +Return Values +------------- + + 0 - success or test skipped (returned by `tap_finish`) + 1 - failed test(s) or bad/missing test plan (returned by `tap_finish`) + 77 - reserved* + 99 - hard error** (set by `ASSERT`) + 123 - valgrind error (set by ptrun) + +* Automake uses an exit status of 77 for simple tests to indicate that a test + was skipped. For TAP-based tests an exit status of 77 counts as failure, so + skipped tests should call `tap_skip_all` and return 0. Avoiding 77 as an + exit status prevents confusion between a failed or skipped test if a test is + accidentally run as a simple test. + +** For compatibility with Automake's simple test runner. + +Template +-------- + +.. code:: c + + #include "../alpmtest.h" + + /* */ + + pt_env_t *pt = NULL; + + void cleanup(void) { + pt_cleanup(pt); + } + + int main(void) { + ASSERT(atexit(cleanup) == 0); + + + + tap_plan(); + + + return tap_finish(); + } + +.. vim: set ft=rst: diff --git a/test/alpm/alpmtest.h b/test/alpm/alpmtest.h new file mode 100644 index 00000000..3df772ab --- /dev/null +++ b/test/alpm/alpmtest.h @@ -0,0 +1,14 @@ +#include "pactest.c" +#include "ptserve.c" +#include "tap.c" +#include +#include + +#define ASSERT(x) \ + if(!(x)) { \ + tap_bail("ASSERT FAILED %s line %d: '%s'", __FILE__, __LINE__, #x); \ + exit(1); \ + } + +#define CAN_CHROOT chroot("/") == 0 +#define CAN_CHOWN geteuid() == 0 diff --git a/test/alpm/pactest.c b/test/alpm/pactest.c new file mode 100644 index 00000000..28fdb14e --- /dev/null +++ b/test/alpm/pactest.c @@ -0,0 +1,458 @@ +/* + * Copyright 2015 Andrew Gregory + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Project URL: http://github.com/andrewgregory/pactest.c + */ + +#ifndef PACTEST_C +#define PACTEST_C + +#define PACTEST_C_VERSION "0.1" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef struct pt_pkg_t { + alpm_list_t *backup; + alpm_list_t *conflicts; + alpm_list_t *depends; + alpm_list_t *groups; + alpm_list_t *licenses; + alpm_list_t *optdepends; + alpm_list_t *makedepends; + alpm_list_t *checkdepends; + alpm_list_t *provides; + alpm_list_t *replaces; + char *arch; + char *base; + char *builddate; + char *desc; + char *csize; + char *isize; + char *name; + char *packager; + char *url; + char *version; + char *filename; +} pt_pkg_t; + +typedef struct pt_db_t { + char *name; + alpm_list_t *pkgs; +} pt_db_t; + +typedef struct pt_env_t { + char *root; + char *dbpath; + int rootfd; + int dbpathfd; + alpm_list_t *pkgs; + alpm_list_t *dbs; +} pt_env_t; + +/************************************ + * utility functions + ************************************/ + +char *pt_path(pt_env_t *pt, const char *path) { + static char abspath[PATH_MAX]; + if(snprintf(abspath, PATH_MAX, "%s/%s", pt->root, path) >= PATH_MAX) { + return NULL; + } else { + return abspath; + } +} + +static int _pt_rmrfat(int dd, const char *path) { + struct stat sbuf; + if(fstatat(dd, path, &sbuf, AT_SYMLINK_NOFOLLOW) != 0) { + return errno == ENOENT ? 0 : -1; + } else if(S_ISDIR(sbuf.st_mode)) { + int fd; + DIR *d; + struct dirent ent, *ctx; + + if((fd = openat(dd, path, O_DIRECTORY)) < 0) { return -1; } + if((d = fdopendir(fd)) == NULL) { close(fd); return -1; } + + while(readdir_r(d, &ent, &ctx) == 0 && ctx != NULL) { + if(strcmp(ent.d_name, ".") == 0) { continue; } + if(strcmp(ent.d_name, "..") == 0) { continue; } + if(_pt_rmrfat(fd, ent.d_name) != 0) { break; } + } + closedir(d); + return ctx == NULL ? unlinkat(dd, path, AT_REMOVEDIR) : -1; + } else { + return unlinkat(dd, path, 0); + } +} + +static int _pt_mkpdirat(int dd, const char *path, mode_t mode) { + char *c, *pcopy = strdup(path); + if(pcopy == NULL) { return -1; } + for(c = pcopy; *c == '/'; c++); /* skip leading '/' */ + while((c = strchr(c, '/'))) { + *c = '\0'; + if(mkdirat(dd, pcopy, mode) != 0 && errno != EEXIST) { + free(pcopy); + return -1; + } + for(*(c++) = '/'; *c == '/'; c++); /* restore and skip '/' */ + } + free(pcopy); + return 0; +} + +int pt_mkdirat(int dd, const char *path, mode_t mode) { + if(_pt_mkpdirat(dd, path, mode) != 0) { return -1; } + return mkdirat(dd, path, mode); +} + +/************************************ + * package functions + ************************************/ + +void pt_pkg_free(pt_pkg_t *pkg) { + if(pkg == NULL) { return; } + FREELIST(pkg->backup); + FREELIST(pkg->conflicts); + FREELIST(pkg->depends); + FREELIST(pkg->groups); + FREELIST(pkg->licenses); + FREELIST(pkg->optdepends); + FREELIST(pkg->provides); + FREELIST(pkg->replaces); + free(pkg->arch); + free(pkg->base); + free(pkg->builddate); + free(pkg->desc); + free(pkg->csize); + free(pkg->isize); + free(pkg->name); + free(pkg->packager); + free(pkg->url); + free(pkg->version); + free(pkg->filename); + free(pkg); +} + +pt_pkg_t *pt_pkg_new(pt_env_t *pt, const char *pkgname, const char *pkgver) { + pt_pkg_t *pkg = NULL; +#define _PT_ASSERT(x) if(!(x)) { pt_pkg_free(pkg); return NULL; } + _PT_ASSERT(pkg = calloc(sizeof(pt_pkg_t), 1)); + _PT_ASSERT(pkg->name = strdup(pkgname)); + _PT_ASSERT(pkg->version = strdup(pkgver)); + _PT_ASSERT(pt == NULL || alpm_list_append(&pt->pkgs, pkg)); +#undef _PT_ASSERT + return pkg; +} + +static int _pt_fwrite_pkgentry(FILE *f, const char *section, const char *value) { + if(value && fprintf(f, "%s = %s\n", section, value) < 0) { return -1; } + return 0; +} + +static int _pt_fwrite_pkglist(FILE *f, const char *section, alpm_list_t *values) { + while(values) { + if(_pt_fwrite_pkgentry(f, section, values->data) < 0) { return -1; } + values = values->next; + } + return 0; +} + + +int _pt_pkg_write_archive(pt_pkg_t *pkg, struct archive *a) { + FILE *contents; + struct archive_entry *e; + char *buf = NULL; + size_t buflen = 0; + + if((contents = open_memstream(&buf, &buflen)) == NULL) { return -1; } + +#define _PT_ASSERT(x) if(!(x)) { fclose(contents); free(buf); return -1; } + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "pkgname", pkg->name) == 0 ); + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "pkgver", pkg->version) == 0 ); + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "pkgdesc", pkg->desc) == 0 ); + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "url", pkg->url) == 0 ); + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "builddate", pkg->builddate) == 0 ); + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "packager", pkg->packager) == 0 ); + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "arch", pkg->arch) == 0 ); + _PT_ASSERT( _pt_fwrite_pkgentry(contents, "size", pkg->isize) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "group", pkg->groups) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "license", pkg->licenses) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "depend", pkg->depends) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "optdepend", pkg->optdepends) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "conflict", pkg->conflicts) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "replaces", pkg->replaces) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "provides", pkg->provides) == 0 ); + _PT_ASSERT( _pt_fwrite_pkglist(contents, "backup", pkg->backup) == 0 ); + _PT_ASSERT( fclose(contents) == 0 ); +#undef _PT_ASSERT + +#define _PT_ASSERT(x) if(!(x)) { free(buf); archive_entry_free(e); return -1; } + _PT_ASSERT( e = archive_entry_new() ); + + archive_entry_set_pathname(e, ".PKGINFO"); + archive_entry_set_perm(e, 0644); + archive_entry_set_filetype(e, AE_IFREG); + archive_entry_set_size(e, buflen); + + _PT_ASSERT( archive_write_header(a, e) == ARCHIVE_OK ); + _PT_ASSERT( archive_write_data(a, buf, buflen) != -1 ); +#undef _PT_ASSERT + + free(buf); + archive_entry_free(e); + + return 0; +} + +int pt_pkg_writeat(int dd, const char *path, pt_pkg_t *pkg) { + struct archive *a; + char *c; + int fd; + + if(_pt_mkpdirat(dd, path, 0700) != 0) { return -1; } + if((fd = openat(dd, path, O_CREAT | O_WRONLY, 0644)) < 0) { return -1; } + + if((a = archive_write_new()) == NULL) { close(fd); return -1; } + +#define _PT_ASSERT(x) if(!(x)) { close(fd); archive_write_free(a); return -1; } + + if((c = strrchr(path, '.'))) { + if(strcmp(c, ".bz2") == 0) { + _PT_ASSERT( archive_write_add_filter_bzip2(a) == ARCHIVE_OK ); + } else if(strcmp(c, ".gz") == 0) { + _PT_ASSERT( archive_write_add_filter_gzip(a) == ARCHIVE_OK ); + } else if(strcmp(c, ".xz") == 0) { + _PT_ASSERT( archive_write_add_filter_xz(a) == ARCHIVE_OK ); + } else if(strcmp(c, ".lz") == 0) { + _PT_ASSERT( archive_write_add_filter_lzip(a) == ARCHIVE_OK ); + } else if(strcmp(c, ".Z") == 0) { + _PT_ASSERT( archive_write_add_filter_compress(a) == ARCHIVE_OK ); + } + } + + _PT_ASSERT( archive_write_set_format_ustar(a) == ARCHIVE_OK ); + _PT_ASSERT( archive_write_open_fd(a, fd) == ARCHIVE_OK ); + _PT_ASSERT( _pt_pkg_write_archive(pkg, a) == 0 ); + +#undef _PT_ASSERT + + archive_write_free(a); + close(fd); + return 0; +} + +/************************************ + * database functions + ************************************/ + +void pt_db_free(pt_db_t *db) { + if(db == NULL) { return; } + alpm_list_free(db->pkgs); + free(db->name); + free(db); +} + +pt_db_t *pt_db_new(pt_env_t *pt, const char *dbname) { + pt_db_t *db = NULL; +#define _PT_ASSERT(x) if(!(x)) { pt_db_free(db); return NULL; } + _PT_ASSERT(db = calloc(sizeof(pt_db_t), 1)); + _PT_ASSERT(db->name = strdup(dbname)); + _PT_ASSERT(pt == NULL || alpm_list_append(&pt->dbs, db)); +#undef _PT_ASSERT + return db; +} + +int pt_db_add_pkg(pt_db_t *db, pt_pkg_t *pkg) { + return alpm_list_append(&db->pkgs, pkg) ? 1 : 0; +} + +int _pt_fwrite_dbheader(FILE *f, const char *header) { + return fprintf(f, "%%" "%s" "%%" "\n", header); +} + +void _pt_fwrite_dbentry(FILE *f, const char *section, const char *value) { + if(value == NULL) { return; } + _pt_fwrite_dbheader(f, section); + fprintf(f, "%s\n\n", value); +} + +void _pt_fwrite_dblist(FILE *f, const char *section, alpm_list_t *values) { + _pt_fwrite_dbheader(f, section); + while(values) { + fprintf(f, "%s\n", (char *) values->data); + values = values->next; + } + fputc('\n', f); +} + +int pt_db_writeat(int dd, const char *path, pt_db_t *db) { + alpm_list_t *i; + struct archive *a = archive_write_new(); + struct archive_entry *e = archive_entry_new(); + int fd = openat(dd, path, O_CREAT | O_WRONLY, 0644); + + archive_write_set_format_ustar(a); + archive_write_open_fd(a, fd); + for(i = db->pkgs; i; i = i->next) { + pt_pkg_t *pkg = i->data; + size_t buflen = 0; + char *buf, ppath[PATH_MAX]; + FILE *f; + + sprintf(ppath, "%s-%s/", pkg->name, pkg->version); + archive_entry_clear(e); + archive_entry_set_pathname(e, ppath); + archive_entry_set_filetype(e, AE_IFDIR); + archive_entry_set_perm(e, 0755); + archive_write_header(a, e); + + f = open_memstream(&buf, &buflen); + _pt_fwrite_dblist(f, "DEPENDS", pkg->depends); + _pt_fwrite_dblist(f, "CONFLICTS", pkg->conflicts); + _pt_fwrite_dblist(f, "PROVIDES", pkg->provides); + _pt_fwrite_dblist(f, "OPTDEPENDS", pkg->optdepends); + _pt_fwrite_dblist(f, "MAKEDEPENDS", pkg->makedepends); + _pt_fwrite_dblist(f, "CHECKDEPENDS", pkg->checkdepends); + fclose(f); + + sprintf(ppath, "%s-%s/depends", pkg->name, pkg->version); + archive_entry_clear(e); + archive_entry_set_pathname(e, ppath); + archive_entry_set_filetype(e, AE_IFREG); + archive_entry_set_perm(e, 0644); + archive_entry_set_size(e, buflen); + archive_write_header(a, e); + archive_write_data(a, buf, buflen); + free(buf); + + f = open_memstream(&buf, &buflen); + _pt_fwrite_dbentry(f, "FILENAME", pkg->filename); + _pt_fwrite_dbentry(f, "NAME", pkg->name); + _pt_fwrite_dbentry(f, "ARCH", pkg->arch); + _pt_fwrite_dbentry(f, "BASE", pkg->base); + _pt_fwrite_dbentry(f, "VERSION", pkg->version); + _pt_fwrite_dbentry(f, "DESC", pkg->desc); + _pt_fwrite_dbentry(f, "CSIZE", pkg->csize); + _pt_fwrite_dbentry(f, "ISIZE", pkg->isize); + _pt_fwrite_dblist(f, "GROUPS", pkg->groups); + fclose(f); + + sprintf(ppath, "%s-%s/desc", pkg->name, pkg->version); + archive_entry_clear(e); + archive_entry_set_pathname(e, ppath); + archive_entry_set_filetype(e, AE_IFREG); + archive_entry_set_perm(e, 0644); + archive_entry_set_size(e, buflen); + archive_write_header(a, e); + archive_write_data(a, buf, buflen); + free(buf); + } + + archive_entry_free(e); + archive_write_free(a); + close(fd); + return 0; +} + +/************************************ + * pactest functions + ************************************/ + +void pt_cleanup(pt_env_t *pt) { + if(pt == NULL) { return; } + + alpm_list_free_inner(pt->pkgs, (alpm_list_fn_free) pt_pkg_free); + alpm_list_free(pt->pkgs); + alpm_list_free_inner(pt->dbs, (alpm_list_fn_free) pt_db_free); + alpm_list_free(pt->dbs); + + close(pt->rootfd); + close(pt->dbpathfd); + + _pt_rmrfat(AT_FDCWD, pt->root); + + free(pt->root); + free(pt->dbpath); + free(pt); +} + +pt_env_t *pt_init(const char *dbpath) { + pt_env_t *pt = NULL; + char *tmpdir = getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp"; + char *template = "pactest-XXXXXX"; + size_t rootlen = strlen(tmpdir) + strlen("/") + strlen(template); + if(dbpath == NULL) { dbpath = "var/lib/pacman"; } +#define _PT_ASSERT(x) if(!(x)) { pt_cleanup(pt); return NULL; } + _PT_ASSERT(pt = calloc(sizeof(pt_env_t), 1)); + _PT_ASSERT(pt->root = malloc(rootlen + 1)); + _PT_ASSERT(sprintf(pt->root, "%s/%s", tmpdir, template) > 0); + _PT_ASSERT(mkdtemp(pt->root) != NULL); + _PT_ASSERT((pt->rootfd = open(pt->root, O_DIRECTORY)) >= 0); + _PT_ASSERT(pt->dbpath = strdup(pt_path(pt, dbpath))); + _PT_ASSERT(pt_mkdirat(pt->rootfd, dbpath, 0700) == 0); + _PT_ASSERT((pt->dbpathfd = open(pt->dbpath, O_DIRECTORY)) >= 0); +#undef _PT_ASSERT + return pt; +} + +int pt_install_db(pt_env_t *pt, pt_db_t *db) { + char path[PATH_MAX]; + pt_mkdirat(pt->dbpathfd, "sync", 0755); + sprintf(path, "sync/%s.db", db->name); + return pt_db_writeat(pt->dbpathfd, path, db); +} + +char *pt_vasprintf(const char *fmt, va_list args) { + va_list arg_cp; + size_t len; + char *ret; + va_copy(arg_cp, args); + len = vsnprintf(NULL, 0, fmt, arg_cp); + va_end(arg_cp); + if((ret = malloc(len + 2)) != NULL) { vsprintf(ret, fmt, args); } + return ret; +} + +char *pt_asprintf(const char *fmt, ...) { + va_list args; + char *ret; + va_start(args, fmt); + ret = pt_vasprintf(fmt, args); + va_end(args); + return ret; +} + +#endif /* PACTEST_C */ diff --git a/test/alpm/ptrun b/test/alpm/ptrun new file mode 100755 index 00000000..dcdc9e55 --- /dev/null +++ b/test/alpm/ptrun @@ -0,0 +1,58 @@ +#!/bin/bash + +fakechroot=0 +fakeroot=0 +gdb=0 +libtool=0 +valgrind=0 +cmd=() + +extend() { + if which "$1" &>/dev/null; then + cmd+=("$@") + else + # bailing out would be counted as a failure by test harnesses, + # ignore missing programs so that tests can be gracefully skipped + printf "warning: command '$1' not found\n" >&2 + fi +} + +usage() { + printf "ptrun - run an executable pacman test\n" + printf "usage: ptrun [options] [test-options]\n" + printf "\n" + printf "Options:\n" + printf " -c fakechroot\n" + printf " -d enable alpm debug log\n" + printf " -r fakeroot\n" + printf " -g gdb (implies -l)\n" + printf " -h display help\n" + printf " -l libtool execute\n" + printf " -s preserve test environment\n" + printf " -v valgrind (implies -l)\n" +} + +while getopts cdghlrsv name; do + case $name in + c) fakechroot=1;; + d) export PT_DEBUG=1;; + g) libtool=1; gdb=1;; + h) usage; exit;; + l) libtool=1;; + r) fakeroot=1;; + s) export PT_KEEP_ROOT=1;; + v) libtool=1; valgrind=1;; + esac +done + +[ $fakechroot -eq 1 ] && extend fakechroot +[ $fakeroot -eq 1 ] && extend fakeroot +[ $libtool -eq 1 ] && extend libtool execute +[ $gdb -eq 1 ] && extend gdb +[ $valgrind -eq 1 ] && extend valgrind --quiet --leak-check=full \ + --gen-suppressions=yes --error-exitcode=123 \ + --suppressions="$(dirname "$0")/../../valgrind.supp" + +while (( --OPTIND > 0 )); do shift; done # remove our options from the stack + +"${cmd[@]}" "$@" diff --git a/test/alpm/ptserve.c b/test/alpm/ptserve.c new file mode 100644 index 00000000..049abda0 --- /dev/null +++ b/test/alpm/ptserve.c @@ -0,0 +1,403 @@ +/* + * Copyright 2015 Andrew Gregory + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Project URL: http://github.com/andrewgregory/pactest.c + */ + +#ifndef PTSERVE_C +#define PTSERVE_C + +#define PTSERVE_C_VERSION 0.1 + +#include +#include +#include +#include +#include +#include + +#include + +typedef struct ptserve_message_t { + struct ptserve_t *ptserve; + char *method; + char *path; + char *protocol; + alpm_list_t *headers; + int socket_fd; +} ptserve_message_t; + +typedef void (ptserve_response_cb_t)(ptserve_message_t *request); + +typedef struct ptserve_t { + ptserve_response_cb_t *response_cb; + uint16_t port; + char *url; + void *data; + int rootfd; + int sd_server; + pid_t _pid; + pthread_t _tid; +} ptserve_t; + +/***************************************************************************** + * utilities + ****************************************************************************/ + +static int _vasprintf(char **strp, const char *fmt, va_list args) { + va_list arg_cp; + size_t len; + va_copy(arg_cp, args); + len = vsnprintf(NULL, 0, fmt, arg_cp); + va_end(arg_cp); + if((*strp = malloc(len + 2)) != NULL) { return vsprintf(*strp, fmt, args); } + else { return -1; } +} + +static int _asprintf(char **strp, const char *fmt, ...) { + va_list args; + int ret; + va_start(args, fmt); + ret = _vasprintf(strp, fmt, args); + va_end(args); + return ret; +} + +static ssize_t _send(int socket, const void *buf, size_t len) { + return send(socket, buf, len, MSG_NOSIGNAL); +} + +static ssize_t _sendf(int socket, const char *fmt, ...) { + ssize_t ret; + char *buf = NULL; + int blen = 0; + va_list args; + va_start(args, fmt); + blen = _vasprintf(&buf, fmt, args); + va_end(args); + ret = _send(socket, buf, blen); + free(buf); + return ret; +} + +static ssize_t _dgetdelim(int fd, char *buf, ssize_t bufsiz, char *delim) { + char *d = delim, *b = buf; + while(1) { + ssize_t ret = read(fd, b, 1); + if(ret == 0) { *b = '\0'; return b - buf; } + if(ret == -1) { return -1; } + if(*d && *b == *d) { + if(*(++d) == '\0') { + b -= strlen(delim) - 1; + *b = '\0'; + return b - buf; + } + } else { + d = delim; + } + if(++b - buf >= bufsiz - 1) { return -1; } + } +} + +/***************************************************************************** + * http message + ****************************************************************************/ + +#define PTSERVE_HDR_MAX 1024 +ptserve_message_t *ptserve_message_new(ptserve_t *server, int socket_fd) { + ptserve_message_t *msg = calloc(sizeof(ptserve_message_t), 1); + char line[PTSERVE_HDR_MAX]; + + _dgetdelim(socket_fd, line, PTSERVE_HDR_MAX, " "); + msg->method = strdup(line); + _dgetdelim(socket_fd, line, PTSERVE_HDR_MAX, " "); + msg->path = strdup(line); + _dgetdelim(socket_fd, line, PTSERVE_HDR_MAX, "\r\n"); + msg->protocol = strdup(line); + + while(_dgetdelim(socket_fd, line, PTSERVE_HDR_MAX, "\r\n") > 0) { + msg->headers = alpm_list_add(msg->headers, strdup(line)); + } + + msg->ptserve = server; + msg->socket_fd = socket_fd; + + return msg; +} + +void ptserve_message_free(ptserve_message_t *msg) { + if(msg == NULL) { return; } + free(msg->method); + free(msg->path); + free(msg->protocol); + FREELIST(msg->headers); + if(msg->socket_fd >= 0) { close(msg->socket_fd); } + free(msg); +} + +const char *ptserve_message_get_header(ptserve_message_t *msg, const char *hdr) { + alpm_list_t *i; + size_t hlen = strlen(hdr); + for(i = msg->headers; i; i = alpm_list_next(i)) { + const char *mhdr = i->data; + if(strncasecmp(mhdr, hdr, hlen) == 0 && strncmp(mhdr + hlen, ": ", 2) == 0) { + return mhdr + hlen + 2; + } + } + return NULL; +} + +void ptserve_message_rm_header(ptserve_message_t *msg, const char *hdr) { + alpm_list_t *i; + size_t hlen = strlen(hdr); + for(i = msg->headers; i; i = i->next) { + char *oldheader = i->data; + if(strncasecmp(i->data, hdr, hlen) == 0 && oldheader[hlen] == ':') { + msg->headers = alpm_list_remove_item(msg->headers, i); + free(i->data); + free(i); + return; + } + } +} + +int ptserve_message_set_header(ptserve_message_t *message, + const char *header, const char *value) { + alpm_list_t *i; + char *newheader; + size_t hlen = strlen(header); + + if(_asprintf(&newheader, "%s: %s", header, value) == -1) { return 0; } + + /* look for an existing header */ + for(i = message->headers; i; i = i->next) { + char *oldheader = i->data; + if(strncasecmp(i->data, header, hlen) == 0 && oldheader[hlen] == ':') { + free(i->data); + i->data = newheader; + return 1; + } + } + + message->headers = alpm_list_add(message->headers, newheader); + return 1; +} + +/***************************************************************************** + * ptserve + ****************************************************************************/ + +ptserve_t *ptserve_new() { + ptserve_t *ptserve = calloc(sizeof(ptserve_t), 1); + if(ptserve == NULL) { return NULL; } + ptserve->rootfd = AT_FDCWD; + ptserve->sd_server = -1; + ptserve->_tid = -1; + return ptserve; +} + +void ptserve_free(ptserve_t *ptserve) { + if(ptserve == NULL) { return; } + if(ptserve->_pid > 0) { + kill(ptserve->_pid, SIGTERM); + waitpid(ptserve->_pid, NULL, 0); + } else if(ptserve->_tid != -1) { + pthread_cancel(ptserve->_tid); + /* pthread_kill(ptserve->_tid, SIGINT); */ + /* pthread_join(ptserve->_tid, NULL); */ + } + free(ptserve->url); + free(ptserve); +} + +void ptserve_listen(ptserve_t *ptserve) { + struct sockaddr_in sin; + socklen_t addrlen = sizeof(sin); + + if(ptserve->sd_server >= 0) { return; } /* already listening */ + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(0); + + ptserve->sd_server = socket(PF_INET, SOCK_STREAM, 0); + bind(ptserve->sd_server, (struct sockaddr*) &sin, sizeof(sin)); + getsockname(ptserve->sd_server, (struct sockaddr*) &sin, &addrlen); + + listen(ptserve->sd_server, SOMAXCONN); + + ptserve->port = ntohs(sin.sin_port); + _asprintf(&(ptserve->url), "http://127.0.0.1:%d", ptserve->port); +} + +int ptserve_accept(ptserve_t *ptserve) { + return accept(ptserve->sd_server, 0, 0); +} + +void *ptserve_serve(ptserve_t *ptserve) { + int session_fd; + ptserve_listen(ptserve); + while((session_fd = ptserve_accept(ptserve)) >= 0) { + ptserve_message_t *msg = ptserve_message_new(ptserve, session_fd); + ptserve->response_cb(msg); + ptserve_message_free(msg); + } + return NULL; +} + +/***************************************************************************** + * ptserve helpers + ****************************************************************************/ + +void ptserve_send_file(int socket, int rootfd, const char *path) { + struct stat sbuf; + ssize_t nread; + char buf[128]; + int fd = openat(rootfd, path, O_RDONLY); + fstat(fd, &sbuf); + _sendf(socket, "HTTP/1.1 200 OK\r\n"); + _sendf(socket, "Content-Length: %zd\r\n", sbuf.st_size); + _sendf(socket, "\r\n"); + while((nread = read(fd, buf, 128)) > 0 && _send(socket, buf, nread)); + close(fd); +} + +void ptserve_send_range(int socket, int rootfd, const char *path, off_t start, off_t len) { + struct stat sbuf; + ssize_t nread; + char buf[128]; + int fd = openat(rootfd, path, O_RDONLY); + lseek(fd, start, SEEK_SET); + fstat(fd, &sbuf); + if(len > sbuf.st_size - start) { len = sbuf.st_size - start; } + _sendf(socket, "HTTP/1.1 200 OK\r\n"); + _sendf(socket, "Content-Length: %zd\r\n", len); + _sendf(socket, "Content-Range: bytes %zd-%zd/%zd\r\n", + start, start + len, sbuf.st_size); + _sendf(socket, "\r\n"); + while((nread = read(fd, buf, 128)) > 0 && _send(socket, buf, nread)); + close(fd); +} + +void ptserve_send_str(int socket, const char *body) { + size_t blen = strlen(body); + _sendf(socket, "HTTP/1.1 200 OK\r\n"); + _sendf(socket, "Content-Length: %zd\r\n", blen); + _sendf(socket, "\r\n"); + _send(socket, body, blen); +} + +void ptserve_cb_dir(ptserve_message_t *request) { + char *c, *path = request->path; + const char *range_hdr; + /* strip protocol and location if present */ + if((c = strstr(path, "://")) != NULL) { + path = c + 3; + if((c = strchr(path, '/')) != NULL) { + path = c + 1; + } else { + path = "/"; + } + } + /* strip leading '/' */ + if(path[0] == '/') { path++; } + if(range_hdr = ptserve_message_get_header(request, "Range")) { + off_t start = 0, len = 0; + sscanf(range_hdr, "Range: bytes=%li-%li", &start, &len); + ptserve_send_range(request->socket_fd, request->ptserve->rootfd, path, start, len); + } else { + ptserve_send_file(request->socket_fd, request->ptserve->rootfd, path); + } +} + +ptserve_t *ptserve_serve_cbat(int fd, ptserve_response_cb_t *cb, void *data) { + ptserve_t *ptserve = ptserve_new(); + if(ptserve == NULL) { + free(ptserve); + return NULL; + } + ptserve->rootfd = fd; + ptserve->response_cb = cb; + ptserve->data = data; + ptserve_serve(ptserve); + return ptserve; +} + +ptserve_t *ptserve_serve_cb(ptserve_response_cb_t *cb, void *data) { + return ptserve_serve_cbat(AT_FDCWD, cb, data); +} + +ptserve_t *ptserve_serve_dirat(int fd, const char *path) { + ptserve_t *ptserve = ptserve_new(); + int rootfd = openat(fd, path, O_RDONLY | O_DIRECTORY); + if(ptserve == NULL || (ptserve->rootfd = rootfd) < 0) { + free(ptserve); + return NULL; + } + ptserve->response_cb = ptserve_cb_dir; + ptserve_listen(ptserve); + /* pthread_create(&ptserve->_tid, NULL, (void* (*)(void*)) ptserve_serve, ptserve); */ + /* pthread_detach(ptserve->_tid); */ + ptserve->_pid = fork(); + if(ptserve->_pid == 0) { + ptserve_serve(ptserve); + } + return ptserve; +} + +ptserve_t *ptserve_serve_dir(const char *path) { + return ptserve_serve_dirat(AT_FDCWD, path); +} + +/***************************************************************************** + * tests + ****************************************************************************/ + +void ptserve_set_proxy(ptserve_t *ptserve) { + setenv("http_proxy", ptserve->url, 1); +} +#if 0 +int main(int argc, char *argv[]) { + ptserve_t *ptserve = ptserve_serve_cbat(AT_FDCWD, ptserve_cb_dir, NULL); + ptserve_listen(ptserve); + printf("listening on port %d\n", ptserve->port); + ptserve_serve(ptserve); + return 0; +} + +int main_nocb(int argc, char *argv[]) { + int fd; + ptserve_t *ptserve = ptserve_new(); + ptserve_listen(ptserve); + printf("listening on port %d\n", ptserve->port); + while((fd = ptserve_accept(ptserve)) >= 0) { + ptserve_message_t *msg = ptserve_message_new(ptserve, fd); + ptserve_cb_dir(msg); + ptserve_message_free(msg); + } + ptserve_free(ptserve); +} +#endif + +#endif /* PTSERVE_C */ + +/* vim: set ts=2 sw=2 noet: */ diff --git a/test/alpm/tap.c b/test/alpm/tap.c new file mode 100644 index 00000000..dcff2add --- /dev/null +++ b/test/alpm/tap.c @@ -0,0 +1,305 @@ +/* + * Copyright 2014-2015 Andrew Gregory + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Project URL: http://github.com/andrewgregory/tap.c + */ + +#ifndef TAP_C +#define TAP_C + +#include +#include +#include +#include +#include +#include + +static int _tap_tests_planned = 0; +static int _tap_tests_run = 0; +static int _tap_tests_failed = 0; +static const char *_tap_todo = NULL; + +#define _tap_output stdout +#define _tap_failure_output stderr +#define _tap_todo_output stdout + +#ifndef TAP_EXIT_SUCCESS +#define TAP_EXIT_SUCCESS EXIT_SUCCESS +#endif + +#ifndef TAP_EXIT_FAILURE +#define TAP_EXIT_FAILURE EXIT_FAILURE +#endif + +#ifndef TAP_EXIT_ASSERT +#define TAP_EXIT_ASSERT TAP_EXIT_FAILURE +#endif + +void tap_plan(int test_count); +void tap_skip_all(const char *reason, ...) + __attribute__ ((format (printf, 1, 2))); +void tap_done_testing(void); +int tap_finish(void); +void tap_todo(const char *reason); +void tap_skip(int count, const char *reason, ...) + __attribute__ ((format (printf, 2, 3))); +void tap_bail(const char *reason, ...) + __attribute__ ((format (printf, 1, 2))); +void tap_diag(const char *message, ...) + __attribute__ ((format (printf, 1, 2))); +void tap_note(const char *message, ...) + __attribute__ ((format (printf, 1, 2))); + +int tap_get_testcount_planned(void); +int tap_get_testcount_run(void); +int tap_get_testcount_failed(void); +const char *tap_get_todo(void); + +#define tap_assert(x) \ + if(!(x)) { tap_bail("ASSERT FAILED: '%s'", #x); exit(TAP_EXIT_ASSERT); } + +#define tap_ok(...) _tap_ok(__FILE__, __LINE__, __VA_ARGS__) +#define tap_vok(success, args) _tap_vok(__FILE__, __LINE__, success, args) +#define tap_is_float(...) _tap_is_float(__FILE__, __LINE__, __VA_ARGS__) +#define tap_is_int(...) _tap_is_int(__FILE__, __LINE__, __VA_ARGS__) +#define tap_is_str(...) _tap_is_str(__FILE__, __LINE__, __VA_ARGS__) + +int _tap_ok(const char *file, int line, int success, const char *name, ...) + __attribute__ ((format (printf, 4, 5))); +int _tap_vok(const char *file, int line, + int success, const char *name, va_list args) + __attribute__ ((format (printf, 4, 0))); +int _tap_is_float(const char *file, int line, + double got, double expected, double delta, const char *name, ...) + __attribute__ ((format (printf, 6, 7))); +int _tap_is_int(const char *file, int line, + intmax_t got, intmax_t expected, const char *name, ...) + __attribute__ ((format (printf, 5, 6))); +int _tap_is_str(const char *file, int line, + const char *got, const char *expected, const char *name, ...) + __attribute__ ((format (printf, 5, 6))); + +#define TAP_VPRINT_MSG(stream, msg) if(msg) { \ + va_list args; \ + va_start(args, msg); \ + fputc(' ', stream); \ + vfprintf(stream, msg, args); \ + va_end(args); \ + } + + +void tap_plan(int test_count) +{ + _tap_tests_planned = test_count; + fprintf(_tap_output, "1..%d\n", test_count); + fflush(_tap_output); +} + +void tap_skip_all(const char *reason, ...) +{ + FILE *stream = _tap_output; + fputs("1..0 # SKIP", _tap_output); + TAP_VPRINT_MSG(stream, reason); + fputc('\n', _tap_output); + fflush(_tap_output); +} + +void tap_done_testing(void) +{ + tap_plan(_tap_tests_run); +} + +int tap_finish(void) +{ + if(_tap_tests_run != _tap_tests_planned) { + tap_diag("Looks like you planned %d tests but ran %d.", + _tap_tests_planned, _tap_tests_run); + } + return _tap_tests_run == _tap_tests_planned + && _tap_tests_failed == 0 ? TAP_EXIT_SUCCESS : TAP_EXIT_FAILURE; +} + +int tap_get_testcount_planned(void) +{ + return _tap_tests_planned; +} + +int tap_get_testcount_run(void) +{ + return _tap_tests_run; +} + +int tap_get_testcount_failed(void) +{ + return _tap_tests_failed; +} + +const char *tap_get_todo(void) +{ + return _tap_todo; +} + +void tap_todo(const char *reason) +{ + _tap_todo = reason; +} + +void tap_skip(int count, const char *reason, ...) +{ + FILE *stream = _tap_output; + while(count--) { + fprintf(_tap_output, "ok %d # SKIP", ++_tap_tests_run); + TAP_VPRINT_MSG(stream, reason); + fputc('\n', _tap_output); + } + fflush(_tap_output); +} + +void tap_bail(const char *reason, ...) +{ + FILE *stream = _tap_output; + fputs("Bail out!", _tap_output); + TAP_VPRINT_MSG(stream, reason); + fputc('\n', _tap_output); + fflush(_tap_output); +} + +void tap_diag(const char *message, ...) +{ + FILE *stream = _tap_todo ? _tap_todo_output : _tap_failure_output; + + fputs("#", stream); + TAP_VPRINT_MSG(stream, message); + fputc('\n', stream); + fflush(stream); + +} + +void tap_note(const char *message, ...) +{ + FILE *stream = _tap_output; + fputs("#", _tap_output); + TAP_VPRINT_MSG(stream, message); + fputc('\n', _tap_output); + fflush(_tap_output); +} + +int _tap_vok(const char *file, int line, + int success, const char *name, va_list args) +{ + const char *result; + if(success) { + result = "ok"; + if(_tap_todo) ++_tap_tests_failed; + } else { + result = "not ok"; + if(!_tap_todo) ++_tap_tests_failed; + } + + fprintf(_tap_output, "%s %d", result, ++_tap_tests_run); + + if(name) { + fputs(" - ", _tap_output); + vfprintf(_tap_output, name, args); + } + + if(_tap_todo) { + fputs(" # TODO", _tap_output); + if(*_tap_todo) { + fputc(' ', _tap_output); + fputs(_tap_todo, _tap_output); + } + } + + fputc('\n', _tap_output); + fflush(_tap_output); + + if(!success && file) { + /* TODO add test name if available */ + tap_diag(" Failed%s test at %s line %d.", + _tap_todo ? " (TODO)" : "", file, line); + } + + return success; +} + +#define TAP_OK(success, name) do { \ + va_list args; \ + va_start(args, name); \ + _tap_vok(file, line, success, name, args); \ + va_end(args); \ + } while(0) + +int _tap_ok(const char *file, int line, + int success, const char *name, ...) +{ + TAP_OK(success, name); + return success; +} + +int _tap_is_float(const char *file, int line, + double got, double expected, double delta, const char *name, ...) +{ + double diff = (expected > got ? expected - got : got - expected); + int match = diff < delta; + TAP_OK(match, name); + if(!match) { + tap_diag(" got: '%f'", got); + tap_diag(" expected: '%f'", expected); + tap_diag(" delta: '%f'", diff); + tap_diag(" allowed: '%f'", delta); + } + return match; +} + +int _tap_is_int(const char *file, int line, + intmax_t got, intmax_t expected, const char *name, ...) +{ + int match = got == expected; + TAP_OK(match, name); + if(!match) { + tap_diag(" got: '%" PRIdMAX "'", got); + tap_diag(" expected: '%" PRIdMAX "'", expected); + } + return match; +} + +int _tap_is_str(const char *file, int line, + const char *got, const char *expected, const char *name, ...) +{ + int match; + if(got && expected) { + match = (strcmp(got, expected) == 0); + } else { + match = (got == expected); + } + TAP_OK(match, name); + if(!match) { + tap_diag(" got: '%s'", got); + tap_diag(" expected: '%s'", expected); + } + return match; +} + +#undef TAP_OK +#undef TAP_VPRINT_MSG + +#endif /* TAP_C */ diff --git a/test/alpm/tests/.gitignore b/test/alpm/tests/.gitignore new file mode 100644 index 00000000..d06c5240 --- /dev/null +++ b/test/alpm/tests/.gitignore @@ -0,0 +1,3 @@ +.deps/ +.libs/ +*.t diff --git a/test/alpm/tests/Makefile.am b/test/alpm/tests/Makefile.am new file mode 100644 index 00000000..a3724e3f --- /dev/null +++ b/test/alpm/tests/Makefile.am @@ -0,0 +1,21 @@ +check_PROGRAMS = \ + add_remove.t \ + cached_part_file.t \ + part_file_in_secondary_cache.t + +noinst_PROGRAMS = $(check_PROGRAMS) + +EXTRA_DIST = $(check_PROGRAMS) + +AM_CPPFLAGS = \ + -imacros $(top_builddir)/config.h \ + -I$(top_srcdir)/lib/libalpm \ + -DLOCALEDIR=\"@localedir@\" + +AM_CFLAGS = -g -D_GNU_SOURCE -pthread + +LDADD = $(LTLIBINTL) $(top_builddir)/lib/libalpm/.libs/libalpm.la + +%.t: %.c + +# vim:set noet: diff --git a/test/alpm/tests/TESTS b/test/alpm/tests/TESTS new file mode 100644 index 00000000..26b0fa51 --- /dev/null +++ b/test/alpm/tests/TESTS @@ -0,0 +1,3 @@ +TESTS += test/alpm/tests/add_remove.t +TESTS += test/alpm/tests/cached_part_file.t +TESTS += test/alpm/tests/part_file_in_secondary_cache.t diff --git a/test/alpm/tests/add_remove.c b/test/alpm/tests/add_remove.c new file mode 100644 index 00000000..13899570 --- /dev/null +++ b/test/alpm/tests/add_remove.c @@ -0,0 +1,56 @@ +#include "../alpmtest.h" + +/* install and them remove a package with a single handle */ +/* http://lists.archlinux.org/pipermail/pacman-dev/2015-February/019906.html */ + +pt_env_t *pt = NULL; +alpm_handle_t *handle = NULL; + +void cleanup(void) +{ + alpm_release(handle); + pt_cleanup(pt); +} + +int main(void) +{ + pt_pkg_t *pkg; + alpm_pkg_t *lpkg; + alpm_handle_t *handle; + alpm_list_t *data = NULL; + const char *pkg_db_path = "var/lib/pacman/local/foo-1-1"; + + ASSERT(atexit(cleanup) == 0); + + ASSERT(pt = pt_init(NULL)); + ASSERT(pkg = pt_pkg_new(pt, "foo", "1-1")); + ASSERT(pt_pkg_writeat(pt->rootfd, "foo.pkg.tar", pkg) == 0); + + ASSERT(handle = alpm_initialize(pt->root, pt->dbpath, NULL)); + ASSERT(alpm_pkg_load(handle, pt_path(pt, "foo.pkg.tar"), 1, 0, &lpkg) == 0); + + /* install the package */ + ASSERT(alpm_trans_init(handle, 0) == 0); + ASSERT(alpm_add_pkg(handle, lpkg) == 0); + ASSERT(alpm_trans_prepare(handle, &data) == 0); + ASSERT(alpm_trans_commit(handle, &data) == 0); + ASSERT(alpm_trans_release(handle) == 0); + ASSERT(lpkg = alpm_db_get_pkg(alpm_get_localdb(handle), "foo")); + ASSERT(faccessat(pt->rootfd, pkg_db_path, F_OK, 0) == 0); + + /* remove the package */ + tap_plan(7); + tap_is_int(alpm_trans_init(handle, 0), 0, "alpm_trans_init"); + tap_is_int(alpm_remove_pkg(handle, lpkg), 0, "alpm_remove_pkg"); + tap_is_int(alpm_trans_prepare(handle, &data), 0, "alpm_trans_prepare"); + tap_is_int(alpm_trans_commit(handle, &data), 0, "alpm_trans_commit"); + tap_is_int(alpm_trans_release(handle), 0, "alpm_trans_release"); + + /* make sure the removal was actually performed */ + tap_ok(alpm_db_get_pkg(alpm_get_localdb(handle), "foo") == NULL, + "foo removed from local cache"); + tap_ok(faccessat(pt->rootfd, pkg_db_path, F_OK, 0) == -1 + && errno == ENOENT, "foo entry removed from db"); + + return tap_finish(); +} diff --git a/test/alpm/tests/cached_part_file.c b/test/alpm/tests/cached_part_file.c new file mode 100644 index 00000000..5811ebad --- /dev/null +++ b/test/alpm/tests/cached_part_file.c @@ -0,0 +1,65 @@ +#include "../alpmtest.h" + +/* install a package with a completed .part file in the cache (FS#35789) */ + +pt_env_t *pt = NULL; +alpm_handle_t *handle = NULL; +int file_downloaded = 0; + +void cleanup(void) +{ + alpm_release(handle); + pt_cleanup(pt); +} + +void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total) +{ + file_downloaded = 1; +} + +int main(void) +{ + pt_pkg_t *pkg; + pt_db_t *db; + alpm_pkg_t *lpkg; + alpm_db_t *adb; + alpm_list_t *data = NULL; + + ASSERT(atexit(cleanup) == 0); + + ASSERT(pt = pt_init(NULL)); + + ASSERT(pkg = pt_pkg_new(pt, "foo", "1-1")); + ASSERT(pkg->filename = strdup("foo.pkg.tar")); + ASSERT(pt_pkg_writeat(pt->rootfd, "tmp/foo.pkg.tar.part", pkg) == 0); + + ASSERT(db = pt_db_new(pt, "sync")); + ASSERT(pt_db_add_pkg(db, pkg)); + ASSERT(pt_install_db(pt, db) == 0); + + ASSERT(handle = alpm_initialize(pt->root, pt->dbpath, NULL)); + ASSERT(alpm_option_add_cachedir(handle, pt_path(pt, "tmp")) == 0); + ASSERT(adb = alpm_register_syncdb(handle, "sync", 0)); + ASSERT(alpm_db_add_server(adb, "http://foo") == 0); + ASSERT(lpkg = alpm_db_get_pkg(adb, "foo")); + + tap_plan(8); + tap_is_int(alpm_trans_init(handle, 0), 0, "alpm_trans_init"); + tap_is_int(alpm_add_pkg(handle, lpkg), 0, "alpm_add_pkg"); + tap_is_int(alpm_trans_prepare(handle, &data), 0, "alpm_trans_prepare"); + + tap_todo("don't fail on .part files"); + tap_is_int(alpm_trans_commit(handle, &data), 0, "alpm_trans_commit"); + tap_todo(NULL); + + tap_is_int(alpm_trans_release(handle), 0, "alpm_trans_release"); + + tap_todo("don't fail on .part files"); + tap_ok(alpm_db_get_pkg(alpm_get_localdb(handle), "foo") != NULL, "foo in local cache"); + tap_ok(faccessat(pt->dbpathfd, "local/foo-1-1", F_OK, 0) == 0, "foo entry in local db"); + tap_todo(NULL); + + tap_ok(file_downloaded == 0, ".part file used"); + + return tap_finish(); +} diff --git a/test/alpm/tests/part_file_in_secondary_cache.c b/test/alpm/tests/part_file_in_secondary_cache.c new file mode 100644 index 00000000..cbf519a6 --- /dev/null +++ b/test/alpm/tests/part_file_in_secondary_cache.c @@ -0,0 +1,102 @@ +#include "../alpmtest.h" + +/* install a package with a partial .part file in a secondary cache */ + +pt_env_t *pt = NULL; +ptserve_t *ptserve = NULL; +alpm_handle_t *handle = NULL; +off_t total_download = 0; +off_t actual_download = 0; + +void cleanup(void) +{ + alpm_release(handle); + pt_cleanup(pt); + ptserve_free(ptserve); +} + +off_t getsize(int dirfd, const char *path) +{ + struct stat s; + return fstatat(dirfd, path, &s, AT_SYMLINK_NOFOLLOW) == 0 ? s.st_size : -1; +} + +void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total) +{ + if(file_xfered == file_total) { + actual_download += file_xfered; + } +} + +void cb_dl_total(off_t total) +{ + total_download += total; +} + +int main(void) +{ + int fd; + const char *cpath = "secondary/foo.pkg.tar.part"; + off_t csize; + pt_pkg_t *pkg; + pt_db_t *db; + alpm_pkg_t *lpkg; + alpm_db_t *adb; + alpm_list_t *data = NULL; + + ASSERT(atexit(cleanup) == 0); + + ASSERT(pt = pt_init(NULL)); + + ASSERT(pkg = pt_pkg_new(pt, "foo", "1-1")); + ASSERT(pkg->filename = strdup("foo.pkg.tar")); + + /* write partial copy to our cache */ + ASSERT(pt_pkg_writeat(pt->rootfd, cpath, pkg) == 0); + ASSERT((csize = getsize(pt->rootfd, cpath)) > 2); + ASSERT(pkg->csize = pt_asprintf("%lu", csize)); + ASSERT((fd = openat(pt->rootfd, cpath, O_WRONLY)) >= 0); + ASSERT(ftruncate(fd, csize / 2) == 0); + ASSERT(close(fd) == 0); + + /* write full copy to server */ + ASSERT(pt_pkg_writeat(pt->rootfd, "srv/foo.pkg.tar", pkg) == 0); + ASSERT(ptserve = ptserve_serve_dirat(pt->rootfd, "srv/")); + + ASSERT(db = pt_db_new(pt, "sync")); + ASSERT(pt_db_add_pkg(db, pkg)); + ASSERT(pt_install_db(pt, db) == 0); + + ASSERT(handle = alpm_initialize(pt->root, pt->dbpath, NULL)); + ASSERT(alpm_option_add_cachedir(handle, pt_path(pt, "primary")) == 0); + ASSERT(alpm_option_add_cachedir(handle, pt_path(pt, "secondary")) == 0); + ASSERT(alpm_option_set_totaldlcb(handle, cb_dl_total) == 0); + ASSERT(alpm_option_set_dlcb(handle, cb_dl_progress) == 0); + + ASSERT(adb = alpm_register_syncdb(handle, "sync", 0)); + ASSERT(alpm_db_add_server(adb, ptserve->url) == 0); + ASSERT(lpkg = alpm_db_get_pkg(adb, "foo")); + + tap_plan(10); + tap_is_int(alpm_trans_init(handle, 0), 0, "alpm_trans_init"); + tap_is_int(alpm_add_pkg(handle, lpkg), 0, "alpm_add_pkg"); + tap_is_int(alpm_trans_prepare(handle, &data), 0, "alpm_trans_prepare"); + tap_is_int(alpm_trans_commit(handle, &data), 0, "alpm_trans_commit"); + tap_is_int(alpm_trans_release(handle), 0, "alpm_trans_release"); + + tap_is_int(total_download, csize - csize / 2, "predicted download size"); + + tap_todo("use .part files in secondary caches"); + + tap_is_int(actual_download, csize - csize / 2, "actual download size"); + tap_ok(faccessat(pt->rootfd, "secondary/foo.pkg.tar", F_OK, 0) == 0, + "downloaded to secondary"); + tap_ok(faccessat(pt->rootfd, "secondary/foo.pkg.tar.part", F_OK, 0) == -1 + && errno == ENOENT, ".part file removed"); + tap_ok(faccessat(pt->rootfd, "primary/foo.pkg.tar", F_OK, 0) == -1 + && errno == ENOENT, "package not downloaded to primary"); + + tap_todo(NULL); + + return tap_finish(); +}