[Libguestfs] [PATCH 01/16] tests: Introduce test harness for running tests.

Richard W.M. Jones rjones at redhat.com
Tue Aug 4 13:19:51 UTC 2015


We would like to have a more flexible way to run tests, including
running them on an installed copy of libguestfs, running them in
parallel, and being able to express dependencies and ordering between
tests and data files properly.

Therefore introduce a test harness (test-harness) program which can
run tests either from the locally built copy, or from an installed
copy of the tests (in $libdir/guestfs/tests).

The test harness is backwards compatible on the command line, ie.
'make check', 'make -C inspector check-valgrind' etc. will still work.
But in addition, you can now run the tests on an installed copy of
libguestfs by doing:

  cd $libdir/guestfs/tests
  ./test-harness

The test-harness script supports various options, see 'test-harness(1)'
for full details.

Other notable features:

 - Checking SKIP_* environment variables in tests is no longer
   necessary.  The test harness deals with these.

 - Every test runs in its own temporary directory.  There is no need
   to clean up output files.  On the other hand, tests must use
   $srcdir, $builddir, $datadir etc. to refer to test data.

This is only implemented for a single directory at the moment
(ie. inspector/)
---
 .gitignore                                       |   6 +-
 Makefile.am                                      |  15 +
 common-rules.mk                                  |   3 +
 configure.ac                                     |   2 +
 generator/Makefile.am                            |  24 +
 generator/main.ml                                |   9 +
 generator/test_harness.ml                        | 731 +++++++++++++++++++++++
 generator/tests.ml                               |  69 +++
 generator/tests_mk.ml                            | 130 ++++
 generator/types.ml                               |  15 +
 inspector/Makefile.am                            |  17 +-
 inspector/test-virt-inspector-local-guests.sh.in |  26 +
 inspector/test-virt-inspector.sh                 |  12 +-
 inspector/test-xmllint.sh.in                     |   5 +
 inspector/tests.mk                               |  92 +++
 pick-guests.pl.in                                |   4 +-
 test-harness.pod                                 | 252 ++++++++
 tests/data/Makefile.am                           |  59 +-
 tests/guests/Makefile.am                         |  94 +--
 tests/guests/guest-aux/make-archlinux-img.sh     |   4 +-
 tests/guests/guest-aux/make-debian-img.sh        |   6 +-
 tests/guests/guest-aux/make-fedora-img.pl        |  21 +-
 tests/guests/guest-aux/make-ubuntu-img.sh        |   4 +-
 tests/guests/guest-aux/make-windows-img.sh       |   6 +-
 24 files changed, 1464 insertions(+), 142 deletions(-)
 create mode 100644 generator/test_harness.ml
 create mode 100644 generator/tests.ml
 create mode 100644 generator/tests_mk.ml
 create mode 100755 inspector/test-virt-inspector-local-guests.sh.in
 create mode 100644 inspector/tests.mk
 create mode 100644 test-harness.pod

diff --git a/.gitignore b/.gitignore
index 4645aa4..fa84e08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -275,8 +275,8 @@ Makefile.in
 /html/virt-v2v.1.html
 /html/virt-v2v-test-harness.1.html
 /html/virt-win-reg.1.html
-/inspector/actual-*.xml
 /inspector/stamp-virt-inspector.pod
+/inspector/test-virt-inspector-local-guests.sh
 /inspector/test-xmllint.sh
 /inspector/virt-inspector
 /inspector/virt-inspector.1
@@ -495,6 +495,8 @@ Makefile.in
 /sysprep/virt-sysprep.1
 /test.err
 /test.out
+/test-harness
+/test-harness.1
 /tests/c-api/test-add-drive-opts
 /tests/c-api/test-add-libvirt-dom
 /tests/c-api/test-backend-settings
@@ -548,7 +550,7 @@ Makefile.in
 /tests/guests/guest-aux/fedora-packages.db
 /tests/guests/guest-aux/windows-software
 /tests/guests/guest-aux/windows-system
-/tests/guests/stamp-fedora-md.img
+/tests/guests/stamp-guests
 /tests/guests/ubuntu.img
 /tests/guests/archlinux.img
 /tests/guests/coreos.img
diff --git a/Makefile.am b/Makefile.am
index 8f0bb1b..cc28bf8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -207,6 +207,7 @@ EXTRA_DIST = \
 	logo/virt-builder.svg \
 	m4/.gitignore \
 	ocaml-link.sh \
+	test-harness \
 	tests/run-xml-to-junit.sh \
 	tests/run-xml-to-junit.xsl \
 	tmp/.gitignore \
@@ -405,6 +406,16 @@ podwrapper.1: podwrapper.pl
 	  $<
 	mv $@-t $@
 
+# NB. test-harness is an internal tool, so the man page mustn't be installed.
+noinst_MANS += test-harness.1
+test-harness.1: test-harness.pod
+	$(PODWRAPPER) \
+	  --section 1 \
+	  --man $@-t \
+	  --license GPLv2+ \
+	  $<
+	mv $@-t $@
+
 # Make clean.
 
 CLEANFILES = \
@@ -414,6 +425,7 @@ CLEANFILES = \
 	podwrapper.1 \
 	qemu-wrapper.sh \
 	stamp-guestfs-release-notes.pod \
+	test-harness.1 \
 	tmp/disk* \
 	tmp/run-* \
 	tmp/valgrind-*.log
@@ -541,6 +553,9 @@ check-slow: build-test-guests
 build-test-guests:
 	$(MAKE) -C tests/guests check
 
+# Install valgrind suppressions file in test directory.
+alltests_DATA = valgrind-suppressions
+
 # Print subdirs.
 #
 # If you want to selectively run tests, or if the test suite fails half
diff --git a/common-rules.mk b/common-rules.mk
index 312107e..5a239ab 100644
--- a/common-rules.mk
+++ b/common-rules.mk
@@ -27,3 +27,6 @@ builddir     ?= @builddir@
 abs_builddir ?= @abs_builddir@
 srcdir       ?= @srcdir@
 abs_srcdir   ?= @abs_srcdir@
+
+# Tests directory.
+alltestsdir   = $(libdir)/guestfs/tests
diff --git a/configure.ac b/configure.ac
index 49a52f3..3698aac 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1700,6 +1700,8 @@ mkdir -p \
 dnl http://www.mail-archive.com/automake@gnu.org/msg10204.html
 AC_CONFIG_FILES([appliance/libguestfs-make-fixed-appliance],
                 [chmod +x,-w appliance/libguestfs-make-fixed-appliance])
+AC_CONFIG_FILES([inspector/test-virt-inspector-local-guests.sh],
+                [chmod +x,-w inspector/test-virt-inspector-local-guests.sh])
 AC_CONFIG_FILES([inspector/test-xmllint.sh],
                 [chmod +x,-w inspector/test-xmllint.sh])
 AC_CONFIG_FILES([p2v/virt-p2v-make-disk],
diff --git a/generator/Makefile.am b/generator/Makefile.am
index a3fe50d..1c16ad0 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -50,6 +50,9 @@ sources = \
 	ruby.ml \
 	structs.ml \
 	structs.mli \
+	tests.ml \
+	tests_mk.ml \
+	test_harness.ml \
 	tests_c_api.ml \
 	types.ml \
 	utils.ml \
@@ -68,6 +71,8 @@ objects = \
 	pr.cmo \
 	docstrings.cmo \
 	checks.cmo \
+	tests.cmo \
+	tests_mk.cmo \
 	c.cmo \
 	xdr.cmo \
 	daemon.cmo \
@@ -90,17 +95,30 @@ objects = \
 	customize.cmo \
 	main.cmo
 
+test_harness_objects = \
+	types.cmo \
+	utils.cmo \
+	tests.cmo \
+	test_harness.cmo
+
 EXTRA_DIST = $(sources) files-generated.txt
 
 OCAMLCFLAGS = $(OCAML_WARN_ERROR) -I $(srcdir) -I . -package unix,str
 
 noinst_PROGRAM = generator
 
+# Install the test harness.
+localtestsdir = $(alltestsdir)
+localtests_SCRIPTS = ../test-harness
+
 if HAVE_OCAML
 
 generator: $(objects)
 	$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -linkpkg $^ -o $@
 
+../test-harness: $(test_harness_objects)
+	$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -linkpkg $^ -o $@
+
 # Dependencies.
 %.cmi: %.mli
 	$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
@@ -133,6 +151,12 @@ generator:
 	chmod +x $@-t
 	mv $@-t $@
 
+../test-harness:
+	rm -f $@ $@-t
+	echo 'echo Warning: Install OCaml compiler in order to rebuild the test-harness.' > $@-t
+	chmod +x $@-t
+	mv $@-t $@
+
 endif
 
 noinst_DATA = stamp-generator
diff --git a/generator/main.ml b/generator/main.ml
index 94f0d09..23e9fbc 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -29,6 +29,8 @@ open Types
 open C
 open Xdr
 open Daemon
+open Tests
+open Tests_mk
 open Tests_c_api
 open Fish
 open Ocaml
@@ -47,6 +49,8 @@ open Bindtests
 open Errnostring
 open Customize
 
+let (//) = Filename.concat
+
 let perror msg = function
   | Unix_error (err, _, _) ->
       eprintf "%s: %s\n" msg (error_message err)
@@ -211,6 +215,11 @@ Run it from the top source directory using the command
   output_to "customize/customize-synopsis.pod" generate_customize_synopsis_pod;
   output_to "customize/customize-options.pod" generate_customize_options_pod;
 
+  List.iter (
+    fun (dir, tests) ->
+      output_to (dir // "tests.mk") (generate_tests_mk dir tests)
+  ) tests;
+
   (* Generate the list of files generated -- last. *)
   printf "generated %d lines of code\n" (get_lines_generated ());
   let files = List.sort compare (get_files_generated ()) in
diff --git a/generator/test_harness.ml b/generator/test_harness.ml
new file mode 100644
index 0000000..983d4f6
--- /dev/null
+++ b/generator/test_harness.ml
@@ -0,0 +1,731 @@
+(* libguestfs
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *)
+
+(* The test-harness standalone program. *)
+
+open Unix
+open Printf
+
+open Types
+open Tests
+
+let (//) = Filename.concat
+
+let isatty = isatty stdout
+
+(* ANSI terminal colours. *)
+let ansi_green ?(chan = Pervasives.stdout) () =
+  if isatty then output_string chan "\x1b[0;32m"
+let ansi_red ?(chan = Pervasives.stdout) () =
+  if isatty then output_string chan "\x1b[1;31m"
+let ansi_blue ?(chan = Pervasives.stdout) () =
+  if isatty then output_string chan "\x1b[1;34m"
+let ansi_restore ?(chan = Pervasives.stdout) () =
+  if isatty then output_string chan "\x1b[0m"
+
+let is_dir path =
+  try (stat path).st_kind = S_DIR
+  with Unix_error _ -> false
+
+let is_file path =
+  try (stat path).st_kind = S_REG
+  with Unix_error _ -> false
+
+let is_executable path =
+  is_file path &&
+    try access path [X_OK]; true
+    with Unix_error _ -> false
+
+let relative_path_to_absolute path =
+  let cmd = sprintf "realpath %s" (Filename.quote path) in
+  let chan = open_process_in cmd in
+  let path = input_line chan in
+  (match close_process_in chan with
+  | WEXITED 0 -> ()
+  | WEXITED _
+  | WSIGNALED _
+  | WSTOPPED _ ->
+    failwith "realpath command failed, see earlier errors"
+  );
+  path
+
+let get_lines cmd =
+  let chan = open_process_in cmd in
+  let rec loop acc =
+    try
+      let line = input_line chan in
+      loop (line :: acc)
+    with End_of_file -> List.rev acc
+  in
+  let lines = loop [] in
+  (match close_process_in chan with
+  | WEXITED 0 -> ()
+  | WEXITED _
+  | WSIGNALED _
+  | WSTOPPED _ ->
+    failwith (sprintf "get_lines: external command failed: %s"
+                cmd)
+  );
+  lines
+
+let mkdtemp () =
+  let chan = open_process_in "mktemp -d" in
+  let path = input_line chan in
+  (match close_process_in chan with
+  | WEXITED 0 -> ()
+  | WEXITED _
+  | WSIGNALED _
+  | WSTOPPED _ ->
+    failwith "mktemp -d command failed"
+  );
+  path
+
+type ('a, 'b) maybe = Either of 'a | Or of 'b
+
+let pushdir dir f =
+  let olddir = getcwd () in
+  chdir dir;
+  let r = try Either (f ()) with exn -> Or exn in
+  chdir olddir;
+  match r with
+  | Either r -> r
+  | Or exn -> raise exn
+
+(* Timeout settings. *)
+let timeout_period = "4h"
+and timeout_kill = "30s"
+
+type start_dir =
+| TopDir                                (* top dir - run all the tests *)
+| PhonyGuestsDir                        (* phony guests dir *)
+| Dir of string * test                  (* a test directory *)
+
+let () =
+  let home =
+    try Sys.getenv "HOME"
+    with Not_found ->
+      failwith "HOME environment variable is not set" in
+
+  let cachedir =
+    try Sys.getenv "XDG_CACHE_HOME"
+    with Not_found -> home // ".cache" in
+
+  let debug = ref false in
+  let direct = ref false in
+  let fast = ref false in
+  let libvirt = ref false in
+  let local_guests = ref false in
+  let make_phony_guests_only = ref false in
+  let slow = ref false in
+  let uml = ref false in
+  let upstream_libvirt = ref false in
+  let upstream_qemu = ref false in
+  let valgrind = ref false in
+  let verbose = ref false in
+  let uml_binary = ref (home // "d/linux-um/vmlinux") in
+  let libvirtdir = ref (home // "d/libvirt") in
+  let qemu_binary = ref (home // "d/qemu/x86_64-softmmu/qemu-system-x86_64") in
+
+  let argspec = Arg.align [
+    "--debug",        Arg.Set debug,      " Run tests with debugging enabled";
+    "--direct",       Arg.Set direct,     " Run tests using the direct backend";
+    "--fast",         Arg.Set fast,       " Run only tests which do not need the appliance";
+    "--libvirt",      Arg.Set libvirt,    " Run tests using the libvirt backend";
+    "--local-guests", Arg.Set local_guests, " Run tests that use locally installed guests r/o";
+    "--make-phony-guests-only", Arg.Set make_phony_guests_only, " Generate the phony guests used for testing";
+    "--slow",         Arg.Set slow,       " Run only long-running tests";
+    "--uml",          Arg.Set uml,        " Run tests using UML backend";
+    "--upstream-libvirt", Arg.Set upstream_libvirt, " Run tests using upstream libvirt";
+    "--upstream-qemu", Arg.Set upstream_qemu, " Run tests using upstream qemu";
+    "--valgrind",     Arg.Set valgrind,   " Run tests under valgrind";
+    "-v",             Arg.Set verbose,    " Enable verbose debugging in test harness";
+    "--verbose",      Arg.Set verbose,    " Enable verbose debugging in test harness";
+    "--with-uml",     Arg.Set_string uml_binary, "vmlinux Select UML binary";
+    "--with-upstream-libvirt", Arg.Set_string libvirtdir, "dir Select libvirt directory";
+    "--with-upstream-qemu", Arg.Set_string qemu_binary, "qemu Select qemu binary";
+  ] in
+  let args = ref [] in
+  let anon_fun s = args := s :: !args in
+  let usage_msg =
+    "test-harness: Run the libguestfs test suite, or parts of it.
+
+See test-harness(1) for full details on how to run this program.
+
+Usage:
+  test-harness [--options] [test-directory]
+
+Options:" in
+  Arg.parse argspec anon_fun usage_msg;
+
+  let args = !args in
+  let debug = !debug in
+  let direct = !direct in
+  let fast = !fast in
+  let libvirt = !libvirt in
+  let local_guests = !local_guests in
+  let make_phony_guests_only = !make_phony_guests_only in
+  let slow = !slow in
+  let uml = !uml in
+  let upstream_libvirt = !upstream_libvirt in
+  let upstream_qemu = !upstream_qemu in
+  let valgrind = !valgrind in
+  let verbose = !verbose in
+  let uml_binary = !uml_binary in
+  let libvirtdir = !libvirtdir in
+  let qemu_binary = !qemu_binary in
+
+  (* Some combinations are not permitted. *)
+  if (fast && slow) || (fast && local_guests) || (slow && local_guests) then
+    failwith "cannot use --fast, --slow and --local-guests options together";
+
+  (* If none of the selection options are used, default to fast + normal. *)
+  let fast, normal =
+    if not fast && not slow && not local_guests then true, true
+    else fast, false in
+
+  (* --direct, --libvirt and --uml cannot be combined. *)
+  if (direct && libvirt) || (direct && uml) || (libvirt && uml) then
+    failwith "cannot use --direct, --libvirt and --uml options together";
+
+  if verbose then (
+    printf "test-harness modes:%s%s%s%s%s%s%s\n"
+           (if fast then " --fast" else "")
+           (if normal then " --normal" else "")
+           (if slow then " --slow" else "")
+           (if local_guests then "--local-guests" else "")
+           (if direct then " --direct" else "")
+           (if libvirt then " --libvirt" else "")
+           (if uml then " --uml" else "")
+  );
+
+  (* If there is a single parameter on the command line, then we
+   * chdir to that directory.
+   *)
+  (match args with
+  | [] -> ()
+  | [dir] -> chdir dir
+  | _ ->
+    failwith "too many command line arguments"
+  );
+
+  (* Which directory are we running in? *)
+  let start_dir =
+    let pwd = getcwd () in
+    let basename = Filename.basename pwd in
+    if is_executable "test-harness" && is_dir "inspector" then
+      TopDir
+    else if basename = "guests" && is_dir "guest-aux" then
+      PhonyGuestsDir
+    else (
+      (* Search through the tests for the current directory. *)
+      try
+        let dir, tests =
+          List.find (
+            fun (dir, tests) ->
+              (* Find the name of any test that should be in the directory. *)
+              let any_file =
+                if tests.check <> [] then List.hd tests.check
+                else if tests.check_fast <> [] then List.hd tests.check_fast
+                else if tests.check_slow <> [] then List.hd tests.check_slow
+                else if tests.check_local_guests <> [] then
+                  List.hd tests.check_local_guests
+                else (
+                  assert (tests.check_data <> []);
+                  List.hd tests.check_data
+                ) in
+              basename = Filename.basename dir && is_file any_file
+          ) tests in
+        Dir (dir, tests)
+      with
+        Not_found ->
+          failwith (sprintf "current directory (%s) is not a libguestfs test directory" basename)
+    ) in
+
+  (* If we are running from automake, then automake will pass $srcdir
+   * to us, and if it's not "." then we have to adjust our path to the
+   * top source directory accordingly.
+   *)
+  let srcdir = try Sys.getenv "srcdir" with Not_found -> "." in
+
+  (* Are we running from the build directory or from installed tests? *)
+  let running_in_builddir = is_file (srcdir // "Makefile.am") in
+
+  (* If installed, then we cannot write to the phony guests directory. *)
+  let phonydir =
+    if running_in_builddir then (
+      match start_dir with
+      | TopDir ->
+        relative_path_to_absolute "tests/guests"
+      | PhonyGuestsDir ->
+        relative_path_to_absolute "."
+      | Dir (dir, _) ->
+        let top_builddir = ref ".." in
+        for i = 0 to String.length dir-1 do
+          if dir.[i] = '/' then top_builddir := !top_builddir // ".."
+        done;
+        let top_builddir = !top_builddir in
+        relative_path_to_absolute (top_builddir // "tests/guests")
+    )
+    else (
+      (try mkdir cachedir 0o755
+       with Unix_error _ -> ());
+      (try mkdir (cachedir // "libguestfs-tests") 0o755
+       with Unix_error _ -> ());
+      (try mkdir (cachedir // "libguestfs-tests/phony-guests") 0o755
+       with Unix_error _ -> ());
+      cachedir // "libguestfs-tests/phony-guests"
+    ) in
+
+  (* If the user gave the valgrind option, check it is valid. *)
+  if valgrind then (
+    if Sys.command "valgrind --help >/dev/null 2>&1" <> 0 then
+      failwith "valgrind is not installed"
+  );
+
+  (* Do we have libtool? *)
+  let have_libtool = Sys.command "libtool --help >/dev/null 2>&1" = 0 in
+
+  (* Do we have Padraig's timeout utility?  It must have the
+   * --foreground option (RHBZ#1025269) and the -k option (not present
+   * in RHEL 6).
+   *)
+  let timeout =
+    Sys.command "timeout --help >/dev/null 2>&1" = 0 &&
+    Sys.command "timeout --foreground 2 sleep 0 >/dev/null 2>&1" = 0 &&
+    Sys.command "timeout -k 10s 10s true >/dev/null 2>&1" = 0 in
+
+  (* Returns a relative path to top build directory from the current
+   * directory.
+   *)
+  let top_builddir () =
+    let rec loop i top_builddir =
+      if i > 10 then
+        failwith "top_builddir: not in a test directory"
+      else if is_executable (top_builddir // "test-harness") then
+        top_builddir
+      else if top_builddir = "." then
+        loop (i+1) ".."
+      else
+        loop (i+1) (top_builddir // "..")
+    in
+    loop 0 "."
+  in
+
+  (* Set up the environment (variables) for a test. *)
+  let set_up_environment () =
+    let abs_builddir = relative_path_to_absolute "." in
+    let abs_srcdir = relative_path_to_absolute srcdir in
+
+    let top_builddir = top_builddir () in
+    let top_srcdir =
+      if srcdir = "." then top_builddir
+      else top_builddir // srcdir in
+    let abs_top_builddir = relative_path_to_absolute top_builddir in
+    let abs_top_srcdir = relative_path_to_absolute top_srcdir in
+
+    let datadir = abs_top_builddir // "tests/data" in
+
+    if verbose then (
+      printf "abs_builddir=%s\n" abs_builddir;
+      printf "abs_srcdir=%s\n" abs_srcdir;
+      printf "abs_top_builddir=%s\n" abs_top_builddir;
+      printf "abs_top_srcdir=%s\n" abs_top_srcdir;
+      printf "phonydir=%s\n" phonydir;
+      printf "datadir=%s\n" datadir;
+    );
+
+    (* Note that because the tests always run in a temporary
+     * directory, relative paths are useless, so we always populate the
+     * environment with absolute paths.
+     *)
+    putenv "builddir" abs_builddir;
+    putenv "srcdir" abs_srcdir;
+    putenv "top_builddir" abs_top_builddir;
+    putenv "top_srcdir" abs_top_srcdir;
+    putenv "abs_builddir" abs_builddir;
+    putenv "abs_srcdir" abs_srcdir;
+    putenv "abs_top_builddir" abs_top_builddir;
+    putenv "abs_top_srcdir" abs_top_srcdir;
+    putenv "phonydir" phonydir;
+    putenv "datadir" datadir;
+
+    (* Debugging? *)
+    if debug then (
+      putenv "LIBGUESTFS_DEBUG" "1";
+      putenv "LIBGUESTFS_TRACE" "1"
+    );
+
+    (* Valgrind? *)
+    if valgrind then (
+      let vg =
+        sprintf "valgrind --vgdb=no --log-file=valgrind.log --leak-check=full --error-exitcode=119 --suppressions=%s/valgrind-suppressions"
+          abs_top_srcdir in
+      putenv "VG" vg
+    );
+
+    (* Direct backend? *)
+    if direct then
+      putenv "LIBGUESTFS_BACKEND" "direct"
+
+    (* Libvirt backend? *)
+    else if libvirt then
+      putenv "LIBGUESTFS_BACKEND" "libvirt"
+
+    (* UML? *)
+    else if uml then (
+      putenv "LIBGUESTFS_BACKEND" "uml";
+      putenv "LIBGUESTFS_HV" uml_binary
+    );
+
+    (* Upstream QEMU? *)
+    if upstream_qemu then
+      putenv "LIBGUESTFS_HV" qemu_binary;
+  in
+
+  (* Function which runs to create the phony guests for testing.
+   * Unfortunately we're recreating 'make' here.  This isn't really
+   * avoidable as we have to be able to run after installation, and
+   * it's inconvenient to create a Makefile for that.
+   *)
+  let rec make_phony_guests () =
+    (* Set up the environment as for tests ... *)
+    set_up_environment ();
+    (* ... but adjust srcdir to point to the tests/guests directory
+     * since the scripts in guest-aux are expecting this.
+     *)
+    let abs_top_srcdir = Sys.getenv "abs_top_srcdir" in
+    putenv "srcdir" (abs_top_srcdir // "tests/guests");
+    if verbose then
+      printf "srcdir [adjusted for make_phony_guests]=%s\n"
+             (Sys.getenv "srcdir");
+
+    pushdir phonydir (fun () ->
+      (* Because we distribute all the intermediate files, they are
+       * always(?)  in srcdir, not builddir.
+       *)
+      let gaux = abs_top_srcdir // "tests/guests/guest-aux" in
+      if verbose then
+        printf "gaux=%s\n" gaux;
+
+      (* Make several different blank images.  These are not guests, but we
+       * include them in the libvirt fake XML to make sure that virt-df and
+       * virt-alignment-scan don't break when they encounter them.
+       *)
+      List.iter (
+        fun ft ->
+          let o = sprintf "blank-%s.img" ft in
+          rule o [] (fun () ->
+            let cmd =
+              sprintf "guestfish -N %s-t=%s exit && mv %s-t %s" o ft o o in
+            if Sys.command cmd <> 0 then
+              failwith "guestfish command failed"
+          )
+      ) [ "disk"; "part"; "fs"; "bootroot"; "bootrootlv" ];
+
+      (* Make several (phony) Fedora images. *)
+      List.iter (
+        fun (layout, o) ->
+          rule o [ gaux // "make-fedora-img.pl";
+                   gaux // "fedora-journal.tar.xz";
+                   gaux // "fedora-name.db";
+                   gaux // "fedora-packages.db" ]
+            (fun () ->
+              let cmd = sprintf "%s/make-fedora-img.pl --layout=%s"
+                gaux layout in
+              if Sys.command cmd <> 0 then
+                failwith "make-fedora-img.pl failed"
+            )
+      ) [ "partitions", "fedora.img";
+          "partitions-md", "fedora-md1.img";
+          "btrfs", "fedora-btrfs.img" ];
+
+      (* Make a (phony) Arch image. *)
+      let o = "archlinux.img" in
+      rule o [ gaux // "make-archlinux-img.sh" ]
+        (fun () ->
+          let cmd = sprintf "%s/make-archlinux-img.sh" gaux in
+          if Sys.command cmd <> 0 then
+            failwith "make-archlinux-img.sh failed"
+        );
+
+      (* Make a (phony) CoreOS image. *)
+      let o = "coreos.img" in
+      rule o [ gaux // "make-coreos-img.sh" ]
+        (fun () ->
+          let cmd = sprintf "%s/make-coreos-img.sh" gaux in
+          if Sys.command cmd <> 0 then
+            failwith "make-coreos-img.sh failed"
+        );
+
+      (* Make a (phony) Debian image. *)
+      let o = "debian.img" in
+      rule o [ gaux // "make-debian-img.sh" ]
+        (fun () ->
+          let cmd = sprintf "%s/make-debian-img.sh" gaux in
+          if Sys.command cmd <> 0 then
+            failwith "make-debian-img.sh failed"
+        );
+
+      (* Make a (phony) Ubuntu image. *)
+      let o = "ubuntu.img" in
+      rule o [ gaux // "make-ubuntu-img.sh" ]
+        (fun () ->
+          let cmd = sprintf "%s/make-ubuntu-img.sh" gaux in
+          if Sys.command cmd <> 0 then
+            failwith "make-ubuntu-img.sh failed"
+        );
+
+      (* Make a (phony) Windows image. *)
+      let o = "windows.img" in
+      rule o [ gaux // "make-windows-img.sh";
+               gaux // "windows-software";
+               gaux // "windows-system" ]
+        (fun () ->
+          let cmd = sprintf "%s/make-windows-img.sh" gaux in
+          if Sys.command cmd <> 0 then
+            failwith "make-windows-img.sh failed"
+        );
+
+      (* Make XML describing all guests we managed to create above. *)
+      let cmd =
+        sprintf "%s/make-guests-all-good.pl > guests-all-good.xml blank-disk.img blank-part.img blank-fs.img blank-bootroot.img blank-bootrootlv.img archlinux.img coreos.img debian.img fedora.img fedora-md1.img fedora-md2.img fedora-btrfs.img ubuntu.img windows.img"
+          gaux in
+      if Sys.command cmd <> 0 then
+        failwith "make-guests-all-good.pl failed";
+
+      let cmd = "rm -f *.tmp.*" in
+      ignore (Sys.command cmd);
+    )
+
+  (* Like 'make', run a rule if target does not exist, or if target is
+   * older than any of the sources.
+   *)
+  and rule target sources f =
+    let target_stat =
+      try Some (stat target)
+      with Unix_error (ENOENT, _, _) ->
+        (* Target file does not exist. *)
+        printf "test-harness: building %s\n%!" target;
+        f (); None in
+    match target_stat with
+    | None -> ()
+    | Some target_stat ->
+      let rec loop = function
+        | [] -> ()
+        | source :: sources ->
+          let source_stat = stat source in
+          if target_stat.st_mtime < source_stat.st_mtime then (
+            printf "test-harness: building %s\n%!" target;
+            f ()
+          )
+          else
+            loop sources
+      in
+      loop sources
+  in
+
+  let null_results = (0, 0, 0, 0, 0) in
+  let add_results (t, s, f, tdo, sk) (t', s', f', tdo', sk') =
+    (t+t', s+s', f+f', tdo+tdo', sk+sk')
+  in
+
+  let rec run_all_tests () =
+    List.fold_left (
+      fun results (dir, tests) ->
+        printf "test-harness: entering directory %s\n%!" dir;
+        let results =
+          pushdir dir (fun () ->
+            add_results results (run_dir_tests dir tests)
+          ) in
+        printf "test-harness: leaving directory %s\n%!" dir;
+        results
+    ) null_results tests
+
+  (* Run all of the tests in a single directory.  'dir' is the
+   * directory name, and we are chdir'd into this directory
+   * already.
+   *)
+  and run_dir_tests dir tests =
+    let results = null_results in
+    let accumulate =
+      List.fold_left
+        (fun results t -> add_results results (run_one_test dir t []))
+    in
+    let results =
+      if fast then accumulate results tests.check_fast
+      else results in
+    let results =
+      if normal then accumulate results tests.check
+      else results in
+    let results =
+      if slow then accumulate results tests.check_slow
+      else results in
+    let results =
+      if local_guests then
+        List.fold_left (
+          fun results t ->
+            add_results results (run_local_guests_test dir t)
+        ) results tests.check_local_guests
+      else results in
+    results
+
+  (* Run a single test. *)
+  and run_one_test dir test args =
+    let skip_env = try Sys.getenv (skip_name test) with Not_found -> "" in
+    if skip_env = "1" then (
+      printf "SKIP: %s\n%!" test;
+      (1, 0, 0, 0, 1)
+    )
+    else (
+      set_up_environment ();
+
+      (* If it's a C program, and we're valgrinding, then we run the
+       * C program under valgrind directly.
+       *)
+      let is_program =
+        not (Filename.check_suffix test ".pl") &&
+        not (Filename.check_suffix test ".sh") in
+
+      (* We run each test in its own temporary directory. *)
+      let tmpdir = mkdtemp () in
+
+      let results, clean_up_tmpdir =
+        pushdir tmpdir (fun () ->
+          putenv "tmpdir" tmpdir;
+
+          let cmd =
+            sprintf "%s%s%s%s$abs_srcdir/%s%s > output 2>&1"
+              (if upstream_libvirt then libvirtdir // "run " else "")
+              (if timeout then
+                  sprintf "timeout --foreground -k %s %s "
+                    timeout_kill timeout_period
+               else "")
+              (if have_libtool then "libtool --mode=execute " else "")
+              (if is_program && valgrind then "$VG " else "")
+              test
+              (String.concat ""
+                 (List.map ((^) " ") (List.map Filename.quote args))) in
+
+          let start_t = gettimeofday () in
+          let r = Sys.command cmd in
+          let end_t = gettimeofday () in
+
+          let secs = end_t -. start_t in
+
+          match r with
+          | 0 ->                            (* successful *)
+            ansi_green ();
+            printf "PASS: %s (%.1f seconds)" test secs;
+            ansi_restore ();
+            print_newline ();
+            (1, 1, 0, 0, 0), true
+          | 77 ->                           (* skipped *)
+            ignore (Sys.command "cat output");
+            ansi_blue ();
+            printf "SKIP: %s" test;
+            ansi_restore ();
+            print_newline ();
+            (1, 0, 0, 0, 1), true
+          | 119 ->                          (* valgrind *)
+            ignore (Sys.command "cat output");
+            eprintf "test results left in %s\n" tmpdir;
+            ansi_blue ~chan:Pervasives.stderr ();
+            eprintf "VALGRIND FAIL: %s" test;
+            ansi_restore ~chan:Pervasives.stderr ();
+            prerr_newline ();
+            (1, 0, 1, 0, 0), false
+          | 124 ->                          (* timed out *)
+            ignore (Sys.command "cat output");
+            eprintf "command timed out after %s\n" timeout_period;
+            ansi_red ~chan:Pervasives.stderr ();
+            eprintf "TIMED OUT: %s" test;
+            ansi_restore ~chan:Pervasives.stderr ();
+            prerr_newline ();
+            (1, 0, 0, 1, 0), true
+          | r ->                            (* error *)
+            ignore (Sys.command "cat output");
+            eprintf "command failed with exit code %d\n" r;
+            eprintf "test results left in %s\n" tmpdir;
+            ansi_red ~chan:Pervasives.stderr ();
+            eprintf "FAIL: %s" test;
+            ansi_restore ~chan:Pervasives.stderr ();
+            prerr_newline ();
+            (1, 0, 1, 0, 0), false
+        ) in
+
+      if clean_up_tmpdir then
+        ignore (Sys.command (sprintf "rm -rf %s" (Filename.quote tmpdir)));
+
+      results
+    )
+
+  (* Run a single test using local guests. *)
+  and run_local_guests_test dir test =
+    let top_builddir = top_builddir () in
+    let cmd = sprintf "%s/pick-guests.pl 5" top_builddir in
+    let guests = get_lines cmd in
+    run_one_test dir test guests
+
+  (* Convert a test name like 'test-network.sh' into 'SKIP_TEST_NETWORK_SH'. *)
+  and skip_name name =
+    let skip = Utils.replace_char name '-' '_' in
+    let skip = Utils.replace_char skip '.' '_' in
+    let skip = "SKIP_" ^ String.uppercase skip in
+    skip
+  in
+
+  (* Make the phony guests? *)
+  if make_phony_guests_only || normal || slow then (
+    make_phony_guests ();
+    if make_phony_guests_only then exit 0
+  );
+
+  (* Run the tests. *)
+  let total, successful, failed, timedout, skipped =
+    match start_dir with
+    | TopDir -> run_all_tests ()
+    | Dir (dir, tests) -> run_dir_tests dir tests
+    | PhonyGuestsDir -> null_results (* do nothing in this directory *) in
+
+  (* Print the test results. *)
+  printf "--------------------------------------------------\n";
+  printf " TEST SUMMARY\n";
+  printf "--------------------------------------------------\n";
+  printf "Total tests run  . . . . . . .   %d\n" total;
+  ansi_green ();
+  printf "Successful . . . . . . . . . .   %d\n" successful;
+  ansi_restore ();
+  if failed > 0 then (
+    ansi_red ();
+    printf "Errors . . . . . . . . . . . .   %d\n" failed;
+    ansi_restore ()
+  );
+  if timedout > 0 then (
+    ansi_red ();
+    printf "Timed out  . . . . . . . . . .   %d\n" timedout;
+    ansi_restore ()
+  );
+  if skipped > 0 then (
+    ansi_blue ();
+    printf "Skipped  . . . . . . . . . . .   %d\n" skipped;
+    ansi_restore ()
+  );
+  printf "--------------------------------------------------\n";
+
+  (* If there were any errors, then exit with a failure. *)
+  exit (if failed = 0 && timedout = 0 then 0 else 1)
diff --git a/generator/tests.ml b/generator/tests.ml
new file mode 100644
index 0000000..3882a09
--- /dev/null
+++ b/generator/tests.ml
@@ -0,0 +1,69 @@
+(* libguestfs
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *)
+
+(* Please read generator/README first. *)
+
+open Printf
+
+open Types
+open Utils
+open Pr
+open Docstrings
+
+let defaults = {
+  check = []; check_fast = []; check_slow = []; check_local_guests = [];
+  check_data = []; check_scripts = []; check_programs = [];
+}
+
+(* The tests in each subdirectory. *)
+let tests = [
+  "inspector", {
+    defaults with
+      check = [
+        "test-virt-inspector.sh";
+      ];
+      check_fast = [
+        "test-xmllint.sh";
+      ];
+      check_local_guests = [
+        "test-virt-inspector-local-guests.sh";
+      ];
+      check_data = [
+        "example-debian-netinst-cd.xml";
+        "example-debian.xml";
+        "example-fedora-dvd.xml";
+        "example-fedora-netinst-cd.xml";
+        "example-fedora.xml";
+        "example-rhel-6-dvd.xml";
+        "example-rhel-6-netinst-cd.xml";
+        "example-rhel-6.xml";
+        "example-ubuntu-live-cd.xml";
+        "example-ubuntu.xml";
+        "example-windows-2003-x64-cd.xml";
+        "example-windows-2003-x86-cd.xml";
+        "example-windows.xml";
+        "example-windows-xp-cd.xml";
+        "expected-debian.img.xml";
+        "expected-fedora.img.xml";
+        "expected-ubuntu.img.xml";
+        "expected-windows.img.xml";
+        "virt-inspector.rng";
+      ]
+  };
+
+]
diff --git a/generator/tests_mk.ml b/generator/tests_mk.ml
new file mode 100644
index 0000000..e2705ce
--- /dev/null
+++ b/generator/tests_mk.ml
@@ -0,0 +1,130 @@
+(* libguestfs
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *)
+
+(* Please read generator/README first. *)
+
+open Printf
+
+open Types
+open Tests
+open Utils
+open Pr
+open Docstrings
+
+(* Generate the tests.mk files in each subdirectory. *)
+let generate_tests_mk dir tests () =
+  generate_header HashStyle GPLv2plus;
+
+  pr "localtestsdir = $(alltestsdir)/%s\n" dir;
+
+  if tests.check_data <> [] then (
+    pr "\n";
+    pr "localtests_DATA =";
+    List.iter (fun n -> pr " \\\n\t%s" n) (List.sort compare tests.check_data);
+    pr "\n";
+  );
+
+  (* Only add *.sh and *.pl to localtests_SCRIPTS.  Others are added to
+   * localtests_PROGRAMS.
+   *)
+  let test_scripts, test_programs =
+    List.partition (
+      fun file ->
+        Filename.check_suffix file ".sh" || Filename.check_suffix file ".pl"
+    ) (tests.check @ tests.check_fast @ tests.check_slow
+       @ tests.check_local_guests) in
+
+  (* Also, check_scripts get added to localtests_SCRIPTS. *)
+  let test_scripts = test_scripts @ tests.check_scripts in
+
+  (* Also, check_programs get added to localtests_PROGRAMS. *)
+  let test_programs = test_programs @ tests.check_programs in
+
+  if test_scripts <> [] then (
+    pr "\n";
+    pr "localtests_SCRIPTS =";
+    List.iter (fun n -> pr " \\\n\t%s" n) (List.sort compare test_scripts);
+    pr "\n"
+  );
+  if test_programs <> [] then (
+    pr "\n";
+    pr "localtests_PROGRAMS =";
+    List.iter (fun n -> pr " \\\n\t%s" n) (List.sort compare test_programs);
+    pr "\n"
+  );
+
+  (* Create rules so that 'make -C dir check' etc will do something. *)
+  if tests.check_fast <> [] || tests.check <> [] then (
+    pr "\n";
+    pr "# Note that we cannot create a simple 'check:' target since\n";
+    pr "# automake will (silently) overrule it, so we do this instead:\n";
+    pr "\n";
+    pr "TESTS_ENVIRONMENT = $(top_builddir)/run\n";
+    pr "TESTS = $(top_builddir)/test-harness\n";
+    pr "\n";
+    pr "check-valgrind:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --valgrind\n";
+    pr "\n";
+    pr "check-direct:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --direct\n";
+    pr "\n";
+    pr "check-valgrind-direct:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --valgrind --direct\n";
+    pr "\n";
+    pr "check-libvirt:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --libvirt\n";
+    pr "\n";
+    pr "check-valgrind-libvirt:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --valgrind --libvirt\n";
+    pr "\n";
+    pr "check-uml:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --uml\n";
+    pr "\n";
+    pr "check-valgrind-uml:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --valgrind --uml\n";
+    pr "\n";
+    pr "check-with-upstream-qemu:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --upstream-qemu\n";
+    pr "\n";
+    pr "check-with-upstream-libvirt:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --upstream-libvirt\n";
+  );
+
+  if tests.check_fast <> [] then (
+    pr "\n";
+    pr "check-fast:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --fast\n"
+  );
+
+  if tests.check_slow <> [] then (
+    pr "\n";
+    pr "check-slow:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --slow\n"
+  );
+
+  if tests.check_local_guests <> [] then (
+    pr "\n";
+    pr "check-local-guests:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --local-guests\n";
+    pr "\n";
+    pr "check-valgrind-local-guests:\n";
+    pr "\t$(top_builddir)/run $(top_builddir)/test-harness --valgrind --local-guests\n"
+  );
+
+  pr "\n";
+  pr "EXTRA_DIST += tests.mk\n"
diff --git a/generator/types.ml b/generator/types.ml
index 83a6a98..9083479 100644
--- a/generator/types.ml
+++ b/generator/types.ml
@@ -207,6 +207,21 @@ type fish_output_t =
   | FishOutputOctal       (* for int return, print in octal *)
   | FishOutputHexadecimal (* for int return, print in hex *)
 
+type test = {
+  check : string list;
+  (* "fast" here means the appliance is not needed *)
+  check_fast : string list;
+  check_slow : string list;
+  check_local_guests : string list;
+
+  (* Data files, non-executable. *)
+  check_data : string list;
+  (* Data files, scripts. *)
+  check_scripts : string list;
+  (* Data files, C programs. *)
+  check_programs : string list;
+}
+
 (* See guestfs(3)/EXTENDING LIBGUESTFS. *)
 type c_api_tests = (c_api_test_init * c_api_test_prereq * c_api_test * c_api_test_cleanup) list
 and c_api_test =
diff --git a/inspector/Makefile.am b/inspector/Makefile.am
index 9c79bed..1b4bfc7 100644
--- a/inspector/Makefile.am
+++ b/inspector/Makefile.am
@@ -17,6 +17,8 @@
 
 include $(top_srcdir)/subdir-rules.mk
 
+generator_built = tests.mk
+
 example_xml = \
 	example-debian.xml \
 	example-fedora.xml \
@@ -40,6 +42,7 @@ EXTRA_DIST = \
 	expected-archlinux.img.xml \
 	expected-coreos.img.xml \
 	expected-windows.img.xml \
+	test-virt-inspector-local-guests.sh \
 	test-virt-inspector.sh \
 	test-xmllint.sh.in \
 	virt-inspector.pod
@@ -103,16 +106,4 @@ stamp-virt-inspector.pod: virt-inspector.pod
 	  $<
 	touch $@
 
-TESTS_ENVIRONMENT = $(top_builddir)/run --test
-TESTS = test-virt-inspector.sh
-if HAVE_XMLLINT
-TESTS += test-xmllint.sh
-endif
-
-check-valgrind:
-	$(MAKE) TESTS="test-virt-inspector.sh" VG="$(top_builddir)/run @VG@" check
-
-check-valgrind-local-guests:
-	for g in $(GUESTS); do \
-	  $(top_builddir)/run --test @VG@ ./virt-inspector -c "$(libvirt_ro_uri)" -d "$$g" || exit $$?; \
-	done
+include $(srcdir)/tests.mk
diff --git a/inspector/test-virt-inspector-local-guests.sh.in b/inspector/test-virt-inspector-local-guests.sh.in
new file mode 100755
index 0000000..91f1a71
--- /dev/null
+++ b/inspector/test-virt-inspector-local-guests.sh.in
@@ -0,0 +1,26 @@
+#!/bin/bash -
+# libguestfs virt-inspector test script
+# @configure_input@
+# Copyright (C) 2012-2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+export LANG=C
+set -e
+set -x
+
+for g in "$@"; do
+    $VG virt-inspector -c "@libvirt_ro_uri@" -d "$g"
+done
diff --git a/inspector/test-virt-inspector.sh b/inspector/test-virt-inspector.sh
index 02bbcad..c8ca0e0 100755
--- a/inspector/test-virt-inspector.sh
+++ b/inspector/test-virt-inspector.sh
@@ -20,23 +20,17 @@ export LANG=C
 set -e
 set -x
 
-# Allow this test to be skipped.
-if [ -n "$SKIP_TEST_VIRT_INSPECTOR_SH" ]; then
-    echo "$0: skipping test because SKIP_TEST_VIRT_INSPECTOR_SH is set."
-    exit 77
-fi
-
 # ntfs-3g can't set UUIDs right now, so ignore just that <uuid>.
 diff_ignore="-I <uuid>[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]</uuid>"
 
-for f in ../tests/guests/{debian,fedora,ubuntu,archlinux,coreos,windows}.img; do
+for f in $phonydir/{debian,fedora,ubuntu,archlinux,coreos,windows}.img; do
     # Ignore zero-sized windows.img if ntfs-3g is not installed.
     if [ -s "$f" ]; then
         b=$(basename "$f" .xml)
-	$VG virt-inspector -a "$f" > "actual-$b.xml"
+	$VG virt-inspector -a "$f" > actual-$b.xml
         # This 'diff' command will fail (because of -e option) if there
         # are any differences.
-        diff -ur $diff_ignore "expected-$b.xml" "actual-$b.xml"
+        diff -ur $diff_ignore $srcdir/expected-$b.xml actual-$b.xml
     fi
 done
 
diff --git a/inspector/test-xmllint.sh.in b/inspector/test-xmllint.sh.in
index 65654bc..2755414 100755
--- a/inspector/test-xmllint.sh.in
+++ b/inspector/test-xmllint.sh.in
@@ -19,6 +19,11 @@
 export LANG=C
 set -e
 
+if ! "@XMLLINT@" --version >/dev/null 2>&1; then
+    echo "$0: test skipped before xmllint is not installed"
+    exit 77
+fi
+
 for f in $srcdir/example-*.xml; do
     @XMLLINT@ --noout --relaxng $srcdir/virt-inspector.rng $f
 done
diff --git a/inspector/tests.mk b/inspector/tests.mk
new file mode 100644
index 0000000..7f0668d
--- /dev/null
+++ b/inspector/tests.mk
@@ -0,0 +1,92 @@
+# libguestfs generated file
+# WARNING: THIS FILE IS GENERATED FROM:
+#   generator/ *.ml
+# ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.
+#
+# Copyright (C) 2009-2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+localtestsdir = $(alltestsdir)/inspector
+
+localtests_DATA = \
+	example-debian-netinst-cd.xml \
+	example-debian.xml \
+	example-fedora-dvd.xml \
+	example-fedora-netinst-cd.xml \
+	example-fedora.xml \
+	example-rhel-6-dvd.xml \
+	example-rhel-6-netinst-cd.xml \
+	example-rhel-6.xml \
+	example-ubuntu-live-cd.xml \
+	example-ubuntu.xml \
+	example-windows-2003-x64-cd.xml \
+	example-windows-2003-x86-cd.xml \
+	example-windows-xp-cd.xml \
+	example-windows.xml \
+	expected-debian.img.xml \
+	expected-fedora.img.xml \
+	expected-ubuntu.img.xml \
+	expected-windows.img.xml \
+	virt-inspector.rng
+
+localtests_SCRIPTS = \
+	test-virt-inspector-local-guests.sh \
+	test-virt-inspector.sh \
+	test-xmllint.sh
+
+# Note that we cannot create a simple 'check:' target since
+# automake will (silently) overrule it, so we do this instead:
+
+TESTS_ENVIRONMENT = $(top_builddir)/run
+TESTS = $(top_builddir)/test-harness
+
+check-valgrind:
+	$(top_builddir)/run $(top_builddir)/test-harness --valgrind
+
+check-direct:
+	$(top_builddir)/run $(top_builddir)/test-harness --direct
+
+check-valgrind-direct:
+	$(top_builddir)/run $(top_builddir)/test-harness --valgrind --direct
+
+check-libvirt:
+	$(top_builddir)/run $(top_builddir)/test-harness --libvirt
+
+check-valgrind-libvirt:
+	$(top_builddir)/run $(top_builddir)/test-harness --valgrind --libvirt
+
+check-uml:
+	$(top_builddir)/run $(top_builddir)/test-harness --uml
+
+check-valgrind-uml:
+	$(top_builddir)/run $(top_builddir)/test-harness --valgrind --uml
+
+check-with-upstream-qemu:
+	$(top_builddir)/run $(top_builddir)/test-harness --upstream-qemu
+
+check-with-upstream-libvirt:
+	$(top_builddir)/run $(top_builddir)/test-harness --upstream-libvirt
+
+check-fast:
+	$(top_builddir)/run $(top_builddir)/test-harness --fast
+
+check-local-guests:
+	$(top_builddir)/run $(top_builddir)/test-harness --local-guests
+
+check-valgrind-local-guests:
+	$(top_builddir)/run $(top_builddir)/test-harness --valgrind --local-guests
+
+EXTRA_DIST += tests.mk
diff --git a/pick-guests.pl.in b/pick-guests.pl.in
index bfc97d8..14feae6 100755
--- a/pick-guests.pl.in
+++ b/pick-guests.pl.in
@@ -56,7 +56,5 @@ $n = @accessible if @accessible < $n;
 
 # Return the first n guests from the list.
 for (my $i = 0; $i < $n; ++$i) {
-    print " " if $i > 0;
-    print $accessible[$i];
+    print $accessible[$i], "\n"
 }
-print "\n";
diff --git a/test-harness.pod b/test-harness.pod
new file mode 100644
index 0000000..b08c8b0
--- /dev/null
+++ b/test-harness.pod
@@ -0,0 +1,252 @@
+=head1 NAME
+
+test-harness - Run the libguestfs test suite, or parts of it.
+
+=head1 SYNOPSIS
+
+ test-harness [--options] [test-directory]
+
+=head1 DESCRIPTION
+
+This program runs the libguestfs test suite, or parts of it.  You can
+run it either on a just-built libguestfs source tree, or on the
+installed libguestfs and virt-* tools.
+
+Normally tests in the current directory are run.  If the current
+directory is the top level of the test suite then the whole test suite
+is run.  If the current directory is a leaf directory, then only tests
+in that directory are run.
+
+If you specify a directory name on the command line, then we run tests
+from that directory instead.
+
+=head1 OPTIONS
+
+=head2 Options to select which tests are run
+
+If neither I<--fast> nor I<--slow>, then I<--fast> + normal tests are run.
+
+=over 4
+
+=item B<--fast>
+
+Run only tests which do not need the appliance.
+
+=item B<--slow>
+
+Run only long-running tests.
+
+=item B<--local-guests>
+
+Run tests that use locally installed guests read-only.
+
+=back
+
+=head2 Options to run the selected tests under different test conditions
+
+=over 4
+
+=item B<--debug>
+
+Run tests with debugging enabled [recommended].
+
+=item B<--valgrind>
+
+Run tests under valgrind.
+
+=item B<--direct>
+
+Run tests using the direct backend.
+
+=item B<--libvirt>
+
+Run tests using the libvirt backend.
+
+=item B<--uml>
+
+Run tests using the UML backend.
+
+=item B<--with-uml VMLINUX>
+
+Select UML binary [default: F<$HOME/d/linux-um/vmlinux>].
+
+=item B<--upstream-libvirt>
+
+Run tests using upstream libvirt.
+
+=item B<--with-upstream-libvirt LIBVIRTDIR>
+
+Select libvirt directory [default: F<$HOME/d/libvirt>].
+
+=item B<--upstream-qemu>
+
+Run tests using upstream qemu.
+
+=item B<--with-upstream-qemu QEMUBINARY>
+
+Select qemu binary
+[default: F<$HOME/d/qemu/x86_64-softmmu/qemu-system-x86_64>].
+
+=back
+
+=head2 Options used internally
+
+=over 4
+
+=item B<--make-phony-guests-only>
+
+Generate the phony guests used for testing.
+
+=back
+
+=head2 Other options
+
+=over 4
+
+=item B<--help>
+
+Display short help and exit.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enables some verbose debugging in the test-harness program itself.
+
+=back
+
+=head1 TEST ENVIRONMENT
+
+=head2 Temporary directory
+
+The test-harness creates a temporary directory for each test and sets
+the test current working directory there.  Any temporary files needed
+by the test should be created in the current (ie. temporary)
+directory.
+
+The temporary directory and its contents are deleted after the test
+has run, except where the test failed.
+
+=head2 Environment variables
+
+When running each test, test-harness passes various environment
+variables:
+
+=over 4
+
+=item C<builddir>
+
+=item C<top_builddir>
+
+=item C<abs_builddir>
+
+=item C<abs_top_builddir>
+
+The path to the build directory and the top build directory.
+
+For example, if running tests in F<libguestfs.git/tests/events> then
+C<builddir> would point to F<libguestfs.git/tests/events>, and
+C<top_builddir> would point to F<libguestfs.git>.
+
+The C<abs_*> variants are absolute paths.  (In fact because of the
+current directory being the temporary directory, all environment
+variables contain absolute paths).
+
+=item C<srcdir>
+
+=item C<top_srcdir>
+
+=item C<abs_srcdir>
+
+=item C<abs_top_srcdir>
+
+If you are not using separate source and build directories, then these
+environment variables are the same as the C<builddir> variants above.
+
+If you are using separate source and build directories, then these
+point to the source directories.
+
+=item C<phonydir>
+
+The phony test guests (see F<libguestfs.git/tests/guests>).
+
+=item C<datadir>
+
+The data files (see F<libguestfs.git/tests/data>).
+
+=item C<tmpdir>
+
+The temporary directory created for the test.  This is the same as the
+current working directory when the test starts.
+
+=item C<LIBGUESTFS_DEBUG=1>
+
+=item C<LIBGUESTFS_TRACE=1>
+
+If the I<--debug> flag was passed, then these environment variables
+are set.
+
+=item C<LIBGUESTFS_BACKEND=(direct|libvirt|uml)>
+
+If using the I<--direct>, I<--libvirt> or I<--uml> flags, then the
+backend environment variable is set appropriately.
+
+=item C<LIBGUESTFS_HV=>(qemu|uml)
+
+Points to the qemu or UML hypervisor.
+
+=back
+
+=head2 Skipping tests
+
+You can skip tests by setting C<SKIP_*> environment variables.  The
+environment variable name is formed by converting all characters to
+uppercase and replacing non-alphanumeric characters with C<_>.  For
+example, F<test-network.sh> becomes C<SKIP_TEST_NETWORK_SH>.
+
+Tests can also skip themselves (eg. if they cannot run with the
+requested backend, or if a data file is missing).  The test should
+print a diagnostic message and exit with code C<77>.
+
+=head2 Exit code of the test
+
+The test should exit with one of the following exit codes:
+
+=over 4
+
+=item C<0>
+
+Test successful.
+
+=item C<1>-C<76>
+
+The test failed.
+
+=item C<77>
+
+The test was skipped.
+
+=item C<119>
+
+L<valgrind(1)> returns this code to indicate a valgrind failure.
+
+=item C<124>
+
+L<timeout(1)> returns this code if the test timed out.
+
+=back
+
+=head1 SEE ALSO
+
+L<automake(1)>,
+L<timeout(1)>,
+L<valgrind(1)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHOR
+
+Richard W.M. Jones.
+
+=head1 COPYRIGHT
+
+Copyright (C) 2014-2015 Red Hat Inc.
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 305136f..d379826 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -19,13 +19,6 @@ include $(top_srcdir)/subdir-rules.mk
 
 EXTRA_DIST = \
 	README-binfiles \
-	filesanddirs-10M.tar.xz \
-	filesanddirs-100M.tar.xz \
-	helloworld.tar \
-	helloworld.tar.gz \
-	helloworld.tar.xz \
-	mbr-ext2-empty.img.gz \
-	empty known-1 known-2 known-3 known-4 known-5 \
 	bin-aarch64-dynamic \
 	bin-armv7-dynamic \
 	bin-i586-dynamic \
@@ -37,6 +30,13 @@ EXTRA_DIST = \
 	bin-win32.exe \
 	bin-win64.exe \
 	bin-x86_64-dynamic \
+	empty \
+	filesanddirs-10M.tar.xz \
+	filesanddirs-100M.tar.xz \
+	helloworld.tar \
+	helloworld.tar.gz \
+	helloworld.tar.xz \
+	known-1 known-2 known-3 known-4 known-5 \
 	lib-aarch64.so \
 	lib-armv7.so \
 	lib-i586.so \
@@ -48,19 +48,11 @@ EXTRA_DIST = \
 	lib-win32.dll \
 	lib-win64.dll \
 	lib-x86_64.so \
-	test-grep.txt \
-	minimal
+	mbr-ext2-empty.img.gz \
+	minimal \
+	test-grep.txt
 
 images_files_src = \
-	$(srcdir)/helloworld.tar \
-	$(srcdir)/helloworld.tar.gz \
-	$(srcdir)/helloworld.tar.xz \
-	$(srcdir)/empty \
-	$(srcdir)/known-1 \
-	$(srcdir)/known-2 \
-	$(srcdir)/known-3 \
-	$(srcdir)/known-4 \
-	$(srcdir)/known-5 \
 	$(srcdir)/bin-aarch64-dynamic \
 	$(srcdir)/bin-armv7-dynamic \
 	$(srcdir)/bin-i586-dynamic \
@@ -72,6 +64,17 @@ images_files_src = \
 	$(srcdir)/bin-win32.exe \
 	$(srcdir)/bin-win64.exe \
 	$(srcdir)/bin-x86_64-dynamic \
+	$(srcdir)/empty \
+	$(srcdir)/filesanddirs-10M.tar.xz \
+	$(srcdir)/filesanddirs-100M.tar.xz \
+	$(srcdir)/helloworld.tar \
+	$(srcdir)/helloworld.tar.gz \
+	$(srcdir)/helloworld.tar.xz \
+	$(srcdir)/known-1 \
+	$(srcdir)/known-2 \
+	$(srcdir)/known-3 \
+	$(srcdir)/known-4 \
+	$(srcdir)/known-5 \
 	$(srcdir)/lib-aarch64.so \
 	$(srcdir)/lib-armv7.so \
 	$(srcdir)/lib-i586.so \
@@ -83,8 +86,8 @@ images_files_src = \
 	$(srcdir)/lib-win32.dll \
 	$(srcdir)/lib-win64.dll \
 	$(srcdir)/lib-x86_64.so \
-	$(srcdir)/test-grep.txt \
-	$(srcdir)/minimal
+	$(srcdir)/minimal \
+	$(srcdir)/test-grep.txt
 
 images_files_build = \
 	100kallzeroes \
@@ -107,12 +110,14 @@ images_files_build = \
 	lib-i586.so.xz \
 	test-grep.txt.gz
 
-check_DATA = $(images_files_build) test.iso
-
-CLEANFILES = $(images_files_build) test.iso
-
 images_files = $(images_files_src) $(images_files_build)
 
+localtestsdir = $(alltestsdir)/tests/data
+
+localtests_DATA = $(images_files) test.iso
+
+CLEANFILES = $(images_files_build) test.iso
+
 test.iso: $(images_files)
 	rm -f $@ $@-t
 	mkdir -p directory
@@ -156,21 +161,21 @@ test.iso: $(images_files)
 # Blank disk images in various sizes and formats.
 blank-disk-1s.raw:
 	rm -f $@
-	../../fish/guestfish sparse $@ 512
+	truncate -s 512 $@
 
 blank-disk-1s.qcow2:
 	qemu-img create -f qcow2 -o preallocation=metadata $@ 512
 
 blank-disk-1K.raw:
 	rm -f $@
-	../../fish/guestfish sparse $@ 1K
+	truncate -s 1K $@
 
 blank-disk-1K.qcow2:
 	qemu-img create -f qcow2 -o preallocation=metadata $@ 1K
 
 blank-disk-1M.raw:
 	rm -f $@
-	../../fish/guestfish sparse $@ 1M
+	truncate -s 1M $@
 
 blank-disk-1M.qcow2:
 	qemu-img create -f qcow2 -o preallocation=metadata $@ 1M
diff --git a/tests/guests/Makefile.am b/tests/guests/Makefile.am
index 6ada4ec..0060a82 100644
--- a/tests/guests/Makefile.am
+++ b/tests/guests/Makefile.am
@@ -60,70 +60,36 @@ disk_images = \
 # time and we need the tools we have built in order to make it.
 check_DATA = $(disk_images) guests-all-good.xml
 
-CLEANFILES = $(check_DATA) \
-	guests-all-good.xml \
-	stamp-fedora-md.img \
-	*.tmp.*
+$(disk_images) guests-all-good.xml: stamp-guests
 
-# Make several different blank images.  These are not guests, but we
-# include them in the libvirt fake XML to make sure that virt-df and
-# virt-alignment-scan don't break when they encounter them.
-blank-%.img:
-	rm -f $@ $@-t
-	$(top_builddir)/run \
-	  ../../fish/guestfish \
-	    -N $@-t="$$(echo $@ | $(SED) -e 's/blank-//' -e 's/.img//')" exit
-	mv $@-t $@
-
-# Make a (dummy) Fedora image.
-fedora.img: guest-aux/make-fedora-img.pl \
-		guest-aux/fedora-journal.tar.xz \
-		guest-aux/fedora-name.db \
-		guest-aux/fedora-packages.db
-	SRCDIR=$(srcdir) LAYOUT=partitions $(top_builddir)/run --test $<
-
-# Make a (dummy) Fedora image using md devices
-fedora-md1.img fedora-md2.img: stamp-fedora-md.img
-
-stamp-fedora-md.img: guest-aux/make-fedora-img.pl \
-		guest-aux/fedora-journal.tar.xz \
-		guest-aux/fedora-name.db \
-		guest-aux/fedora-packages.db
-	rm -f $@
-	SRCDIR=$(srcdir) LAYOUT=partitions-md $(top_builddir)/run --test $<
+stamp-guests: $(top_builddir)/test-harness
+	$(top_builddir)/run $< --make-phony-guests-only
 	touch $@
 
-fedora-btrfs.img: guest-aux/make-fedora-img.pl \
-		guest-aux/fedora-journal.tar.xz \
-		guest-aux/fedora-name.db \
-		guest-aux/fedora-packages.db
-	SRCDIR=$(srcdir) LAYOUT=btrfs $(top_builddir)/run --test $<
+CLEANFILES = \
+	$(check_DATA) \
+	stamp-guests \
+	*.tmp.*
 
-# Make a (dummy) Debian image.
-debian.img: guest-aux/make-debian-img.sh
-	SRCDIR=$(srcdir) $(top_builddir)/run --test $<
-
-# Make a (dummy) Ubuntu image.
-ubuntu.img: guest-aux/make-ubuntu-img.sh
-	SRCDIR=$(srcdir) $(top_builddir)/run --test $<
-
-# Make a (dummy) Arch Linux image.
-archlinux.img: guest-aux/make-archlinux-img.sh
-	SRCDIR=$(srcdir) $(top_builddir)/run --test $<
-
-# Make a (dummy) CoreOS image.
-coreos.img: guest-aux/make-coreos-img.sh
-	SRCDIR=$(srcdir) $(top_builddir)/run --test $<
-
-# Make a (dummy) Windows image.
-windows.img: guest-aux/make-windows-img.sh \
-	     guest-aux/windows-software guest-aux/windows-system
-	SRCDIR=$(srcdir) $(top_builddir)/run --test $<
-
-guests-all-good.xml: guest-aux/make-guests-all-good.pl $(disk_images)
-	rm -f $@ $@-t
-	$^ > $@-t
-	mv $@-t $@
+# For installed tests.
+localtestsdir = $(alltestsdir)/tests/guests/guest-aux
+localtests_DATA = \
+	guest-aux/debian-packages \
+	guest-aux/debian-syslog \
+	guest-aux/fedora-journal.tar.xz \
+	guest-aux/fedora-name.db \
+	guest-aux/fedora-packages.db \
+	guest-aux/minimal-hive \
+	guest-aux/windows-software \
+	guest-aux/windows-system
+localtests_SCRIPTS = \
+	guest-aux/make-archlinux-img.sh \
+	guest-aux/make-coreos-img.sh \
+	guest-aux/make-debian-img.sh \
+	guest-aux/make-fedora-img.pl \
+	guest-aux/make-guests-all-good.pl \
+	guest-aux/make-ubuntu-img.sh \
+	guest-aux/make-windows-img.sh
 
 # Since users might not have the tools needed to create this, we also
 # distribute these files and they are only cleaned by 'make distclean'
@@ -155,11 +121,3 @@ DISTCLEANFILES = \
 	guest-aux/fedora-packages.db \
 	guest-aux/windows-software \
 	guest-aux/windows-system
-
-# Don't construct the guests in parallel.  In automake 1.13, check_DATA
-# was changed so it can now run in parallel, but this causes everything
-# to fall over on machines with limited memory.
-#
-# ALSO: the guestfish rules above for making the blank-*.img files are
-# NOT safe to run in parallel.
-.NOTPARALLEL:
diff --git a/tests/guests/guest-aux/make-archlinux-img.sh b/tests/guests/guest-aux/make-archlinux-img.sh
index 62babd1..54a27b0 100755
--- a/tests/guests/guest-aux/make-archlinux-img.sh
+++ b/tests/guests/guest-aux/make-archlinux-img.sh
@@ -47,9 +47,9 @@ write /etc/fstab "/dev/sda1 / ext4 rw,relatime,data=ordered 0 1"
 touch /etc/arch-release
 write /etc/hostname "archlinux.test"
 
-upload $SRCDIR/guest-aux/archlinux-package /var/lib/pacman/local/test-package-1:0.1-1/desc
+upload $srcdir/guest-aux/archlinux-package /var/lib/pacman/local/test-package-1:0.1-1/desc
 
-upload $SRCDIR/../data/bin-x86_64-dynamic /bin/ls
+upload $datadir/../data/bin-x86_64-dynamic /bin/ls
 
 mkdir /boot/grub
 touch /boot/grub/grub.conf
diff --git a/tests/guests/guest-aux/make-debian-img.sh b/tests/guests/guest-aux/make-debian-img.sh
index 95228ab..af251b4 100755
--- a/tests/guests/guest-aux/make-debian-img.sh
+++ b/tests/guests/guest-aux/make-debian-img.sh
@@ -82,11 +82,11 @@ upload fstab.tmp.$$ /etc/fstab
 write /etc/debian_version "5.0.1"
 write /etc/hostname "debian.invalid"
 
-upload $SRCDIR/guest-aux/debian-packages /var/lib/dpkg/status
+upload $srcdir/guest-aux/debian-packages /var/lib/dpkg/status
 
-upload $SRCDIR/../data/bin-x86_64-dynamic /bin/ls
+upload $datadir/../data/bin-x86_64-dynamic /bin/ls
 
-upload $SRCDIR/guest-aux/debian-syslog /var/log/syslog
+upload $srcdir/guest-aux/debian-syslog /var/log/syslog
 
 mkdir /boot/grub
 touch /boot/grub/grub.conf
diff --git a/tests/guests/guest-aux/make-fedora-img.pl b/tests/guests/guest-aux/make-fedora-img.pl
index eae11d6..c85271f 100755
--- a/tests/guests/guest-aux/make-fedora-img.pl
+++ b/tests/guests/guest-aux/make-fedora-img.pl
@@ -44,11 +44,12 @@ my $g = Sys::Guestfs->new ();
 
 my $bootdev;
 
-foreach ('LAYOUT', 'SRCDIR') {
-  defined ($ENV{$_}) or die "Missing environment variable: $_";
+if (@ARGV < 1 || $ARGV[0] !~ /^--layout=(.*)/) {
+    die "usage: $0 --layout=(partitions|partitions-md|btrfs)\n"
 }
+my $layout = $1;
 
-if ($ENV{LAYOUT} eq 'partitions') {
+if ($layout eq 'partitions') {
   push (@images, "fedora.img.tmp.$$");
 
   open (my $fstab, '>', "fstab.tmp.$$") or die;
@@ -73,7 +74,7 @@ EOF
   init_lvm_root ('/dev/sda2');
 }
 
-elsif ($ENV{LAYOUT} eq 'partitions-md') {
+elsif ($layout eq 'partitions-md') {
   push (@images, "fedora-md1.img.tmp.$$", "fedora-md2.img.tmp.$$");
 
   open (my $fstab, '>', "fstab.tmp.$$") or die;
@@ -122,7 +123,7 @@ EOF
   init_lvm_root ('/dev/md/root');
 }
 
-elsif ($ENV{LAYOUT} eq 'btrfs') {
+elsif ($layout eq 'btrfs') {
   push (@images, "fedora-btrfs.img.tmp.$$");
 
   open (my $fstab, '>', "fstab.tmp.$$") or die;
@@ -154,7 +155,7 @@ EOF
 }
 
 else {
-  print STDERR "$0: Unknown LAYOUT: ",$ENV{LAYOUT},"\n";
+  print STDERR "$0: Unknown layout: ",$layout,"\n";
   exit 1;
 }
 
@@ -217,12 +218,12 @@ if (-f "mdadm.tmp.$$") {
   unlink ("mdadm.tmp.$$") or die;
 }
 
-$g->upload ($ENV{SRCDIR}.'/guest-aux/fedora-name.db', '/var/lib/rpm/Name');
-$g->upload ($ENV{SRCDIR}.'/guest-aux/fedora-packages.db', '/var/lib/rpm/Packages');
+$g->upload ($ENV{srcdir}.'/guest-aux/fedora-name.db', '/var/lib/rpm/Name');
+$g->upload ($ENV{srcdir}.'/guest-aux/fedora-packages.db', '/var/lib/rpm/Packages');
 
-$g->upload ($ENV{SRCDIR}.'/../data/bin-x86_64-dynamic', '/bin/ls');
+$g->upload ($ENV{datadir}.'/../data/bin-x86_64-dynamic', '/bin/ls');
 
-$g->txz_in ($ENV{SRCDIR}.'/guest-aux/fedora-journal.tar.xz', '/var/log/journal');
+$g->txz_in ($ENV{srcdir}.'/guest-aux/fedora-journal.tar.xz', '/var/log/journal');
 
 $g->mkdir ('/boot/grub');
 $g->touch ('/boot/grub/grub.conf');
diff --git a/tests/guests/guest-aux/make-ubuntu-img.sh b/tests/guests/guest-aux/make-ubuntu-img.sh
index 183985b..36abe55 100755
--- a/tests/guests/guest-aux/make-ubuntu-img.sh
+++ b/tests/guests/guest-aux/make-ubuntu-img.sh
@@ -73,9 +73,9 @@ write /etc/debian_version "5.0.1"
 upload release.tmp.$$ /etc/lsb-release
 write /etc/hostname "ubuntu.invalid"
 
-upload $SRCDIR/guest-aux/debian-packages /var/lib/dpkg/status
+upload $srcdir/guest-aux/debian-packages /var/lib/dpkg/status
 
-upload $SRCDIR/../data/bin-x86_64-dynamic /bin/ls
+upload $datadir/../data/bin-x86_64-dynamic /bin/ls
 
 mkdir /boot/grub
 touch /boot/grub/grub.conf
diff --git a/tests/guests/guest-aux/make-windows-img.sh b/tests/guests/guest-aux/make-windows-img.sh
index cfe59dc..ad3201a 100755
--- a/tests/guests/guest-aux/make-windows-img.sh
+++ b/tests/guests/guest-aux/make-windows-img.sh
@@ -59,10 +59,10 @@ mount /dev/sda2 /
 mkdir-p /Windows/System32/Config
 mkdir-p /Windows/System32/Drivers
 
-upload $SRCDIR/guest-aux/windows-software /Windows/System32/Config/SOFTWARE
-upload $SRCDIR/guest-aux/windows-system /Windows/System32/Config/SYSTEM
+upload $srcdir/guest-aux/windows-software /Windows/System32/Config/SOFTWARE
+upload $srcdir/guest-aux/windows-system /Windows/System32/Config/SYSTEM
 
-upload $SRCDIR/../data/bin-win32.exe /Windows/System32/cmd.exe
+upload $datadir/../data/bin-win32.exe /Windows/System32/cmd.exe
 
 mkdir "/Program Files"
 touch /autoexec.bat
-- 
2.5.0




More information about the Libguestfs mailing list