From 83846468fd394de4928dfedb61278638b1178328 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Wed, 26 Dec 2012 13:36:03 +0100
Subject: [PATCH] Move htest/ files under the test/ tree
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

htest/data becomes test/data/htools (basically reverting commit
8feabc89), and htest/* becomes test/hs/*.

Most changes beside the rename are trivial s/…/…, with the exception
of autotools/run-in-tempdir, which needed some more changes now that
test/ is not just Python files:

- test/py is still being copied
- test/hs moves from individual symlinks to entire dir symlink
- test/data is symlinked in its entirety

Checked with make distcheck, pep8 and pylint, so at least VPATH builds
are OK.

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>
---
 .ghci                                         |   2 +-
 .gitignore                                    |  24 +-
 Makefile.am                                   | 216 +++++++++---------
 autotools/run-in-tempdir                      |  19 +-
 doc/devnotes.rst                              |   2 +-
 htest/hpc-htools.hs                           |   1 -
 htest/hpc-mon-collector.hs                    |   1 -
 htest/shelltests/htools-basic.test            |  43 ----
 htest/shelltests/htools-dynutil.test          |  19 --
 htest/shelltests/htools-excl.test             |  15 --
 htest/shelltests/htools-hspace.test           |   8 -
 htest/shelltests/htools-single-group.test     |  37 ---
 .../data/htools}/clean-nonzero-score.data     |   0
 .../data/htools}/common-suffix.data           |   0
 .../data/htools}/empty-cluster.data           |   0
 .../data/htools}/hail-alloc-drbd.json         |   0
 .../data/htools}/hail-change-group.json       |   0
 .../data/htools}/hail-invalid-reloc.json      |   0
 .../data/htools}/hail-node-evac.json          |   0
 .../data/htools}/hail-reloc-drbd.json         |   0
 .../data/htools}/hbal-excl-tags.data          |   0
 .../data/htools}/hbal-split-insts.data        |   0
 .../data/htools}/invalid-node.data            |   0
 .../data/htools}/missing-resources.data       |   0
 .../data => test/data/htools}/n1-failure.data |   0
 .../data/htools}/rapi/groups.json             |   0
 .../data => test/data/htools}/rapi/info.json  |   0
 .../data/htools}/rapi/instances.json          |   0
 .../data => test/data/htools}/rapi/nodes.json |   0
 {htest => test/hs}/Test/Ganeti/Attoparsec.hs  |   0
 {htest => test/hs}/Test/Ganeti/BasicTypes.hs  |   0
 .../hs}/Test/Ganeti/Block/Drbd/Parser.hs      |   0
 .../hs}/Test/Ganeti/Block/Drbd/Types.hs       |   0
 {htest => test/hs}/Test/Ganeti/Common.hs      |   0
 {htest => test/hs}/Test/Ganeti/Confd/Types.hs |   0
 {htest => test/hs}/Test/Ganeti/Confd/Utils.hs |   0
 {htest => test/hs}/Test/Ganeti/Daemon.hs      |   0
 {htest => test/hs}/Test/Ganeti/Errors.hs      |   0
 .../hs}/Test/Ganeti/HTools/Backend/Simu.hs    |   0
 .../hs}/Test/Ganeti/HTools/Backend/Text.hs    |   0
 {htest => test/hs}/Test/Ganeti/HTools/CLI.hs  |   0
 .../hs}/Test/Ganeti/HTools/Cluster.hs         |   0
 .../hs}/Test/Ganeti/HTools/Container.hs       |   0
 .../hs}/Test/Ganeti/HTools/Graph.hs           |   0
 .../hs}/Test/Ganeti/HTools/Instance.hs        |   0
 .../hs}/Test/Ganeti/HTools/Loader.hs          |   0
 {htest => test/hs}/Test/Ganeti/HTools/Node.hs |   0
 .../hs}/Test/Ganeti/HTools/PeerMap.hs         |   0
 .../hs}/Test/Ganeti/HTools/Types.hs           |   0
 {htest => test/hs}/Test/Ganeti/JQueue.hs      |   0
 {htest => test/hs}/Test/Ganeti/JSON.hs        |   0
 {htest => test/hs}/Test/Ganeti/Jobs.hs        |   0
 {htest => test/hs}/Test/Ganeti/Luxi.hs        |   0
 {htest => test/hs}/Test/Ganeti/Network.hs     |   0
 {htest => test/hs}/Test/Ganeti/Objects.hs     |   2 +-
 {htest => test/hs}/Test/Ganeti/OpCodes.hs     |   0
 .../hs}/Test/Ganeti/Query/Filter.hs           |   0
 .../hs}/Test/Ganeti/Query/Language.hs         |   0
 {htest => test/hs}/Test/Ganeti/Query/Query.hs |   0
 {htest => test/hs}/Test/Ganeti/Rpc.hs         |   0
 {htest => test/hs}/Test/Ganeti/Ssconf.hs      |   0
 {htest => test/hs}/Test/Ganeti/THH.hs         |   0
 {htest => test/hs}/Test/Ganeti/TestCommon.hs  |   2 +-
 {htest => test/hs}/Test/Ganeti/TestHTools.hs  |   0
 {htest => test/hs}/Test/Ganeti/TestHelper.hs  |   0
 .../hs}/Test/Ganeti/TestImports.hs.in         |   0
 {htest => test/hs}/Test/Ganeti/Types.hs       |   0
 {htest => test/hs}/Test/Ganeti/Utils.hs       |   0
 {htest => test/hs}/cli-tests-defs.sh          |   4 +-
 test/hs/hpc-htools.hs                         |   1 +
 test/hs/hpc-mon-collector.hs                  |   1 +
 {htest => test/hs}/live-test.sh               |   0
 {htest => test/hs}/offline-test.sh            |  14 +-
 .../hs}/shelltests/htools-balancing.test      |  40 ++--
 test/hs/shelltests/htools-basic.test          |  43 ++++
 test/hs/shelltests/htools-dynutil.test        |  19 ++
 test/hs/shelltests/htools-excl.test           |  15 ++
 .../hs}/shelltests/htools-hail.test           |  34 +--
 test/hs/shelltests/htools-hspace.test         |   8 +
 .../hs}/shelltests/htools-invalid.test        |  26 +--
 .../hs}/shelltests/htools-mon-collector.test  |  24 +-
 .../hs}/shelltests/htools-multi-group.test    |  24 +-
 .../hs}/shelltests/htools-no-backend.test     |  12 +-
 .../hs}/shelltests/htools-rapi.test           |   4 +-
 test/hs/shelltests/htools-single-group.test   |  37 +++
 .../hs}/shelltests/htools-text-backend.test   |  14 +-
 {htest => test/hs}/test.hs                    |   0
 87 files changed, 355 insertions(+), 356 deletions(-)
 delete mode 120000 htest/hpc-htools.hs
 delete mode 120000 htest/hpc-mon-collector.hs
 delete mode 100644 htest/shelltests/htools-basic.test
 delete mode 100644 htest/shelltests/htools-dynutil.test
 delete mode 100644 htest/shelltests/htools-excl.test
 delete mode 100644 htest/shelltests/htools-hspace.test
 delete mode 100644 htest/shelltests/htools-single-group.test
 rename {htest/data => test/data/htools}/clean-nonzero-score.data (100%)
 rename {htest/data => test/data/htools}/common-suffix.data (100%)
 rename {htest/data => test/data/htools}/empty-cluster.data (100%)
 rename {htest/data => test/data/htools}/hail-alloc-drbd.json (100%)
 rename {htest/data => test/data/htools}/hail-change-group.json (100%)
 rename {htest/data => test/data/htools}/hail-invalid-reloc.json (100%)
 rename {htest/data => test/data/htools}/hail-node-evac.json (100%)
 rename {htest/data => test/data/htools}/hail-reloc-drbd.json (100%)
 rename {htest/data => test/data/htools}/hbal-excl-tags.data (100%)
 rename {htest/data => test/data/htools}/hbal-split-insts.data (100%)
 rename {htest/data => test/data/htools}/invalid-node.data (100%)
 rename {htest/data => test/data/htools}/missing-resources.data (100%)
 rename {htest/data => test/data/htools}/n1-failure.data (100%)
 rename {htest/data => test/data/htools}/rapi/groups.json (100%)
 rename {htest/data => test/data/htools}/rapi/info.json (100%)
 rename {htest/data => test/data/htools}/rapi/instances.json (100%)
 rename {htest/data => test/data/htools}/rapi/nodes.json (100%)
 rename {htest => test/hs}/Test/Ganeti/Attoparsec.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/BasicTypes.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Block/Drbd/Parser.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Block/Drbd/Types.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Common.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Confd/Types.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Confd/Utils.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Daemon.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Errors.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Backend/Simu.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Backend/Text.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/CLI.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Cluster.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Container.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Graph.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Instance.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Loader.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Node.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/PeerMap.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/HTools/Types.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/JQueue.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/JSON.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Jobs.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Luxi.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Network.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Objects.hs (99%)
 rename {htest => test/hs}/Test/Ganeti/OpCodes.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Query/Filter.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Query/Language.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Query/Query.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Rpc.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Ssconf.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/THH.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/TestCommon.hs (99%)
 rename {htest => test/hs}/Test/Ganeti/TestHTools.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/TestHelper.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/TestImports.hs.in (100%)
 rename {htest => test/hs}/Test/Ganeti/Types.hs (100%)
 rename {htest => test/hs}/Test/Ganeti/Utils.hs (100%)
 rename {htest => test/hs}/cli-tests-defs.sh (92%)
 create mode 120000 test/hs/hpc-htools.hs
 create mode 120000 test/hs/hpc-mon-collector.hs
 rename {htest => test/hs}/live-test.sh (100%)
 rename {htest => test/hs}/offline-test.sh (85%)
 rename {htest => test/hs}/shelltests/htools-balancing.test (66%)
 create mode 100644 test/hs/shelltests/htools-basic.test
 create mode 100644 test/hs/shelltests/htools-dynutil.test
 create mode 100644 test/hs/shelltests/htools-excl.test
 rename {htest => test/hs}/shelltests/htools-hail.test (57%)
 create mode 100644 test/hs/shelltests/htools-hspace.test
 rename {htest => test/hs}/shelltests/htools-invalid.test (57%)
 rename {htest => test/hs}/shelltests/htools-mon-collector.test (93%)
 rename {htest => test/hs}/shelltests/htools-multi-group.test (52%)
 rename {htest => test/hs}/shelltests/htools-no-backend.test (67%)
 rename {htest => test/hs}/shelltests/htools-rapi.test (57%)
 create mode 100644 test/hs/shelltests/htools-single-group.test
 rename {htest => test/hs}/shelltests/htools-text-backend.test (55%)
 rename {htest => test/hs}/test.hs (100%)

diff --git a/.ghci b/.ghci
index 825dbc44e..13c79f5f4 100644
--- a/.ghci
+++ b/.ghci
@@ -1 +1 @@
-:set -isrc -ihtest
+:set -isrc -itest/hs
diff --git a/.gitignore b/.gitignore
index e9ed7e74c..db07174a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -87,17 +87,17 @@
 /man/*.gen
 /man/footer.man
 
-# htest
-/htest/hail
-/htest/hbal
-/htest/hcheck
-/htest/hinfo
-/htest/hroller
-/htest/hscan
-/htest/hspace
-/htest/hpc-htools
-/htest/hpc-mon-collector
-/htest/test
+# test/hs
+/test/hs/hail
+/test/hs/hbal
+/test/hs/hcheck
+/test/hs/hinfo
+/test/hs/hroller
+/test/hs/hscan
+/test/hs/hspace
+/test/hs/hpc-htools
+/test/hs/hpc-mon-collector
+/test/hs/test
 
 # tools
 /tools/kvm-ifup
@@ -129,4 +129,4 @@
 # automatically-built Haskell files
 /src/Ganeti/Constants.hs
 /src/Ganeti/Version.hs
-/htest/Test/Ganeti/TestImports.hs
+/test/hs/Test/Ganeti/TestImports.hs
diff --git a/Makefile.am b/Makefile.am
index 0b0bfe715..83d3507a0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -65,15 +65,15 @@ HS_DIRS = \
 	src/Ganeti/HTools/Backend \
 	src/Ganeti/HTools/Program \
 	src/Ganeti/Query \
-	htest \
-	htest/Test \
-	htest/Test/Ganeti \
-	htest/Test/Ganeti/Block \
-	htest/Test/Ganeti/Block/Drbd \
-	htest/Test/Ganeti/Confd \
-	htest/Test/Ganeti/HTools \
-	htest/Test/Ganeti/HTools/Backend \
-	htest/Test/Ganeti/Query
+	test/hs \
+	test/hs/Test \
+	test/hs/Test/Ganeti \
+	test/hs/Test/Ganeti/Block \
+	test/hs/Test/Ganeti/Block/Drbd \
+	test/hs/Test/Ganeti/Confd \
+	test/hs/Test/Ganeti/HTools \
+	test/hs/Test/Ganeti/HTools/Backend \
+	test/hs/Test/Ganeti/Query
 
 DIRS = \
 	$(HS_DIRS) \
@@ -84,9 +84,9 @@ DIRS = \
 	doc/examples \
 	doc/examples/gnt-debug \
 	doc/examples/hooks \
-	htest/data \
-	htest/data/rapi \
-	htest/shelltests \
+	test/data/htools \
+	test/data/htools/rapi \
+	test/hs/shelltests \
 	lib \
 	lib/build \
 	lib/client \
@@ -184,7 +184,7 @@ CLEANFILES = \
 	$(HS_ALL_PROGS) $(HS_BUILT_SRCS) \
 	$(HS_BUILT_TEST_HELPERS) \
 	src/ganeti-confd \
-	.hpc/*.mix src/*.tix htest/*.tix \
+	.hpc/*.mix src/*.tix test/hs/*.tix \
 	doc/hs-lint.html
 
 GENERATED_FILES = \
@@ -415,14 +415,14 @@ HS_HTOOLS_PROGS = $(HS_BIN_ROLES) hail
 
 HS_ALL_PROGS = \
 	$(HS_PROGS) \
-	htest/hpc-htools \
-	htest/hpc-mon-collector \
-	htest/test \
+	test/hs/hpc-htools \
+	test/hs/hpc-mon-collector \
+	test/hs/test \
 	src/hconfd \
 	src/rpc-test
 
 HS_PROG_SRCS = $(patsubst %,%.hs,$(HS_ALL_PROGS))
-HS_BUILT_TEST_HELPERS = $(HS_BIN_ROLES:%=htest/%) htest/hail
+HS_BUILT_TEST_HELPERS = $(HS_BIN_ROLES:%=test/hs/%) test/hs/hail
 
 HFLAGS = \
 	-O -Wall -Werror -isrc \
@@ -432,7 +432,7 @@ HFLAGS = \
 
 # extra flags that can be overriden on the command line (e.g. -Wwarn, etc.)
 HEXTRA =
-# internal extra flags (used for htest/test mainly)
+# internal extra flags (used for test/hs/test mainly)
 HEXTRA_INT =
 # exclude options for coverage reports
 HPCEXCL = --exclude Main \
@@ -515,49 +515,49 @@ HS_LIB_SRCS = \
 	src/Ganeti/Utils.hs
 
 HS_TEST_SRCS = \
-	htest/Test/Ganeti/Attoparsec.hs \
-	htest/Test/Ganeti/BasicTypes.hs \
-	htest/Test/Ganeti/Block/Drbd/Parser.hs \
-	htest/Test/Ganeti/Block/Drbd/Types.hs \
-	htest/Test/Ganeti/Common.hs \
-	htest/Test/Ganeti/Confd/Types.hs \
-	htest/Test/Ganeti/Confd/Utils.hs \
-	htest/Test/Ganeti/Daemon.hs \
-	htest/Test/Ganeti/Errors.hs \
-	htest/Test/Ganeti/HTools/Backend/Simu.hs \
-	htest/Test/Ganeti/HTools/Backend/Text.hs \
-	htest/Test/Ganeti/HTools/CLI.hs \
-	htest/Test/Ganeti/HTools/Cluster.hs \
-	htest/Test/Ganeti/HTools/Container.hs \
-	htest/Test/Ganeti/HTools/Graph.hs \
-	htest/Test/Ganeti/HTools/Instance.hs \
-	htest/Test/Ganeti/HTools/Loader.hs \
-	htest/Test/Ganeti/HTools/Node.hs \
-	htest/Test/Ganeti/HTools/PeerMap.hs \
-	htest/Test/Ganeti/HTools/Types.hs \
-	htest/Test/Ganeti/JSON.hs \
-	htest/Test/Ganeti/Jobs.hs \
-	htest/Test/Ganeti/JQueue.hs \
-	htest/Test/Ganeti/Luxi.hs \
-	htest/Test/Ganeti/Network.hs \
-	htest/Test/Ganeti/Objects.hs \
-	htest/Test/Ganeti/OpCodes.hs \
-	htest/Test/Ganeti/Query/Filter.hs \
-	htest/Test/Ganeti/Query/Language.hs \
-	htest/Test/Ganeti/Query/Query.hs \
-	htest/Test/Ganeti/Rpc.hs \
-	htest/Test/Ganeti/Ssconf.hs \
-	htest/Test/Ganeti/THH.hs \
-	htest/Test/Ganeti/TestCommon.hs \
-	htest/Test/Ganeti/TestHTools.hs \
-	htest/Test/Ganeti/TestHelper.hs \
-	htest/Test/Ganeti/Types.hs \
-	htest/Test/Ganeti/Utils.hs
+	test/hs/Test/Ganeti/Attoparsec.hs \
+	test/hs/Test/Ganeti/BasicTypes.hs \
+	test/hs/Test/Ganeti/Block/Drbd/Parser.hs \
+	test/hs/Test/Ganeti/Block/Drbd/Types.hs \
+	test/hs/Test/Ganeti/Common.hs \
+	test/hs/Test/Ganeti/Confd/Types.hs \
+	test/hs/Test/Ganeti/Confd/Utils.hs \
+	test/hs/Test/Ganeti/Daemon.hs \
+	test/hs/Test/Ganeti/Errors.hs \
+	test/hs/Test/Ganeti/HTools/Backend/Simu.hs \
+	test/hs/Test/Ganeti/HTools/Backend/Text.hs \
+	test/hs/Test/Ganeti/HTools/CLI.hs \
+	test/hs/Test/Ganeti/HTools/Cluster.hs \
+	test/hs/Test/Ganeti/HTools/Container.hs \
+	test/hs/Test/Ganeti/HTools/Graph.hs \
+	test/hs/Test/Ganeti/HTools/Instance.hs \
+	test/hs/Test/Ganeti/HTools/Loader.hs \
+	test/hs/Test/Ganeti/HTools/Node.hs \
+	test/hs/Test/Ganeti/HTools/PeerMap.hs \
+	test/hs/Test/Ganeti/HTools/Types.hs \
+	test/hs/Test/Ganeti/JSON.hs \
+	test/hs/Test/Ganeti/Jobs.hs \
+	test/hs/Test/Ganeti/JQueue.hs \
+	test/hs/Test/Ganeti/Luxi.hs \
+	test/hs/Test/Ganeti/Network.hs \
+	test/hs/Test/Ganeti/Objects.hs \
+	test/hs/Test/Ganeti/OpCodes.hs \
+	test/hs/Test/Ganeti/Query/Filter.hs \
+	test/hs/Test/Ganeti/Query/Language.hs \
+	test/hs/Test/Ganeti/Query/Query.hs \
+	test/hs/Test/Ganeti/Rpc.hs \
+	test/hs/Test/Ganeti/Ssconf.hs \
+	test/hs/Test/Ganeti/THH.hs \
+	test/hs/Test/Ganeti/TestCommon.hs \
+	test/hs/Test/Ganeti/TestHTools.hs \
+	test/hs/Test/Ganeti/TestHelper.hs \
+	test/hs/Test/Ganeti/Types.hs \
+	test/hs/Test/Ganeti/Utils.hs
 
 HS_LIBTEST_SRCS = $(HS_LIB_SRCS) $(HS_TEST_SRCS)
 
 HS_BUILT_SRCS = \
-	htest/Test/Ganeti/TestImports.hs \
+	test/hs/Test/Ganeti/TestImports.hs \
 	src/Ganeti/Constants.hs \
 	src/Ganeti/Version.hs
 HS_BUILT_SRCS_IN = $(patsubst %,%.in,$(HS_BUILT_SRCS))
@@ -684,17 +684,17 @@ $(HS_ALL_PROGS): %: %.hs $(HS_LIBTEST_SRCS) $(HS_BUILT_SRCS) Makefile
 	  $(HEXTRA) $(HEXTRA_INT) $@
 	@touch "$@"
 
-# for the htest/test binary, we need to enable profiling/coverage
-htest/test: HEXTRA_INT=-fhpc -ihtest
+# for the test/hs/test binary, we need to enable profiling/coverage
+test/hs/test: HEXTRA_INT=-fhpc -itest/hs
 
 # we compile the hpc-htools binary with the program coverage
-htest/hpc-htools: HEXTRA_INT=-fhpc
+test/hs/hpc-htools: HEXTRA_INT=-fhpc
 
 # we compile the hpc-mon-collector binary with the program coverage
-htest/hpc-mon-collector: HEXTRA_INT=-fhpc
+test/hs/hpc-mon-collector: HEXTRA_INT=-fhpc
 
 # test dependency
-htest/offline-tests.sh: htest/hpc-htools htest/hpc-mon-collector
+test/hs/offline-tests.sh: test/hs/hpc-htools test/hs/hpc-mon-collector
 
 # rules for building profiling-enabled versions of the haskell
 # programs: hs-prof does the full two-step build, whereas
@@ -828,8 +828,8 @@ EXTRA_DIST = \
 	$(HS_LIBTEST_SRCS) $(HS_BUILT_SRCS_IN) \
 	$(HS_PROG_SRCS) \
 	src/lint-hints.hs \
-	htest/cli-tests-defs.sh \
-	htest/offline-test.sh \
+	test/hs/cli-tests-defs.sh \
+	test/hs/offline-test.sh \
 	.ghci
 
 man_MANS = \
@@ -872,36 +872,36 @@ maninput = \
 	man/footer.man man/footer.html $(mangen)
 
 TEST_FILES = \
-	htest/data/clean-nonzero-score.data \
-	htest/data/common-suffix.data \
-	htest/data/empty-cluster.data \
-	htest/data/hail-alloc-drbd.json \
-	htest/data/hail-change-group.json \
-	htest/data/hail-invalid-reloc.json \
-	htest/data/hail-node-evac.json \
-	htest/data/hail-reloc-drbd.json \
-	htest/data/hbal-excl-tags.data \
-	htest/data/hbal-split-insts.data \
-	htest/data/invalid-node.data \
-	htest/data/missing-resources.data \
-	htest/data/n1-failure.data \
-	htest/data/rapi/groups.json \
-	htest/data/rapi/info.json \
-	htest/data/rapi/instances.json \
-	htest/data/rapi/nodes.json \
-	htest/shelltests/htools-balancing.test \
-	htest/shelltests/htools-basic.test \
-	htest/shelltests/htools-dynutil.test \
-	htest/shelltests/htools-excl.test \
-	htest/shelltests/htools-hail.test \
-	htest/shelltests/htools-hspace.test \
-	htest/shelltests/htools-invalid.test \
-	htest/shelltests/htools-multi-group.test \
-	htest/shelltests/htools-no-backend.test \
-	htest/shelltests/htools-rapi.test \
-	htest/shelltests/htools-single-group.test \
-	htest/shelltests/htools-text-backend.test \
-	htest/shelltests/htools-mon-collector.test \
+	test/data/htools/clean-nonzero-score.data \
+	test/data/htools/common-suffix.data \
+	test/data/htools/empty-cluster.data \
+	test/data/htools/hail-alloc-drbd.json \
+	test/data/htools/hail-change-group.json \
+	test/data/htools/hail-invalid-reloc.json \
+	test/data/htools/hail-node-evac.json \
+	test/data/htools/hail-reloc-drbd.json \
+	test/data/htools/hbal-excl-tags.data \
+	test/data/htools/hbal-split-insts.data \
+	test/data/htools/invalid-node.data \
+	test/data/htools/missing-resources.data \
+	test/data/htools/n1-failure.data \
+	test/data/htools/rapi/groups.json \
+	test/data/htools/rapi/info.json \
+	test/data/htools/rapi/instances.json \
+	test/data/htools/rapi/nodes.json \
+	test/hs/shelltests/htools-balancing.test \
+	test/hs/shelltests/htools-basic.test \
+	test/hs/shelltests/htools-dynutil.test \
+	test/hs/shelltests/htools-excl.test \
+	test/hs/shelltests/htools-hail.test \
+	test/hs/shelltests/htools-hspace.test \
+	test/hs/shelltests/htools-invalid.test \
+	test/hs/shelltests/htools-multi-group.test \
+	test/hs/shelltests/htools-no-backend.test \
+	test/hs/shelltests/htools-rapi.test \
+	test/hs/shelltests/htools-single-group.test \
+	test/hs/shelltests/htools-text-backend.test \
+	test/hs/shelltests/htools-mon-collector.test \
 	test/data/bdev-drbd-8.0.txt \
 	test/data/bdev-drbd-8.3.txt \
 	test/data/bdev-drbd-disk.txt \
@@ -1040,7 +1040,7 @@ python_tests = \
 	test/py/qa.qa_config_unittest.py \
 	test/py/tempfile_fork_unittest.py
 
-haskell_tests = htest/test
+haskell_tests = test/hs/test
 
 dist_TESTS = \
 	test/py/check-cert-expired_unittest.bash \
@@ -1056,10 +1056,10 @@ check_SCRIPTS =
 
 if WANT_HSTESTS
 nodist_TESTS += $(haskell_tests)
-dist_TESTS += htest/offline-test.sh
+dist_TESTS += test/hs/offline-test.sh
 check_SCRIPTS += \
-	htest/hpc-htools \
-	htest/hpc-mon-collector \
+	test/hs/hpc-htools \
+	test/hs/hpc-mon-collector \
 	$(HS_BUILT_TEST_HELPERS)
 endif
 
@@ -1106,8 +1106,8 @@ srclink_files = \
 	test/py/import-export_unittest.bash \
 	test/py/cli-test.bash \
 	test/py/bash_completion.bash \
-	htest/offline-test.sh \
-	htest/cli-tests-defs.sh \
+	test/hs/offline-test.sh \
+	test/hs/cli-tests-defs.sh \
 	$(all_python_code) \
 	$(HS_LIBTEST_SRCS) $(HS_PROG_SRCS)
 
@@ -1269,7 +1269,7 @@ src/Ganeti/Constants.hs: src/Ganeti/Constants.hs.in \
 	  PYTHONPATH=. $(RUN_IN_TEMPDIR) $(CURDIR)/$(CONVERT_CONSTANTS); \
 	} > $@
 
-htest/Test/Ganeti/TestImports.hs: htest/Test/Ganeti/TestImports.hs.in \
+test/hs/Test/Ganeti/TestImports.hs: test/hs/Test/Ganeti/TestImports.hs.in \
 	$(built_base_sources)
 	set -e; \
 	{ cat $< ; \
@@ -1433,7 +1433,7 @@ tools/ensure-dirs: MODULE = ganeti.tools.ensure_dirs
 tools/node-daemon-setup: MODULE = ganeti.tools.node_daemon_setup
 tools/prepare-node-join: MODULE = ganeti.tools.prepare_node_join
 tools/node-cleanup: MODULE = ganeti.tools.node_cleanup
-$(HS_BUILT_TEST_HELPERS): TESTROLE = $(patsubst htest/%,%,$@)
+$(HS_BUILT_TEST_HELPERS): TESTROLE = $(patsubst test/hs/%,%,$@)
 
 $(PYTHON_BOOTSTRAP): Makefile | stamp-directories
 	test -n "$(MODULE)" || { echo Missing module; exit 1; }
@@ -1469,7 +1469,7 @@ $(HS_BUILT_TEST_HELPERS): Makefile
 	  echo '# This file is automatically generated, do not edit!'; \
 	  echo "# Edit Makefile.am instead."; \
 	  echo; \
-	  echo "HTOOLS=$(TESTROLE) exec ./htest/hpc-htools \"\$$@\""; \
+	  echo "HTOOLS=$(TESTROLE) exec ./test/hs/hpc-htools \"\$$@\""; \
 	} > $@
 	chmod u+x $@
 
@@ -1552,11 +1552,11 @@ check-local: check-dirs $(GENERATED_FILES)
 	test -z "$$error"
 
 .PHONY: hs-check
-hs-check: htest/test htest/hpc-htools htest/hpc-mon-collector $(HS_BUILT_TEST_HELPERS) \
+hs-check: test/hs/test test/hs/hpc-htools test/hs/hpc-mon-collector $(HS_BUILT_TEST_HELPERS) \
 	| $(BUILT_PYTHON_SOURCES)
 	@rm -f *.tix
-	./htest/test
-	HBINARY="./htest/hpc-htools" ./htest/offline-test.sh
+	./test/hs/test
+	HBINARY="./test/hs/hpc-htools" ./test/hs/offline-test.sh
 
 # E111: indentation is not a multiple of four
 # E121: continuation line indentation is not a multiple of four
@@ -1606,7 +1606,7 @@ pep8: $(GENERATED_FILES)
 	  --repeat $(pep8_python_code)
 
 # FIXME: remove ignore "Use void" when GHC 6.x is deprecated
-HLINT_EXCLUDES = src/Ganeti/THH.hs htest/hpc-htools.hs
+HLINT_EXCLUDES = src/Ganeti/THH.hs test/hs/hpc-htools.hs
 .PHONY: hlint
 hlint: $(HS_BUILT_SRCS) src/lint-hints.hs
 	@test -n "$(HLINT)" || { echo 'hlint' not found during configure; exit 1; }
@@ -1768,7 +1768,7 @@ py-coverage: $(GENERATED_FILES) $(python_tests)
 	$(python_tests)
 
 .PHONY: hs-coverage
-hs-coverage: $(haskell_tests) htest/hpc-htools htest/hpc-mon-collector
+hs-coverage: $(haskell_tests) test/hs/hpc-htools test/hs/hpc-mon-collector
 	rm -f *.tix
 	$(MAKE) $(AM_MAKEFLAGS) hs-check
 	@mkdir_p@ $(COVERAGE_HS_DIR)
diff --git a/autotools/run-in-tempdir b/autotools/run-in-tempdir
index 86f643944..128978312 100755
--- a/autotools/run-in-tempdir
+++ b/autotools/run-in-tempdir
@@ -8,26 +8,25 @@ set -e
 tmpdir=$(mktemp -d -t gntbuild.XXXXXXXX)
 trap "rm -rf $tmpdir" EXIT
 
-mkdir $tmpdir/doc
+# fully copy items
+cp -r autotools daemons scripts lib tools qa $tmpdir
 
-cp -r autotools daemons scripts lib tools test qa $tmpdir
+mkdir $tmpdir/doc
 ln -s $PWD/doc/examples $tmpdir/doc
 
+mkdir $tmpdir/test/
+cp -r test/py $tmpdir/test/py
+ln -s $PWD/test/data $tmpdir/test
+ln -s $PWD/test/hs $tmpdir/test
+
 mv $tmpdir/lib $tmpdir/ganeti
 ln -T -s $tmpdir/ganeti $tmpdir/lib
 
-mkdir -p $tmpdir/src $tmpdir/htest
+mkdir -p $tmpdir/src $tmpdir/test/hs
 for hfile in htools ganeti-confd mon-collector; do
   if [ -e src/$hfile ]; then
     ln -s $PWD/src/$hfile $tmpdir/src/
   fi
 done
 
-for hfile in hpc-htools test offline-test.sh cli-tests-defs.sh \
-  hbal hscan hspace hinfo hcheck hail hroller hpc-mon-collector; do
-  if [ -e htest/$hfile ]; then
-    ln -s $PWD/htest/$hfile $tmpdir/htest/
-  fi
-done
-
 cd $tmpdir && GANETI_TEMP_DIR="$tmpdir" "$@"
diff --git a/doc/devnotes.rst b/doc/devnotes.rst
index 77112dd94..81823c36d 100644
--- a/doc/devnotes.rst
+++ b/doc/devnotes.rst
@@ -180,7 +180,7 @@ For Python tests::
 
 For Haskell tests::
 
-  $ make htest/test && ./htest/test -t %pattern%
+  $ make test/hs/test && ./test/hs/test -t %pattern%
 
 Where ``pattern`` can be a simple test pattern (e.g. ``comma``,
 matching any test whose name contains ``comma``), a test pattern
diff --git a/htest/hpc-htools.hs b/htest/hpc-htools.hs
deleted file mode 120000
index edd10816f..000000000
--- a/htest/hpc-htools.hs
+++ /dev/null
@@ -1 +0,0 @@
-../src/htools.hs
\ No newline at end of file
diff --git a/htest/hpc-mon-collector.hs b/htest/hpc-mon-collector.hs
deleted file mode 120000
index 1a34a1a26..000000000
--- a/htest/hpc-mon-collector.hs
+++ /dev/null
@@ -1 +0,0 @@
-../src/mon-collector.hs
\ No newline at end of file
diff --git a/htest/shelltests/htools-basic.test b/htest/shelltests/htools-basic.test
deleted file mode 100644
index 9b45b31bc..000000000
--- a/htest/shelltests/htools-basic.test
+++ /dev/null
@@ -1,43 +0,0 @@
-# help/version tests
-./htest/hail --version
->>>= 0
-./htest/hail --help
->>>= 0
-./htest/hail --help-completion
->>>= 0
-./htest/hbal --version
->>>= 0
-./htest/hbal --help
->>>= 0
-./htest/hbal --help-completion
->>>= 0
-./htest/hspace --version
->>>= 0
-./htest/hspace --help
->>>= 0
-./htest/hspace --help-completion
->>>= 0
-./htest/hscan --version
->>>= 0
-./htest/hscan --help
->>>= 0
-./htest/hscan --help-completion
->>>= 0
-./htest/hinfo --version
->>>= 0
-./htest/hinfo --help
->>>= 0
-./htest/hinfo --help-completion
->>>= 0
-./htest/hcheck --version
->>>= 0
-./htest/hcheck --help
->>>= 0
-./htest/hcheck --help-completion
->>>= 0
-./htest/hroller --version
->>>= 0
-./htest/hroller --help
->>>= 0
-./htest/hroller --help-completion
->>>= 0
diff --git a/htest/shelltests/htools-dynutil.test b/htest/shelltests/htools-dynutil.test
deleted file mode 100644
index ed01a7907..000000000
--- a/htest/shelltests/htools-dynutil.test
+++ /dev/null
@@ -1,19 +0,0 @@
-echo a > $T/dynu; ./htest/hbal -U $T/dynu $BACKEND_DYNU
->>>2 /Cannot parse line/
->>>= !0
-
-echo a b c d e f g h > $T/dynu; ./htest/hbal -U $T/dynu $BACKEND_DYNU
->>>2 /Cannot parse line/
->>>= !0
-
-echo inst cpu mem dsk net >$T/dynu; ./htest/hbal -U $T/dynu $BACKEND_DYNU
->>>2 /cannot parse string '(cpu|mem|dsk|net)'/
->>>= !0
-
-# unknown instances are currently just ignored
-echo no-such-inst 2 2 2 2 > $T/dynu; ./htest/hbal -U $T/dynu $BACKEND_DYNU
->>>= 0
-
-# new-0 is the name of the first instance allocated by hspace
-echo new-0 2 2 2 2 > $T/dynu; ./htest/hbal -U $T/dynu $BACKEND_DYNU
->>>= 0
diff --git a/htest/shelltests/htools-excl.test b/htest/shelltests/htools-excl.test
deleted file mode 100644
index 22b12c9af..000000000
--- a/htest/shelltests/htools-excl.test
+++ /dev/null
@@ -1,15 +0,0 @@
-./htest/hbal $BACKEND_EXCL --exclude-instances no-such-instance
->>>2 /Unknown instance/
->>>= !0
-
-./htest/hbal $BACKEND_EXCL --select-instances no-such-instances
->>>2 /Unknown instance/
->>>= !0
-
-./htest/hbal $BACKEND_EXCL --exclude-instances new-0 --select-instances new-1
->>>= 0
-
-# Test exclusion tags too (both from the command line and cluster tags).
-./htest/hbal -t $TESTDATA_DIR/hbal-excl-tags.data --exclusion-tags test
->>> /Cluster score improved/
->>>= 0
diff --git a/htest/shelltests/htools-hspace.test b/htest/shelltests/htools-hspace.test
deleted file mode 100644
index cee8c132e..000000000
--- a/htest/shelltests/htools-hspace.test
+++ /dev/null
@@ -1,8 +0,0 @@
-# test that hspace machine readable output looks correct
-./htest/hspace --simu p,4,8T,64g,16 --machine-readable --disk-template drbd -l 8
->>> /^HTS_OK=1/
->>>= 0
-
-# test again via a file and shell parsing
-./htest/hspace --simu p,4,8T,64g,16 --machine-readable --disk-template drbd -l 8 > $T/capacity && sh -c ". $T/capacity && test x\$HTS_OK = x1"
->>>= 0
diff --git a/htest/shelltests/htools-single-group.test b/htest/shelltests/htools-single-group.test
deleted file mode 100644
index 38281827a..000000000
--- a/htest/shelltests/htools-single-group.test
+++ /dev/null
@@ -1,37 +0,0 @@
-# standard single-group tests
-./htest/hinfo -v -v -p --print-instances -t$T/simu-onegroup.standard
->>>= 0
-
-./htest/hbal  -v -v -p --print-instances -t$T/simu-onegroup.standard
->>>= 0
-
-# hbal should not be able to balance
-./htest/hbal -t$T/simu-onegroup.standard
->>> /(Nothing to do, exiting|No solution found)/
->>>= 0
-
-
-# tiered single-group tests
-./htest/hinfo -v -v -p --print-instances -t$T/simu-onegroup.tiered
->>>= 0
-
-./htest/hbal  -v -v -p --print-instances -t$T/simu-onegroup.tiered
->>>= 0
-
-# hbal should not be able to balance
-./htest/hbal -t$T/simu-onegroup.tiered
->>> /(Nothing to do, exiting|No solution found)/
->>>= 0
-
-# hcheck should not find reason to rebalance
-./htest/hcheck -t$T/simu-onegroup.tiered --machine-readable
->>> /HCHECK_INIT_CLUSTER_NEED_REBALANCE=0/
->>>= 0
-
-# hroller should be able to print the solution
-./htest/hroller -t$T/simu-onegroup.tiered
->>>= 0
-
-# hroller should be able to print the solution, in verbose mode as well
-./htest/hroller -t$T/simu-onegroup.tiered -v -v
->>>= 0
diff --git a/htest/data/clean-nonzero-score.data b/test/data/htools/clean-nonzero-score.data
similarity index 100%
rename from htest/data/clean-nonzero-score.data
rename to test/data/htools/clean-nonzero-score.data
diff --git a/htest/data/common-suffix.data b/test/data/htools/common-suffix.data
similarity index 100%
rename from htest/data/common-suffix.data
rename to test/data/htools/common-suffix.data
diff --git a/htest/data/empty-cluster.data b/test/data/htools/empty-cluster.data
similarity index 100%
rename from htest/data/empty-cluster.data
rename to test/data/htools/empty-cluster.data
diff --git a/htest/data/hail-alloc-drbd.json b/test/data/htools/hail-alloc-drbd.json
similarity index 100%
rename from htest/data/hail-alloc-drbd.json
rename to test/data/htools/hail-alloc-drbd.json
diff --git a/htest/data/hail-change-group.json b/test/data/htools/hail-change-group.json
similarity index 100%
rename from htest/data/hail-change-group.json
rename to test/data/htools/hail-change-group.json
diff --git a/htest/data/hail-invalid-reloc.json b/test/data/htools/hail-invalid-reloc.json
similarity index 100%
rename from htest/data/hail-invalid-reloc.json
rename to test/data/htools/hail-invalid-reloc.json
diff --git a/htest/data/hail-node-evac.json b/test/data/htools/hail-node-evac.json
similarity index 100%
rename from htest/data/hail-node-evac.json
rename to test/data/htools/hail-node-evac.json
diff --git a/htest/data/hail-reloc-drbd.json b/test/data/htools/hail-reloc-drbd.json
similarity index 100%
rename from htest/data/hail-reloc-drbd.json
rename to test/data/htools/hail-reloc-drbd.json
diff --git a/htest/data/hbal-excl-tags.data b/test/data/htools/hbal-excl-tags.data
similarity index 100%
rename from htest/data/hbal-excl-tags.data
rename to test/data/htools/hbal-excl-tags.data
diff --git a/htest/data/hbal-split-insts.data b/test/data/htools/hbal-split-insts.data
similarity index 100%
rename from htest/data/hbal-split-insts.data
rename to test/data/htools/hbal-split-insts.data
diff --git a/htest/data/invalid-node.data b/test/data/htools/invalid-node.data
similarity index 100%
rename from htest/data/invalid-node.data
rename to test/data/htools/invalid-node.data
diff --git a/htest/data/missing-resources.data b/test/data/htools/missing-resources.data
similarity index 100%
rename from htest/data/missing-resources.data
rename to test/data/htools/missing-resources.data
diff --git a/htest/data/n1-failure.data b/test/data/htools/n1-failure.data
similarity index 100%
rename from htest/data/n1-failure.data
rename to test/data/htools/n1-failure.data
diff --git a/htest/data/rapi/groups.json b/test/data/htools/rapi/groups.json
similarity index 100%
rename from htest/data/rapi/groups.json
rename to test/data/htools/rapi/groups.json
diff --git a/htest/data/rapi/info.json b/test/data/htools/rapi/info.json
similarity index 100%
rename from htest/data/rapi/info.json
rename to test/data/htools/rapi/info.json
diff --git a/htest/data/rapi/instances.json b/test/data/htools/rapi/instances.json
similarity index 100%
rename from htest/data/rapi/instances.json
rename to test/data/htools/rapi/instances.json
diff --git a/htest/data/rapi/nodes.json b/test/data/htools/rapi/nodes.json
similarity index 100%
rename from htest/data/rapi/nodes.json
rename to test/data/htools/rapi/nodes.json
diff --git a/htest/Test/Ganeti/Attoparsec.hs b/test/hs/Test/Ganeti/Attoparsec.hs
similarity index 100%
rename from htest/Test/Ganeti/Attoparsec.hs
rename to test/hs/Test/Ganeti/Attoparsec.hs
diff --git a/htest/Test/Ganeti/BasicTypes.hs b/test/hs/Test/Ganeti/BasicTypes.hs
similarity index 100%
rename from htest/Test/Ganeti/BasicTypes.hs
rename to test/hs/Test/Ganeti/BasicTypes.hs
diff --git a/htest/Test/Ganeti/Block/Drbd/Parser.hs b/test/hs/Test/Ganeti/Block/Drbd/Parser.hs
similarity index 100%
rename from htest/Test/Ganeti/Block/Drbd/Parser.hs
rename to test/hs/Test/Ganeti/Block/Drbd/Parser.hs
diff --git a/htest/Test/Ganeti/Block/Drbd/Types.hs b/test/hs/Test/Ganeti/Block/Drbd/Types.hs
similarity index 100%
rename from htest/Test/Ganeti/Block/Drbd/Types.hs
rename to test/hs/Test/Ganeti/Block/Drbd/Types.hs
diff --git a/htest/Test/Ganeti/Common.hs b/test/hs/Test/Ganeti/Common.hs
similarity index 100%
rename from htest/Test/Ganeti/Common.hs
rename to test/hs/Test/Ganeti/Common.hs
diff --git a/htest/Test/Ganeti/Confd/Types.hs b/test/hs/Test/Ganeti/Confd/Types.hs
similarity index 100%
rename from htest/Test/Ganeti/Confd/Types.hs
rename to test/hs/Test/Ganeti/Confd/Types.hs
diff --git a/htest/Test/Ganeti/Confd/Utils.hs b/test/hs/Test/Ganeti/Confd/Utils.hs
similarity index 100%
rename from htest/Test/Ganeti/Confd/Utils.hs
rename to test/hs/Test/Ganeti/Confd/Utils.hs
diff --git a/htest/Test/Ganeti/Daemon.hs b/test/hs/Test/Ganeti/Daemon.hs
similarity index 100%
rename from htest/Test/Ganeti/Daemon.hs
rename to test/hs/Test/Ganeti/Daemon.hs
diff --git a/htest/Test/Ganeti/Errors.hs b/test/hs/Test/Ganeti/Errors.hs
similarity index 100%
rename from htest/Test/Ganeti/Errors.hs
rename to test/hs/Test/Ganeti/Errors.hs
diff --git a/htest/Test/Ganeti/HTools/Backend/Simu.hs b/test/hs/Test/Ganeti/HTools/Backend/Simu.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Backend/Simu.hs
rename to test/hs/Test/Ganeti/HTools/Backend/Simu.hs
diff --git a/htest/Test/Ganeti/HTools/Backend/Text.hs b/test/hs/Test/Ganeti/HTools/Backend/Text.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Backend/Text.hs
rename to test/hs/Test/Ganeti/HTools/Backend/Text.hs
diff --git a/htest/Test/Ganeti/HTools/CLI.hs b/test/hs/Test/Ganeti/HTools/CLI.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/CLI.hs
rename to test/hs/Test/Ganeti/HTools/CLI.hs
diff --git a/htest/Test/Ganeti/HTools/Cluster.hs b/test/hs/Test/Ganeti/HTools/Cluster.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Cluster.hs
rename to test/hs/Test/Ganeti/HTools/Cluster.hs
diff --git a/htest/Test/Ganeti/HTools/Container.hs b/test/hs/Test/Ganeti/HTools/Container.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Container.hs
rename to test/hs/Test/Ganeti/HTools/Container.hs
diff --git a/htest/Test/Ganeti/HTools/Graph.hs b/test/hs/Test/Ganeti/HTools/Graph.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Graph.hs
rename to test/hs/Test/Ganeti/HTools/Graph.hs
diff --git a/htest/Test/Ganeti/HTools/Instance.hs b/test/hs/Test/Ganeti/HTools/Instance.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Instance.hs
rename to test/hs/Test/Ganeti/HTools/Instance.hs
diff --git a/htest/Test/Ganeti/HTools/Loader.hs b/test/hs/Test/Ganeti/HTools/Loader.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Loader.hs
rename to test/hs/Test/Ganeti/HTools/Loader.hs
diff --git a/htest/Test/Ganeti/HTools/Node.hs b/test/hs/Test/Ganeti/HTools/Node.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Node.hs
rename to test/hs/Test/Ganeti/HTools/Node.hs
diff --git a/htest/Test/Ganeti/HTools/PeerMap.hs b/test/hs/Test/Ganeti/HTools/PeerMap.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/PeerMap.hs
rename to test/hs/Test/Ganeti/HTools/PeerMap.hs
diff --git a/htest/Test/Ganeti/HTools/Types.hs b/test/hs/Test/Ganeti/HTools/Types.hs
similarity index 100%
rename from htest/Test/Ganeti/HTools/Types.hs
rename to test/hs/Test/Ganeti/HTools/Types.hs
diff --git a/htest/Test/Ganeti/JQueue.hs b/test/hs/Test/Ganeti/JQueue.hs
similarity index 100%
rename from htest/Test/Ganeti/JQueue.hs
rename to test/hs/Test/Ganeti/JQueue.hs
diff --git a/htest/Test/Ganeti/JSON.hs b/test/hs/Test/Ganeti/JSON.hs
similarity index 100%
rename from htest/Test/Ganeti/JSON.hs
rename to test/hs/Test/Ganeti/JSON.hs
diff --git a/htest/Test/Ganeti/Jobs.hs b/test/hs/Test/Ganeti/Jobs.hs
similarity index 100%
rename from htest/Test/Ganeti/Jobs.hs
rename to test/hs/Test/Ganeti/Jobs.hs
diff --git a/htest/Test/Ganeti/Luxi.hs b/test/hs/Test/Ganeti/Luxi.hs
similarity index 100%
rename from htest/Test/Ganeti/Luxi.hs
rename to test/hs/Test/Ganeti/Luxi.hs
diff --git a/htest/Test/Ganeti/Network.hs b/test/hs/Test/Ganeti/Network.hs
similarity index 100%
rename from htest/Test/Ganeti/Network.hs
rename to test/hs/Test/Ganeti/Network.hs
diff --git a/htest/Test/Ganeti/Objects.hs b/test/hs/Test/Ganeti/Objects.hs
similarity index 99%
rename from htest/Test/Ganeti/Objects.hs
rename to test/hs/Test/Ganeti/Objects.hs
index 9f6448233..3526902d3 100644
--- a/htest/Test/Ganeti/Objects.hs
+++ b/test/hs/Test/Ganeti/Objects.hs
@@ -268,7 +268,7 @@ prop_Config_serialisation =
 -- | Custom HUnit test to check the correspondence between Haskell-generated
 -- networks and their Python decoded, validated and re-encoded version.
 -- For the technical background of this unit test, check the documentation
--- of "case_py_compat_types" of htest/Test/Ganeti/Opcodes.hs
+-- of "case_py_compat_types" of test/hs/Test/Ganeti/Opcodes.hs
 case_py_compat_networks :: HUnit.Assertion
 case_py_compat_networks = do
   let num_networks = 500::Int
diff --git a/htest/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs
similarity index 100%
rename from htest/Test/Ganeti/OpCodes.hs
rename to test/hs/Test/Ganeti/OpCodes.hs
diff --git a/htest/Test/Ganeti/Query/Filter.hs b/test/hs/Test/Ganeti/Query/Filter.hs
similarity index 100%
rename from htest/Test/Ganeti/Query/Filter.hs
rename to test/hs/Test/Ganeti/Query/Filter.hs
diff --git a/htest/Test/Ganeti/Query/Language.hs b/test/hs/Test/Ganeti/Query/Language.hs
similarity index 100%
rename from htest/Test/Ganeti/Query/Language.hs
rename to test/hs/Test/Ganeti/Query/Language.hs
diff --git a/htest/Test/Ganeti/Query/Query.hs b/test/hs/Test/Ganeti/Query/Query.hs
similarity index 100%
rename from htest/Test/Ganeti/Query/Query.hs
rename to test/hs/Test/Ganeti/Query/Query.hs
diff --git a/htest/Test/Ganeti/Rpc.hs b/test/hs/Test/Ganeti/Rpc.hs
similarity index 100%
rename from htest/Test/Ganeti/Rpc.hs
rename to test/hs/Test/Ganeti/Rpc.hs
diff --git a/htest/Test/Ganeti/Ssconf.hs b/test/hs/Test/Ganeti/Ssconf.hs
similarity index 100%
rename from htest/Test/Ganeti/Ssconf.hs
rename to test/hs/Test/Ganeti/Ssconf.hs
diff --git a/htest/Test/Ganeti/THH.hs b/test/hs/Test/Ganeti/THH.hs
similarity index 100%
rename from htest/Test/Ganeti/THH.hs
rename to test/hs/Test/Ganeti/THH.hs
diff --git a/htest/Test/Ganeti/TestCommon.hs b/test/hs/Test/Ganeti/TestCommon.hs
similarity index 99%
rename from htest/Test/Ganeti/TestCommon.hs
rename to test/hs/Test/Ganeti/TestCommon.hs
index a684a2b4d..72ae311ac 100644
--- a/htest/Test/Ganeti/TestCommon.hs
+++ b/test/hs/Test/Ganeti/TestCommon.hs
@@ -298,5 +298,5 @@ readPythonTestData filename = do
 -- | Returns the content of the specified haskell test data file.
 readTestData :: String -> IO String
 readTestData filename = do
-    name <- testDataFilename "/htest/data/" filename
+    name <- testDataFilename "/test/data/htools/" filename
     readFile name
diff --git a/htest/Test/Ganeti/TestHTools.hs b/test/hs/Test/Ganeti/TestHTools.hs
similarity index 100%
rename from htest/Test/Ganeti/TestHTools.hs
rename to test/hs/Test/Ganeti/TestHTools.hs
diff --git a/htest/Test/Ganeti/TestHelper.hs b/test/hs/Test/Ganeti/TestHelper.hs
similarity index 100%
rename from htest/Test/Ganeti/TestHelper.hs
rename to test/hs/Test/Ganeti/TestHelper.hs
diff --git a/htest/Test/Ganeti/TestImports.hs.in b/test/hs/Test/Ganeti/TestImports.hs.in
similarity index 100%
rename from htest/Test/Ganeti/TestImports.hs.in
rename to test/hs/Test/Ganeti/TestImports.hs.in
diff --git a/htest/Test/Ganeti/Types.hs b/test/hs/Test/Ganeti/Types.hs
similarity index 100%
rename from htest/Test/Ganeti/Types.hs
rename to test/hs/Test/Ganeti/Types.hs
diff --git a/htest/Test/Ganeti/Utils.hs b/test/hs/Test/Ganeti/Utils.hs
similarity index 100%
rename from htest/Test/Ganeti/Utils.hs
rename to test/hs/Test/Ganeti/Utils.hs
diff --git a/htest/cli-tests-defs.sh b/test/hs/cli-tests-defs.sh
similarity index 92%
rename from htest/cli-tests-defs.sh
rename to test/hs/cli-tests-defs.sh
index 86dd64f1d..b33a756e7 100644
--- a/htest/cli-tests-defs.sh
+++ b/test/hs/cli-tests-defs.sh
@@ -19,9 +19,9 @@
 
 # This is an shell testing configuration fragment.
 
-HBINARY=${HBINARY:-./htest/hpc-htools}
+HBINARY=${HBINARY:-./test/hs/hpc-htools}
 
-export TESTDATA_DIR=${TOP_SRCDIR:-.}/htest/data
+export TESTDATA_DIR=${TOP_SRCDIR:-.}/test/data/htools
 export PYTESTDATA_DIR=${TOP_SRCDIR:-.}/test/data
 
 hbal() {
diff --git a/test/hs/hpc-htools.hs b/test/hs/hpc-htools.hs
new file mode 120000
index 000000000..27f95cd6e
--- /dev/null
+++ b/test/hs/hpc-htools.hs
@@ -0,0 +1 @@
+../../src/htools.hs
\ No newline at end of file
diff --git a/test/hs/hpc-mon-collector.hs b/test/hs/hpc-mon-collector.hs
new file mode 120000
index 000000000..2ae7dc5f2
--- /dev/null
+++ b/test/hs/hpc-mon-collector.hs
@@ -0,0 +1 @@
+../../src/mon-collector.hs
\ No newline at end of file
diff --git a/htest/live-test.sh b/test/hs/live-test.sh
similarity index 100%
rename from htest/live-test.sh
rename to test/hs/live-test.sh
diff --git a/htest/offline-test.sh b/test/hs/offline-test.sh
similarity index 85%
rename from htest/offline-test.sh
rename to test/hs/offline-test.sh
index 54eb81c80..cbcba41e0 100755
--- a/htest/offline-test.sh
+++ b/test/hs/offline-test.sh
@@ -34,12 +34,12 @@ echo Using $T as temporary dir
 
 echo -n Generating hspace simulation data for hinfo and hbal...
 # this cluster spec should be fine
-./htest/hspace --simu p,4,8T,64g,16 -S $T/simu-onegroup \
+./test/hs/hspace --simu p,4,8T,64g,16 -S $T/simu-onegroup \
   --disk-template drbd -l 8 -v -v -v >/dev/null 2>&1
 echo OK
 
 echo -n Generating hinfo and hbal test files for multi-group...
-./htest/hspace --simu p,4,8T,64g,16 --simu p,4,8T,64g,16 \
+./test/hs/hspace --simu p,4,8T,64g,16 --simu p,4,8T,64g,16 \
   -S $T/simu-twogroups --disk-template drbd -l 8 >/dev/null 2>&1
 echo OK
 
@@ -48,7 +48,7 @@ echo -n Generating test files for rebalancing...
 # policy, then we change all nodes from this group to the allocable
 # one, and we check for rebalancing
 FROOT="$T/simu-rebal-orig"
-./htest/hspace --simu u,4,8T,64g,16 --simu p,4,8T,64g,16 \
+./test/hs/hspace --simu u,4,8T,64g,16 --simu p,4,8T,64g,16 \
   -S $FROOT --disk-template drbd -l 8 >/dev/null 2>&1
 for suffix in standard tiered; do
   RELOC="$T/simu-rebal-merged.$suffix"
@@ -75,19 +75,19 @@ echo OK
 echo -n Checking file-based RAPI...
 mkdir -p $T/hscan
 export RAPI_URL="file://$TESTDATA_DIR/rapi"
-./htest/hscan -d $T/hscan/ -p -v -v $RAPI_URL >/dev/null 2>&1
+./test/hs/hscan -d $T/hscan/ -p -v -v $RAPI_URL >/dev/null 2>&1
 # check that we file parsing is correct, i.e. hscan saves correct text
 # files, and is idempotent (rapi+text == rapi); more is tested in
 # shelltest later
 RAPI_TXT="$(ls $T/hscan/*.data|head -n1)"
-./htest/hinfo -p --print-instances -m $RAPI_URL > $T/hscan/direct.hinfo 2>&1
-./htest/hinfo -p --print-instances -t $RAPI_TXT > $T/hscan/fromtext.hinfo 2>&1
+./test/hs/hinfo -p --print-instances -m $RAPI_URL > $T/hscan/direct.hinfo 2>&1
+./test/hs/hinfo -p --print-instances -t $RAPI_TXT > $T/hscan/fromtext.hinfo 2>&1
 echo OK
 
 echo Running shelltest...
 
 shelltest $SHELLTESTARGS \
-  ${TOP_SRCDIR:-.}/htest/shelltests/htools-*.test \
+  ${TOP_SRCDIR:-.}/test/hs/shelltests/htools-*.test \
   -- --hide-successes
 
 echo All OK
diff --git a/htest/shelltests/htools-balancing.test b/test/hs/shelltests/htools-balancing.test
similarity index 66%
rename from htest/shelltests/htools-balancing.test
rename to test/hs/shelltests/htools-balancing.test
index 6c54d5540..bd3ef57e6 100644
--- a/htest/shelltests/htools-balancing.test
+++ b/test/hs/shelltests/htools-balancing.test
@@ -1,32 +1,32 @@
 ### std tests
 
 # test basic parsing
-./htest/hinfo -v -v -p --print-instances $BACKEND_BAL_STD
+./test/hs/hinfo -v -v -p --print-instances $BACKEND_BAL_STD
 >>>= 0
-./htest/hbal -v -v -v -p --print-instances $BACKEND_BAL_STD -G group-01
+./test/hs/hbal -v -v -v -p --print-instances $BACKEND_BAL_STD -G group-01
 >>> !/(Nothing to do, exiting|No solution found)/
 >>>2 !/(Nothing to do, exiting|No solution found)/
 >>>= 0
 
 # test command output
-./htest/hbal $BACKEND_BAL_STD -G group-01 -C -S $T/simu-rebal.standard
+./test/hs/hbal $BACKEND_BAL_STD -G group-01 -C -S $T/simu-rebal.standard
 >>> /gnt-instance (failover|migrate|replace-disks)/
 >>>= 0
 
 # test that hbal won't execute rebalances when using the text backend
-./htest/hbal $BACKEND_BAL_STD -G group-01 -X
+./test/hs/hbal $BACKEND_BAL_STD -G group-01 -X
 >>>2
 Error: hbal: Execution of commands possible only on LUXI
 >>>= !0
 
 # test that hbal won't execute any moves if we request an absurdly-high
 # minimum-improvement
-./htest/hbal $BACKEND_BAL_STD -G group-01 -C --min-gain 10000 --min-gain-limit 10000
+./test/hs/hbal $BACKEND_BAL_STD -G group-01 -C --min-gain 10000 --min-gain-limit 10000
 >>>/No solution found/
 >>>= 0
 
 # test saving commands
-./htest/hbal $BACKEND_BAL_STD -G group-01 -C$T/rebal-cmds.standard
+./test/hs/hbal $BACKEND_BAL_STD -G group-01 -C$T/rebal-cmds.standard
 >>>= 0
 # and now check the file (depends on previous test)
 cat $T/rebal-cmds.standard
@@ -40,32 +40,32 @@ diff -u $T/simu-rebal-merged.standard $T/simu-rebal.standard.original
 >>>= 0
 
 # no double rebalance; depends on previous test
-./htest/hbal -t $T/simu-rebal.standard.balanced -G group-01
+./test/hs/hbal -t $T/simu-rebal.standard.balanced -G group-01
 >>> /(Nothing to do, exiting|No solution found)/
 >>>= 0
 
 # hcheck sees no reason to rebalance after rebalancing was already done
-./htest/hcheck -t$T/simu-rebal.standard.balanced --machine-readable
+./test/hs/hcheck -t$T/simu-rebal.standard.balanced --machine-readable
 >>> /HCHECK_INIT_CLUSTER_NEED_REBALANCE=0/
 >>>= 0
 
 ### now tiered tests
 
 # test basic parsing
-./htest/hinfo -v -v -p --print-instances $BACKEND_BAL_TIER
+./test/hs/hinfo -v -v -p --print-instances $BACKEND_BAL_TIER
 >>>= 0
-./htest/hbal -v -v -v -p --print-instances $BACKEND_BAL_TIER -G group-01
+./test/hs/hbal -v -v -v -p --print-instances $BACKEND_BAL_TIER -G group-01
 >>> !/(Nothing to do, exiting|No solution found)/
 >>>2 !/(Nothing to do, exiting|No solution found)/
 >>>= 0
 
 # test command output
-./htest/hbal $BACKEND_BAL_TIER -G group-01 -C -S $T/simu-rebal.tiered
+./test/hs/hbal $BACKEND_BAL_TIER -G group-01 -C -S $T/simu-rebal.tiered
 >>> /gnt-instance (failover|migrate|replace-disks)/
 >>>= 0
 
 # test saving commands
-./htest/hbal $BACKEND_BAL_TIER -G group-01 -C$T/rebal-cmds.tiered
+./test/hs/hbal $BACKEND_BAL_TIER -G group-01 -C$T/rebal-cmds.tiered
 >>>= 0
 # and now check the file (depends on previous test)
 cat $T/rebal-cmds.tiered
@@ -79,45 +79,45 @@ diff -u $T/simu-rebal-merged.tiered $T/simu-rebal.tiered.original
 >>>= 0
 
 # no double rebalance; depends on previous test
-./htest/hbal -t $T/simu-rebal.tiered.balanced -G group-01
+./test/hs/hbal -t $T/simu-rebal.tiered.balanced -G group-01
 >>> /(Nothing to do, exiting|No solution found)/
 >>>= 0
 
 ### now some other custom tests
 
 # n+1 bad instances are reported as such
-./htest/hbal -t$TESTDATA_DIR/n1-failure.data -G group-01
+./test/hs/hbal -t$TESTDATA_DIR/n1-failure.data -G group-01
 >>>/Initial check done: 4 bad nodes, 8 bad instances./
 >>>=0
 
 # same test again, different message check (shelltest can't test multiple
 # messages via regexp
-./htest/hbal -t$TESTDATA_DIR/n1-failure.data -G group-01
+./test/hs/hbal -t$TESTDATA_DIR/n1-failure.data -G group-01
 >>>/Cluster is not N\+1 happy, continuing but no guarantee that the cluster will end N\+1 happy./
 >>>2
 >>>=0
 
 # and hcheck should report this as needs rebalancing
-./htest/hcheck -t$TESTDATA_DIR/n1-failure.data
+./test/hs/hcheck -t$TESTDATA_DIR/n1-failure.data
 >>>/Cluster needs rebalancing./
 >>>= 1
 
 # ... unless we request no-simulation mode
-./htest/hcheck -t$TESTDATA_DIR/n1-failure.data --no-simulation
+./test/hs/hcheck -t$TESTDATA_DIR/n1-failure.data --no-simulation
 >>>/Running in no-simulation mode./
 >>>= 0
 
 # and a clean cluster should be reported as such
-./htest/hcheck $BACKEND_BAL_STD
+./test/hs/hcheck $BACKEND_BAL_STD
 >>>/No need to rebalance cluster, no problems found./
 >>>= 0
 
 # ... and even one with non-zero score
-./htest/hcheck -t $TESTDATA_DIR/clean-nonzero-score.data
+./test/hs/hcheck -t $TESTDATA_DIR/clean-nonzero-score.data
 >>>/No need to rebalance cluster, no problems found./
 >>>= 0
 
 # hbal should work on empty groups as well
-./htest/hbal -t$TESTDATA_DIR/n1-failure.data -G group-02
+./test/hs/hbal -t$TESTDATA_DIR/n1-failure.data -G group-02
 >>>/Group size 0 nodes, 0 instances/
 >>>= 0
diff --git a/test/hs/shelltests/htools-basic.test b/test/hs/shelltests/htools-basic.test
new file mode 100644
index 000000000..251d27dab
--- /dev/null
+++ b/test/hs/shelltests/htools-basic.test
@@ -0,0 +1,43 @@
+# help/version tests
+./test/hs/hail --version
+>>>= 0
+./test/hs/hail --help
+>>>= 0
+./test/hs/hail --help-completion
+>>>= 0
+./test/hs/hbal --version
+>>>= 0
+./test/hs/hbal --help
+>>>= 0
+./test/hs/hbal --help-completion
+>>>= 0
+./test/hs/hspace --version
+>>>= 0
+./test/hs/hspace --help
+>>>= 0
+./test/hs/hspace --help-completion
+>>>= 0
+./test/hs/hscan --version
+>>>= 0
+./test/hs/hscan --help
+>>>= 0
+./test/hs/hscan --help-completion
+>>>= 0
+./test/hs/hinfo --version
+>>>= 0
+./test/hs/hinfo --help
+>>>= 0
+./test/hs/hinfo --help-completion
+>>>= 0
+./test/hs/hcheck --version
+>>>= 0
+./test/hs/hcheck --help
+>>>= 0
+./test/hs/hcheck --help-completion
+>>>= 0
+./test/hs/hroller --version
+>>>= 0
+./test/hs/hroller --help
+>>>= 0
+./test/hs/hroller --help-completion
+>>>= 0
diff --git a/test/hs/shelltests/htools-dynutil.test b/test/hs/shelltests/htools-dynutil.test
new file mode 100644
index 000000000..a4b60415a
--- /dev/null
+++ b/test/hs/shelltests/htools-dynutil.test
@@ -0,0 +1,19 @@
+echo a > $T/dynu; ./test/hs/hbal -U $T/dynu $BACKEND_DYNU
+>>>2 /Cannot parse line/
+>>>= !0
+
+echo a b c d e f g h > $T/dynu; ./test/hs/hbal -U $T/dynu $BACKEND_DYNU
+>>>2 /Cannot parse line/
+>>>= !0
+
+echo inst cpu mem dsk net >$T/dynu; ./test/hs/hbal -U $T/dynu $BACKEND_DYNU
+>>>2 /cannot parse string '(cpu|mem|dsk|net)'/
+>>>= !0
+
+# unknown instances are currently just ignored
+echo no-such-inst 2 2 2 2 > $T/dynu; ./test/hs/hbal -U $T/dynu $BACKEND_DYNU
+>>>= 0
+
+# new-0 is the name of the first instance allocated by hspace
+echo new-0 2 2 2 2 > $T/dynu; ./test/hs/hbal -U $T/dynu $BACKEND_DYNU
+>>>= 0
diff --git a/test/hs/shelltests/htools-excl.test b/test/hs/shelltests/htools-excl.test
new file mode 100644
index 000000000..b22880c0f
--- /dev/null
+++ b/test/hs/shelltests/htools-excl.test
@@ -0,0 +1,15 @@
+./test/hs/hbal $BACKEND_EXCL --exclude-instances no-such-instance
+>>>2 /Unknown instance/
+>>>= !0
+
+./test/hs/hbal $BACKEND_EXCL --select-instances no-such-instances
+>>>2 /Unknown instance/
+>>>= !0
+
+./test/hs/hbal $BACKEND_EXCL --exclude-instances new-0 --select-instances new-1
+>>>= 0
+
+# Test exclusion tags too (both from the command line and cluster tags).
+./test/hs/hbal -t $TESTDATA_DIR/hbal-excl-tags.data --exclusion-tags test
+>>> /Cluster score improved/
+>>>= 0
diff --git a/htest/shelltests/htools-hail.test b/test/hs/shelltests/htools-hail.test
similarity index 57%
rename from htest/shelltests/htools-hail.test
rename to test/hs/shelltests/htools-hail.test
index 2f1c9f9c7..55dd3ca20 100644
--- a/htest/shelltests/htools-hail.test
+++ b/test/hs/shelltests/htools-hail.test
@@ -1,77 +1,77 @@
 # test that on invalid files it can't parse the request
-./htest/hail /dev/null
+./test/hs/hail /dev/null
 >>>2 /Invalid JSON/
 >>>= !0
 
 # another invalid example
-echo '[]' | ./htest/hail -
+echo '[]' | ./test/hs/hail -
 >>>2 /Unable to read JSObject/
 >>>= !0
 
 # empty dict
-echo '{}' | ./htest/hail -
+echo '{}' | ./test/hs/hail -
 >>>2 /key 'request' not found/
 >>>= !0
 
-echo '{"request": 0}' | ./htest/hail -
+echo '{"request": 0}' | ./test/hs/hail -
 >>>2 /key 'request'/
 >>>= !0
 
-./htest/hail $TESTDATA_DIR/hail-invalid-reloc.json
+./test/hs/hail $TESTDATA_DIR/hail-invalid-reloc.json
 >>>2 /key 'name': Unable to read String/
 >>>= !0
 
 # and now start the real tests
-./htest/hail $TESTDATA_DIR/hail-alloc-drbd.json
+./test/hs/hail $TESTDATA_DIR/hail-alloc-drbd.json
 >>> /"success":true,.*,"result":\["node2","node1"\]/
 >>>= 0
 
-./htest/hail $TESTDATA_DIR/hail-reloc-drbd.json
+./test/hs/hail $TESTDATA_DIR/hail-reloc-drbd.json
 >>> /"success":true,.*,"result":\["node1"\]/
 >>>= 0
 
-./htest/hail $TESTDATA_DIR/hail-node-evac.json
+./test/hs/hail $TESTDATA_DIR/hail-node-evac.json
 >>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
 >>>= 0
 
-./htest/hail $TESTDATA_DIR/hail-change-group.json
+./test/hs/hail $TESTDATA_DIR/hail-change-group.json
 >>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
 >>>= 0
 
 # check that hail can use the simu backend
-./htest/hail --simu p,8,8T,16g,16 $TESTDATA_DIR/hail-alloc-drbd.json
+./test/hs/hail --simu p,8,8T,16g,16 $TESTDATA_DIR/hail-alloc-drbd.json
 >>> /"success":true,/
 >>>= 0
 
 # check that hail can use the text backend
-./htest/hail -t $T/simu-rebal-merged.standard $TESTDATA_DIR/hail-alloc-drbd.json
+./test/hs/hail -t $T/simu-rebal-merged.standard $TESTDATA_DIR/hail-alloc-drbd.json
 >>> /"success":true,/
 >>>= 0
 
 # check that hail can use the simu backend
-./htest/hail -t $T/simu-rebal-merged.standard $TESTDATA_DIR/hail-alloc-drbd.json
+./test/hs/hail -t $T/simu-rebal-merged.standard $TESTDATA_DIR/hail-alloc-drbd.json
 >>> /"success":true,/
 >>>= 0
 
 # check that hail pre/post saved state differs after allocation
-./htest/hail -v -v -v -p $TESTDATA_DIR/hail-alloc-drbd.json -S $T/hail-alloc >/dev/null 2>&1 && ! diff -q $T/hail-alloc.pre-ialloc $T/hail-alloc.post-ialloc
+./test/hs/hail -v -v -v -p $TESTDATA_DIR/hail-alloc-drbd.json -S $T/hail-alloc >/dev/null 2>&1 && ! diff -q $T/hail-alloc.pre-ialloc $T/hail-alloc.post-ialloc
 >>> /Files .* and .* differ/
 >>>= 0
 
 # check that hail pre/post saved state differs after relocation
-./htest/hail -v -v -v -p $TESTDATA_DIR/hail-reloc-drbd.json -S $T/hail-reloc >/dev/null 2>&1 && ! diff -q $T/hail-reloc.pre-ialloc $T/hail-reloc.post-ialloc
+./test/hs/hail -v -v -v -p $TESTDATA_DIR/hail-reloc-drbd.json -S $T/hail-reloc >/dev/null 2>&1 && ! diff -q $T/hail-reloc.pre-ialloc $T/hail-reloc.post-ialloc
 >>> /Files .* and .* differ/
 >>>= 0
 
 # evac tests
-./htest/hail $T/hail-node-evac.json.primary-only
+./test/hs/hail $T/hail-node-evac.json.primary-only
 >>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
 >>>= 0
 
-./htest/hail $T/hail-node-evac.json.secondary-only
+./test/hs/hail $T/hail-node-evac.json.secondary-only
 >>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
 >>>= 0
 
-./htest/hail $T/hail-node-evac.json.all
+./test/hs/hail $T/hail-node-evac.json.all
 >>> /"success":true,"info":"Request successful: 0 instances failed to move and 1 were moved successfully"/
 >>>= 0
diff --git a/test/hs/shelltests/htools-hspace.test b/test/hs/shelltests/htools-hspace.test
new file mode 100644
index 000000000..553c98dff
--- /dev/null
+++ b/test/hs/shelltests/htools-hspace.test
@@ -0,0 +1,8 @@
+# test that hspace machine readable output looks correct
+./test/hs/hspace --simu p,4,8T,64g,16 --machine-readable --disk-template drbd -l 8
+>>> /^HTS_OK=1/
+>>>= 0
+
+# test again via a file and shell parsing
+./test/hs/hspace --simu p,4,8T,64g,16 --machine-readable --disk-template drbd -l 8 > $T/capacity && sh -c ". $T/capacity && test x\$HTS_OK = x1"
+>>>= 0
diff --git a/htest/shelltests/htools-invalid.test b/test/hs/shelltests/htools-invalid.test
similarity index 57%
rename from htest/shelltests/htools-invalid.test
rename to test/hs/shelltests/htools-invalid.test
index 649c54025..4c08a5859 100644
--- a/htest/shelltests/htools-invalid.test
+++ b/test/hs/shelltests/htools-invalid.test
@@ -1,59 +1,59 @@
 # invalid option test
-./htest/hail --no-such-option
+./test/hs/hail --no-such-option
 >>>= 2
 
 # invalid option test
-./htest/hbal --no-such-option
+./test/hs/hbal --no-such-option
 >>>= 2
 
 # invalid option test
-./htest/hspace --no-such-option
+./test/hs/hspace --no-such-option
 >>>= 2
 
 # invalid option test
-./htest/hscan --no-such-option
+./test/hs/hscan --no-such-option
 >>>= 2
 
 # invalid option test
-./htest/hinfo --no-such-option
+./test/hs/hinfo --no-such-option
 >>>= 2
 
 # invalid option test
-./htest/hcheck --no-such-option
+./test/hs/hcheck --no-such-option
 >>>= 2
 
 # invalid option test
-./htest/hroller --no-such-option
+./test/hs/hroller --no-such-option
 >>>= 2
 
 # extra arguments
-./htest/hspace unexpected-argument
+./test/hs/hspace unexpected-argument
 >>>2
 Error: This program doesn't take any arguments.
 >>>=1
 
-./htest/hbal unexpected-argument
+./test/hs/hbal unexpected-argument
 >>>2
 Error: This program doesn't take any arguments.
 >>>=1
 
-./htest/hinfo unexpected-argument
+./test/hs/hinfo unexpected-argument
 >>>2
 Error: This program doesn't take any arguments.
 >>>=1
 
-./htest/hcheck unexpected-argument
+./test/hs/hcheck unexpected-argument
 >>>2
 Error: This program doesn't take any arguments.
 >>>=1
 
-./htest/hroller unexpected-argument
+./test/hs/hroller unexpected-argument
 >>>2
 Error: This program doesn't take any arguments.
 >>>=1
 
 # hroller fails to build a graph for an empty cluster
-./htest/hroller -t$TESTDATA_DIR/empty-cluster.data
+./test/hs/hroller -t$TESTDATA_DIR/empty-cluster.data
 >>>2
 Error: Cannot create node graph
 >>>=1
diff --git a/htest/shelltests/htools-mon-collector.test b/test/hs/shelltests/htools-mon-collector.test
similarity index 93%
rename from htest/shelltests/htools-mon-collector.test
rename to test/hs/shelltests/htools-mon-collector.test
index de276e168..e0ab7a309 100644
--- a/htest/shelltests/htools-mon-collector.test
+++ b/test/hs/shelltests/htools-mon-collector.test
@@ -1,29 +1,29 @@
 # 1. Test that mon-collector won't run without specifying a personality
-./htest/hpc-mon-collector
+./test/hs/hpc-mon-collector
 >>>= !0
 
 # 2. Test that standard options are accepted, both at top level
 # and subcommands level
-./htest/hpc-mon-collector --help
+./test/hs/hpc-mon-collector --help
 >>>= 0
 
-./htest/hpc-mon-collector --help-completion
+./test/hs/hpc-mon-collector --help-completion
 >>>= 0
 
-./htest/hpc-mon-collector --version
+./test/hs/hpc-mon-collector --version
 >>>= 0
 
-./htest/hpc-mon-collector drbd --help
+./test/hs/hpc-mon-collector drbd --help
 >>>= 0
 
-./htest/hpc-mon-collector drbd --help-completion
+./test/hs/hpc-mon-collector drbd --help-completion
 >>>= 0
 
-./htest/hpc-mon-collector drbd --version
+./test/hs/hpc-mon-collector drbd --version
 >>>= 0
 
 # 3. Test that the drbd collector fails parsing /dev/null
-./htest/hpc-mon-collector drbd /dev/null
+./test/hs/hpc-mon-collector drbd /dev/null
 >>>2
 Error: ""
 []
@@ -31,22 +31,22 @@ Failed reading: versionInfo
 >>>= !0
 
 # 4. Test that a non-existent file is correctly reported
-./htest/hpc-mon-collector drbd /dev/no-such-file
+./test/hs/hpc-mon-collector drbd /dev/no-such-file
 >>>2/Error: reading from file: .* does not exist/
 >>>= !0
 
 # 5. Test that multiple files are rejected
-./htest/hpc-mon-collector drbd /dev/null /dev/null
+./test/hs/hpc-mon-collector drbd /dev/null /dev/null
 >>>2/takes only one argument/
 >>>= !0
 
 # 6. Test that a standard test file is parsed correctly
-./htest/hpc-mon-collector drbd $PYTESTDATA_DIR/proc_drbd83.txt
+./test/hs/hpc-mon-collector drbd $PYTESTDATA_DIR/proc_drbd83.txt
 >>>=0
 
 # 7. Test that the drbd collector fails parsing /dev/zero, but is not
 # stuck forever printing \NUL chars
-./htest/hpc-mon-collector drbd /dev/zero
+./test/hs/hpc-mon-collector drbd /dev/zero
 >>>2
 Error: "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL"
 []
diff --git a/htest/shelltests/htools-multi-group.test b/test/hs/shelltests/htools-multi-group.test
similarity index 52%
rename from htest/shelltests/htools-multi-group.test
rename to test/hs/shelltests/htools-multi-group.test
index 34a41b487..7bb1a21a6 100644
--- a/htest/shelltests/htools-multi-group.test
+++ b/test/hs/shelltests/htools-multi-group.test
@@ -1,47 +1,47 @@
 # standard multi-group tests
-./htest/hinfo -v -v -p --print-instances -t$T/simu-twogroups.standard
+./test/hs/hinfo -v -v -p --print-instances -t$T/simu-twogroups.standard
 >>>= 0
-./htest/hbal -t$T/simu-twogroups.standard
+./test/hs/hbal -t$T/simu-twogroups.standard
 >>>= !0
 
 # hbal should not be able to balance
-./htest/hbal -t$T/simu-twogroups.standard
+./test/hs/hbal -t$T/simu-twogroups.standard
 >>>2 /Found multiple node groups/
 >>>= !0
 
 # but hbal should be able to balance one node group
-./htest/hbal -t$T/simu-twogroups.standard -G group-01
+./test/hs/hbal -t$T/simu-twogroups.standard -G group-01
 >>>= 0
 # and it should not find an invalid group
-./htest/hbal -t$T/simu-twogroups.standard -G no-such-group
+./test/hs/hbal -t$T/simu-twogroups.standard -G no-such-group
 >>>= !0
 
 # tiered allocs multi-group tests
-./htest/hinfo -v -v -p --print-instances -t$T/simu-twogroups.tiered
+./test/hs/hinfo -v -v -p --print-instances -t$T/simu-twogroups.tiered
 >>>= 0
-./htest/hbal -t$T/simu-twogroups.tiered
+./test/hs/hbal -t$T/simu-twogroups.tiered
 >>>= !0
 
 # hbal should not be able to balance
-./htest/hbal -t$T/simu-twogroups.tiered
+./test/hs/hbal -t$T/simu-twogroups.tiered
 >>>2 /Found multiple node groups/
 >>>= !0
 
 # but hbal should be able to balance one node group
-./htest/hbal -t$T/simu-twogroups.tiered -G group-01
+./test/hs/hbal -t$T/simu-twogroups.tiered -G group-01
 >>>= 0
 # and it should not find an invalid group
-./htest/hbal -t$T/simu-twogroups.tiered -G no-such-group
+./test/hs/hbal -t$T/simu-twogroups.tiered -G no-such-group
 >>>= !0
 
 # hcheck should be able to run with multiple groups
-./htest/hcheck -t$T/simu-twogroups.tiered --machine-readable
+./test/hs/hcheck -t$T/simu-twogroups.tiered --machine-readable
 >>> /HCHECK_OK=1/
 >>>= 0
 
 # hcheck should be able to improve a group with split instances, and also
 # warn us about them
-./htest/hbal -t $TESTDATA_DIR/hbal-split-insts.data -G group-01 -O node-01-001 -v
+./test/hs/hbal -t $TESTDATA_DIR/hbal-split-insts.data -G group-01 -O node-01-001 -v
 >>> /Cluster score improved from .* to .*/
 >>>2/Found instances belonging to multiple node groups:/
 >>>= 0
diff --git a/htest/shelltests/htools-no-backend.test b/test/hs/shelltests/htools-no-backend.test
similarity index 67%
rename from htest/shelltests/htools-no-backend.test
rename to test/hs/shelltests/htools-no-backend.test
index fc2ac9c38..66c14dee8 100644
--- a/htest/shelltests/htools-no-backend.test
+++ b/test/hs/shelltests/htools-no-backend.test
@@ -1,25 +1,25 @@
 # hail no input file
-./htest/hail
+./test/hs/hail
 >>>= 1
 
 # hbal no backend
-./htest/hbal
+./test/hs/hbal
 >>>= 1
 
 # hspace no backend
-./htest/hspace
+./test/hs/hspace
 >>>= 1
 
 # hinfo no backend
-./htest/hinfo
+./test/hs/hinfo
 >>>= 1
 
 # hroller no backend
-./htest/hroller
+./test/hs/hroller
 >>>= 1
 
 # hbal multiple backends
-./htest/hbal -t /dev/null -m localhost
+./test/hs/hbal -t /dev/null -m localhost
 >>>2
 Error: Only one of the rapi, luxi, and data files options should be given.
 >>>= 1
diff --git a/htest/shelltests/htools-rapi.test b/test/hs/shelltests/htools-rapi.test
similarity index 57%
rename from htest/shelltests/htools-rapi.test
rename to test/hs/shelltests/htools-rapi.test
index a0692c95a..ffe2991ac 100644
--- a/htest/shelltests/htools-rapi.test
+++ b/test/hs/shelltests/htools-rapi.test
@@ -1,8 +1,8 @@
 # test loading data via RAPI
-./htest/hinfo -v -v -p --print-instances -m $RAPI_URL
+./test/hs/hinfo -v -v -p --print-instances -m $RAPI_URL
 >>>= 0
 
-./htest/hbal -v -v -p --print-instances -m $RAPI_URL
+./test/hs/hbal -v -v -p --print-instances -m $RAPI_URL
 >>>= 0
 
 # this compares generated files from hscan
diff --git a/test/hs/shelltests/htools-single-group.test b/test/hs/shelltests/htools-single-group.test
new file mode 100644
index 000000000..f8e629cb5
--- /dev/null
+++ b/test/hs/shelltests/htools-single-group.test
@@ -0,0 +1,37 @@
+# standard single-group tests
+./test/hs/hinfo -v -v -p --print-instances -t$T/simu-onegroup.standard
+>>>= 0
+
+./test/hs/hbal  -v -v -p --print-instances -t$T/simu-onegroup.standard
+>>>= 0
+
+# hbal should not be able to balance
+./test/hs/hbal -t$T/simu-onegroup.standard
+>>> /(Nothing to do, exiting|No solution found)/
+>>>= 0
+
+
+# tiered single-group tests
+./test/hs/hinfo -v -v -p --print-instances -t$T/simu-onegroup.tiered
+>>>= 0
+
+./test/hs/hbal  -v -v -p --print-instances -t$T/simu-onegroup.tiered
+>>>= 0
+
+# hbal should not be able to balance
+./test/hs/hbal -t$T/simu-onegroup.tiered
+>>> /(Nothing to do, exiting|No solution found)/
+>>>= 0
+
+# hcheck should not find reason to rebalance
+./test/hs/hcheck -t$T/simu-onegroup.tiered --machine-readable
+>>> /HCHECK_INIT_CLUSTER_NEED_REBALANCE=0/
+>>>= 0
+
+# hroller should be able to print the solution
+./test/hs/hroller -t$T/simu-onegroup.tiered
+>>>= 0
+
+# hroller should be able to print the solution, in verbose mode as well
+./test/hs/hroller -t$T/simu-onegroup.tiered -v -v
+>>>= 0
diff --git a/htest/shelltests/htools-text-backend.test b/test/hs/shelltests/htools-text-backend.test
similarity index 55%
rename from htest/shelltests/htools-text-backend.test
rename to test/hs/shelltests/htools-text-backend.test
index ab89804b7..839885276 100644
--- a/htest/shelltests/htools-text-backend.test
+++ b/test/hs/shelltests/htools-text-backend.test
@@ -1,30 +1,30 @@
 # missing resources test
-./htest/hbal -t $TESTDATA_DIR/missing-resources.data
+./test/hs/hbal -t $TESTDATA_DIR/missing-resources.data
 >>>2 /node node2 is missing .* ram and .* disk/
 >>>= 0
-./htest/hinfo -t $TESTDATA_DIR/missing-resources.data
+./test/hs/hinfo -t $TESTDATA_DIR/missing-resources.data
 >>>2 /node node2 is missing .* ram and .* disk/
 >>>= 0
 
 
 # common suffix test
-./htest/hbal -t $TESTDATA_DIR/common-suffix.data -v -v
+./test/hs/hbal -t $TESTDATA_DIR/common-suffix.data -v -v
 >>>/Stripping common suffix of '\.example\.com' from names/
 >>>= 0
-./htest/hinfo -t $TESTDATA_DIR/common-suffix.data -v -v
+./test/hs/hinfo -t $TESTDATA_DIR/common-suffix.data -v -v
 >>>/Stripping common suffix of '\.example\.com' from names/
 >>>= 0
 
 
 # invalid node test
-./htest/hbal -t $TESTDATA_DIR/invalid-node.data
+./test/hs/hbal -t $TESTDATA_DIR/invalid-node.data
 >>>2 /Unknown node '.*' for instance new-0/
 >>>= !0
 
-./htest/hspace -t $TESTDATA_DIR/invalid-node.data
+./test/hs/hspace -t $TESTDATA_DIR/invalid-node.data
 >>>2 /Unknown node '.*' for instance new-0/
 >>>= !0
 
-./htest/hinfo -t $TESTDATA_DIR/invalid-node.data
+./test/hs/hinfo -t $TESTDATA_DIR/invalid-node.data
 >>>2 /Unknown node '.*' for instance new-0/
 >>>= !0
diff --git a/htest/test.hs b/test/hs/test.hs
similarity index 100%
rename from htest/test.hs
rename to test/hs/test.hs
-- 
GitLab