@@ -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:
@@ -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
new file mode 100644
@@ -0,0 +1,11 @@
+SUBDIRS = tests
+
+check_SCRIPTS = ptrun
+
+noinst_SCRIPTS = $(check_SCRIPTS)
+
+EXTRA_DIST = \
+ README \
+ $(check_SCRIPTS)
+
+# vim:set noet:
new file mode 100644
@@ -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"
+
+ /* <describe what is being tested> */
+
+ pt_env_t *pt = NULL;
+
+ void cleanup(void) {
+ pt_cleanup(pt);
+ }
+
+ int main(void) {
+ ASSERT(atexit(cleanup) == 0);
+
+ <setup test>
+
+ tap_plan(<number of tests>);
+ <perform test>
+
+ return tap_finish();
+ }
+
+.. vim: set ft=rst:
new file mode 100644
@@ -0,0 +1,14 @@
+#include "pactest.c"
+#include "ptserve.c"
+#include "tap.c"
+#include <alpm.h>
+#include <stdio.h>
+
+#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
new file mode 100644
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2015 Andrew Gregory <andrew.gregory.8@gmail.com>
+ *
+ * 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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <alpm.h>
+#include <archive.h>
+#include <archive_entry.h>
+
+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 */
new file mode 100755
@@ -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] <testfile> [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[@]}" "$@"
new file mode 100644
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2015 Andrew Gregory <andrew.gregory.8@gmail.com>
+ *
+ * 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 <arpa/inet.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include <alpm.h>
+
+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: */
new file mode 100644
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2014-2015 Andrew Gregory <andrew.gregory.8@gmail.com>
+ *
+ * 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 <inttypes.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+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 */
new file mode 100644
@@ -0,0 +1,3 @@
+.deps/
+.libs/
+*.t
new file mode 100644
@@ -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:
new file mode 100644
@@ -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
new file mode 100644
@@ -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();
+}
new file mode 100644
@@ -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();
+}
new file mode 100644
@@ -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();
+}