From patchwork Sun Apr 23 23:05:30 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Gregory X-Patchwork-Id: 143 Return-Path: Delivered-To: patchwork@archlinux.org Received: from nymeria.archlinux.org by nymeria.archlinux.org (Dovecot) with LMTP id fXSzNFAz/VjAYgAAtiB/HQ for ; Mon, 24 Apr 2017 01:05:52 +0200 Received: from nymeria.archlinux.org (localhost.localdomain [127.0.0.1]) by nymeria.archlinux.org (Postfix) with ESMTP id 3B60140190; Mon, 24 Apr 2017 01:05:49 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on nymeria.archlinux.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=2.5 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED,FREEMAIL_FROM,RCVD_IN_DNSWL_MED,T_DKIM_INVALID shortcircuit=no autolearn=unavailable autolearn_force=no version=3.4.1 Received: from luna.archlinux.org (luna.archlinux.org [5.9.250.164]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by nymeria.archlinux.org (Postfix) with ESMTPS; Mon, 24 Apr 2017 01:05:49 +0200 (CEST) Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id CF0DC2350D; Sun, 23 Apr 2017 23:05:48 +0000 (UTC) Authentication-Results: luna.archlinux.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=aj9UUw9H Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id B9C882340A for ; Sun, 23 Apr 2017 23:05:45 +0000 (UTC) Received: from nymeria.archlinux.org (nymeria.archlinux.org [IPv6:2a00:1828:2000:547::2]) by luna.archlinux.org (Postfix) with ESMTPS for ; Sun, 23 Apr 2017 23:05:45 +0000 (UTC) Received: from nymeria.archlinux.org (localhost.localdomain [127.0.0.1]) by nymeria.archlinux.org (Postfix) with ESMTP id 58F0F40190 for ; Mon, 24 Apr 2017 01:05:40 +0200 (CEST) Received: from mail-yw0-x22e.google.com (mail-yw0-x22e.google.com [IPv6:2607:f8b0:4002:c05::22e]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by nymeria.archlinux.org (Postfix) with ESMTPS for ; Mon, 24 Apr 2017 01:05:39 +0200 (CEST) Received: by mail-yw0-x22e.google.com with SMTP id k11so24020575ywb.1 for ; Sun, 23 Apr 2017 16:05:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=hTkcoWcs7g+83byJohhnXCm/rlooMqHlc8DngoyXK7s=; b=aj9UUw9HgqUd7ff+3VYPRglza64xmLwADtMV7R7nD+A8hXKhfgqQ8JpKDijKCkKRTo TcCSzs83LsJ+Ne0wXN0In1RwCNOt0+7fcHO3Gwi2nQHgVE8M0pzU0HSGSvjKOn4dg70L aN29FlcOBwOnPdhcYjFlf4bibi8im5sKRQJZT+OGsmBIWvRybM0Z1XCeqvL65RkKJZh0 Tt1bIH9tT7bHrq3VZAGZemvOMrQoRs8r+NKjJaXiOkXuocqOgZ5vKtgYizoMTZ0o1pOb tY9IqzHH6ugJ7GLWfMA/DKswRKmBTnzf/Od5v3/XCD6qQVsxcIhTzsvJDiWEPiMpLULg RycA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=hTkcoWcs7g+83byJohhnXCm/rlooMqHlc8DngoyXK7s=; b=PbSD07S6zdeZp3l+XaNYP7On8Y446WufV26aryKOMQo+peGufgqmefmxYzyrG0zqjr H4t8IYYdhpj88FDnmcv1Kltfn7sdrE1V0O+Y+/ruGOZrgA2qK432MAI8OicXbp3i/9Sl EiSmff0SFEE4Dm77FbAQiaMcS6XCRX7CYSmTJx8qW+Xd7/MZnS35kqg88VVBYIJkl+Nr FCOHY5hGviHsWorAYf6W5KUHiP+9OlYejciZ/32la49+Dy1B08NE76TpuRKa0BBmTjM1 fLwZRdhElJPMKqtEH67qEyGvMT7F0+ZkkmzTpWw3NAZDDQPulDX1w/JyV7yibqs9/dsG oApw== X-Gm-Message-State: AN3rC/7BGA9517XxG8Cl68YGtU/CSuc0Z3nMhWJ6Ks6MMu2EIhB41uZn lOzVBozytI8XiQ== X-Received: by 10.129.158.143 with SMTP id v137mr2929718ywg.128.1492988737801; Sun, 23 Apr 2017 16:05:37 -0700 (PDT) Received: from b42-desktop.local (cpe-76-182-123-164.nc.res.rr.com. [76.182.123.164]) by smtp.gmail.com with ESMTPSA id 203sm3487517ywn.20.2017.04.23.16.05.36 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sun, 23 Apr 2017 16:05:36 -0700 (PDT) From: Andrew Gregory To: pacman-dev@archlinux.org Date: Sun, 23 Apr 2017 19:05:30 -0400 Message-Id: <20170423230530.25945-1-andrew.gregory.8@gmail.com> X-Mailer: git-send-email 2.12.2 Subject: [pacman-dev] [PATCH][WIP] add alpm tests X-BeenThere: pacman-dev@archlinux.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Discussion list for pacman development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Discussion list for pacman development Cc: Andrew Gregory Errors-To: pacman-dev-bounces@archlinux.org Sender: "pacman-dev" X-UID: 341 Status: X-Keywords: Content-Length: 54408 --- For alpm tests, we use compiled tests using pactest.c to setup package/db files and temporary working environments, ptserve.c to run an http server for sync packages, and tap.c to generate the test output. Everything is already working, but I would appreciate feedback on the following in particular: 1. autotools - I have no idea if I got all of the autotools nonsense correct. 2. pactest.c API - Being C programs these are always going to be more verbose and have more boilerplate than our python test suite, but any suggestions for how to improve the pactest.c API to make writing tests easier are welcome. 3. git submodules - tap.c, pactest.c, and ptserve.c are projects that I'm managing externally, just like tap.sh. This patch just includes static copies of them; should we use git submodules instead? Makefile.am | 7 +- configure.ac | 2 + test/alpm/Makefile.am | 11 + test/alpm/README | 108 ++++++ test/alpm/alpmtest.h | 14 + test/alpm/pactest.c | 458 +++++++++++++++++++++++++ test/alpm/ptrun | 58 ++++ test/alpm/ptserve.c | 403 ++++++++++++++++++++++ test/alpm/tap.c | 305 ++++++++++++++++ test/alpm/tests/.gitignore | 3 + test/alpm/tests/Makefile.am | 21 ++ test/alpm/tests/TESTS | 3 + test/alpm/tests/add_remove.c | 56 +++ test/alpm/tests/cached_part_file.c | 65 ++++ test/alpm/tests/part_file_in_secondary_cache.c | 102 ++++++ 15 files changed, 1615 insertions(+), 1 deletion(-) create mode 100644 test/alpm/Makefile.am create mode 100644 test/alpm/README create mode 100644 test/alpm/alpmtest.h create mode 100644 test/alpm/pactest.c create mode 100755 test/alpm/ptrun create mode 100644 test/alpm/ptserve.c create mode 100644 test/alpm/tap.c create mode 100644 test/alpm/tests/.gitignore create mode 100644 test/alpm/tests/Makefile.am create mode 100644 test/alpm/tests/TESTS create mode 100644 test/alpm/tests/add_remove.c create mode 100644 test/alpm/tests/cached_part_file.c create mode 100644 test/alpm/tests/part_file_in_secondary_cache.c 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(); +}