aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2007-04-15 22:57:51 +0000
committerAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2007-04-15 22:57:51 +0000
commitc27a1dc3115a1c282c3ec010fe46128c119b5751 (patch)
tree058eb32e0178c8ca947ddea8f45ec554962c4912
parent0abd2e2f37f8edf61a4a03ae8a403dc3fcb8b502 (diff)
downloadpaludis-c27a1dc3115a1c282c3ec010fe46128c119b5751.tar.gz
paludis-c27a1dc3115a1c282c3ec010fe46128c119b5751.tar.xz
New .hook format
-rw-r--r--paludis/Makefile.am.m48
-rw-r--r--paludis/files.m42
-rwxr-xr-xpaludis/hooker.bash37
-rw-r--r--paludis/hooker.cc323
-rw-r--r--paludis/hooker_TEST.cc89
-rwxr-xr-xpaludis/hooker_TEST_cleanup.sh9
-rwxr-xr-xpaludis/hooker_TEST_setup.sh93
7 files changed, 503 insertions, 58 deletions
diff --git a/paludis/Makefile.am.m4 b/paludis/Makefile.am.m4
index 0b477cd..676f589 100644
--- a/paludis/Makefile.am.m4
+++ b/paludis/Makefile.am.m4
@@ -78,7 +78,7 @@ DEFS= \
-DLIBDIR=\"$(libdir)\"
EXTRA_DIST = about.hh.in Makefile.am.m4 paludis.hh.m4 files.m4 \
hashed_containers.hh.in testscriptlist srlist srcleanlist selist secleanlist \
- repository_blacklist.txt
+ repository_blacklist.txt hooker.bash
SUBDIRS = digests fetchers syncers util selinux . dep_list merger repositories environments args qa tasks
BUILT_SOURCES = srcleanlist secleanlist
@@ -100,7 +100,8 @@ endif
TESTS = testlist
TESTS_ENVIRONMENT = env \
- PALUDIS_EBUILD_DIR="$(top_srcdir)/ebuild/" \
+ PALUDIS_EBUILD_DIR="$(top_srcdir)/paludis/repositories/gentoo/ebuild/" \
+ PALUDIS_HOOKER_DIR="$(top_srcdir)/paludis/" \
PALUDIS_SKIP_CONFIG="yes" \
PALUDIS_REPOSITORY_SO_DIR="$(top_builddir)/paludis/repositories" \
TEST_SCRIPT_DIR="$(srcdir)/" \
@@ -112,6 +113,9 @@ check_SCRIPTS = testscriptlist
paludis_datadir = $(datadir)/paludis
paludis_data_DATA = repository_blacklist.txt
+paludis_libexecdir = $(libexecdir)/paludis
+paludis_libexec_SCRIPTS = hooker.bash
+
if MONOLITHIC
noinst_LTLIBRARIES = libpaludis.la libpaludismanpagethings.la
diff --git a/paludis/files.m4 b/paludis/files.m4
index 02a91f0..7f7673a 100644
--- a/paludis/files.m4
+++ b/paludis/files.m4
@@ -17,7 +17,7 @@ add(`dep_spec_pretty_printer', `hh', `cc')
add(`dep_tag', `hh', `cc', `sr')
add(`environment', `hh', `cc')
add(`hashed_containers', `hhx', `cc', `test')
-add(`hooker', `hh', `cc')
+add(`hooker', `hh', `cc', `test', `testscript')
add(`host_tuple_name', `hh', `cc', `sr', `test')
add(`mask_reasons', `hh', `cc', `se')
add(`match_package', `hh', `cc')
diff --git a/paludis/hooker.bash b/paludis/hooker.bash
new file mode 100755
index 0000000..67c4a07
--- /dev/null
+++ b/paludis/hooker.bash
@@ -0,0 +1,37 @@
+#!/bin/bash
+# vim: set sw=4 sts=4 et :
+
+# Copyright (c) 2007 Ciaran McCreesh <ciaranm@ciaranm.org>
+#
+# This file is part of the Paludis package manager. Paludis is free software;
+# you can redistribute it and/or modify it under the terms of the GNU General
+# Public License, version 2, as published by the Free Software Foundation.
+#
+# Paludis 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., 59 Temple
+# Place, Suite 330, Boston, MA 02111-1307 USA
+
+unalias -a
+set +C
+unset GZIP BZIP BZIP2 CDPATH GREP_OPTIONS GREP_COLOR GLOBIGNORE
+eval unset LANG ${!LC_*}
+
+shopt -s expand_aliases
+shopt -s extglob
+
+if ! source $1 ; then
+ echo "Error sourcing '$1' for hook '$HOOK'" 1>&2
+ exit 123
+fi
+
+if [[ $(type $2 2>/dev/null ) != "function" ]] ; then
+ echo "Error running undefined function '$2' from file '$1' for hook '$HOOK'" 1>&2
+fi
+
+$2
+
diff --git a/paludis/hooker.cc b/paludis/hooker.cc
index df10811..dfb3ca2 100644
--- a/paludis/hooker.cc
+++ b/paludis/hooker.cc
@@ -27,11 +27,216 @@
#include <paludis/util/is_file_with_extension.hh>
#include <paludis/util/system.hh>
#include <paludis/util/strip.hh>
+#include <paludis/util/graph.hh>
+#include <paludis/util/graph-impl.hh>
+#include <paludis/util/pstream.hh>
+#include <paludis/util/tokeniser.hh>
#include <list>
+#include <iterator>
using namespace paludis;
-typedef MakeHashedMap<std::string, bool>::Type HookPresentCache;
+namespace
+{
+ class HookFile;
+
+ class HookFile :
+ private InstantiationPolicy<HookFile, instantiation_method::NonCopyableTag>
+ {
+ public:
+ virtual ~HookFile()
+ {
+ }
+
+ virtual int run(const Hook &) const PALUDIS_ATTRIBUTE((warn_unused_result)) = 0;
+ virtual const FSEntry file_name() const = 0;
+ virtual void add_dependencies(const Hook &, DirectedGraph<std::string, int> &) = 0;
+ };
+
+ class BashHookFile :
+ public HookFile
+ {
+ private:
+ const FSEntry _file_name;
+ const bool _run_prefixed;
+ const Environment * const _env;
+
+ public:
+ BashHookFile(const FSEntry & f, const bool r, const Environment * const e) :
+ _file_name(f),
+ _run_prefixed(r),
+ _env(e)
+ {
+ }
+
+ virtual int run(const Hook &) const PALUDIS_ATTRIBUTE((warn_unused_result));
+
+ virtual const FSEntry file_name() const
+ {
+ return _file_name;
+ }
+
+ virtual void add_dependencies(const Hook &, DirectedGraph<std::string, int> &)
+ {
+ }
+ };
+
+ class FancyHookFile :
+ public HookFile
+ {
+ private:
+ const FSEntry _file_name;
+ const bool _run_prefixed;
+ const Environment * const _env;
+
+ public:
+ FancyHookFile(const FSEntry & f, const bool r, const Environment * const e) :
+ _file_name(f),
+ _run_prefixed(r),
+ _env(e)
+ {
+ }
+
+ virtual int run(const Hook &) const PALUDIS_ATTRIBUTE((warn_unused_result));
+
+ virtual const FSEntry file_name() const
+ {
+ return _file_name;
+ }
+
+ virtual void add_dependencies(const Hook &, DirectedGraph<std::string, int> &);
+ };
+}
+
+int
+BashHookFile::run(const Hook & hook) const
+{
+ Context c("When running hook script '" + stringify(file_name()) + "' for hook '" + hook.name() + "':");
+
+ Log::get_instance()->message(ll_debug, lc_no_context, "Starting hook script '" +
+ stringify(file_name()) + "' for '" + hook.name() + "'");
+
+ Command cmd(Command("bash '" + stringify(file_name()) + "'")
+ .with_setenv("ROOT", stringify(_env->root()))
+ .with_setenv("HOOK", hook.name())
+ .with_setenv("HOOK_FILE", stringify(file_name()))
+ .with_setenv("HOOK_LOG_LEVEL", stringify(Log::get_instance()->log_level()))
+ .with_setenv("PALUDIS_EBUILD_DIR", getenv_with_default("PALUDIS_EBUILD_DIR", LIBEXECDIR "/paludis"))
+ .with_setenv("PALUDIS_REDUCED_GID", stringify(_env->reduced_gid()))
+ .with_setenv("PALUDIS_REDUCED_UID", stringify(_env->reduced_uid()))
+ .with_setenv("PALUDIS_COMMAND", _env->paludis_command()));
+
+ if (_run_prefixed)
+ cmd
+ .with_stdout_prefix(strip_trailing_string(file_name().basename(), ".bash") + "> ")
+ .with_stderr_prefix(strip_trailing_string(file_name().basename(), ".bash") + "> ");
+
+ for (Hook::Iterator x(hook.begin()), x_end(hook.end()) ; x != x_end ; ++x)
+ cmd.with_setenv(x->first, x->second);
+
+ int exit_status(run_command(cmd));
+ if (0 == exit_status)
+ Log::get_instance()->message(ll_debug, lc_no_context, "Hook '" + stringify(file_name())
+ + "' returned success '" + stringify(exit_status) + "'");
+ else
+ Log::get_instance()->message(ll_warning, lc_no_context, "Hook '" + stringify(file_name())
+ + "' returned failure '" + stringify(exit_status) + "'");
+
+ return exit_status;
+}
+
+int
+FancyHookFile::run(const Hook & hook) const
+{
+ Context c("When running hook script '" + stringify(file_name()) + "' for hook '" + hook.name() + "':");
+
+ Log::get_instance()->message(ll_debug, lc_no_context, "Starting hook script '" +
+ stringify(file_name()) + "' for '" + hook.name() + "'");
+
+ Command cmd(getenv_with_default("PALUDIS_HOOKER_DIR", LIBEXECDIR "/paludis") +
+ "/hooker.bash '" + stringify(file_name()) + "' 'hook_run_" + stringify(hook.name()) + "'");
+
+ cmd
+ .with_setenv("ROOT", stringify(_env->root()))
+ .with_setenv("HOOK", hook.name())
+ .with_setenv("HOOK_FILE", stringify(file_name()))
+ .with_setenv("HOOK_LOG_LEVEL", stringify(Log::get_instance()->log_level()))
+ .with_setenv("PALUDIS_EBUILD_DIR", getenv_with_default("PALUDIS_EBUILD_DIR", LIBEXECDIR "/paludis"))
+ .with_setenv("PALUDIS_REDUCED_GID", stringify(_env->reduced_gid()))
+ .with_setenv("PALUDIS_REDUCED_UID", stringify(_env->reduced_uid()))
+ .with_setenv("PALUDIS_COMMAND", _env->paludis_command());
+
+ if (_run_prefixed)
+ cmd
+ .with_stdout_prefix(strip_trailing_string(file_name().basename(), ".bash") + "> ")
+ .with_stderr_prefix(strip_trailing_string(file_name().basename(), ".bash") + "> ");
+
+ for (Hook::Iterator x(hook.begin()), x_end(hook.end()) ; x != x_end ; ++x)
+ cmd.with_setenv(x->first, x->second);
+
+ int exit_status(run_command(cmd));
+ if (0 == exit_status)
+ Log::get_instance()->message(ll_debug, lc_no_context, "Hook '" + stringify(file_name())
+ + "' returned success '" + stringify(exit_status) + "'");
+ else
+ Log::get_instance()->message(ll_warning, lc_no_context, "Hook '" + stringify(file_name())
+ + "' returned failure '" + stringify(exit_status) + "'");
+
+ return exit_status;
+}
+
+void
+FancyHookFile::add_dependencies(const Hook & hook, DirectedGraph<std::string, int> & g)
+{
+ Context c("When finding dependencies of hook script '" + stringify(file_name()) + "' for hook '" + hook.name() + "':");
+
+ Log::get_instance()->message(ll_debug, lc_no_context, "Starting hook script '" +
+ stringify(file_name()) + "' for dependencies of '" + hook.name() + "'");
+
+ Command cmd(getenv_with_default("PALUDIS_HOOKER_DIR", LIBEXECDIR "/paludis") +
+ "/hooker.bash '" + stringify(file_name()) + "' 'hook_depend_" + stringify(hook.name()) + "'");
+
+ cmd
+ .with_setenv("ROOT", stringify(_env->root()))
+ .with_setenv("HOOK", hook.name())
+ .with_setenv("HOOK_FILE", stringify(file_name()))
+ .with_setenv("HOOK_LOG_LEVEL", stringify(Log::get_instance()->log_level()))
+ .with_setenv("PALUDIS_EBUILD_DIR", getenv_with_default("PALUDIS_EBUILD_DIR", LIBEXECDIR "/paludis"))
+ .with_setenv("PALUDIS_REDUCED_GID", stringify(_env->reduced_gid()))
+ .with_setenv("PALUDIS_REDUCED_UID", stringify(_env->reduced_uid()))
+ .with_setenv("PALUDIS_COMMAND", _env->paludis_command());
+
+ cmd.with_stderr_prefix(strip_trailing_string(file_name().basename(), ".bash") + " (depend)> ");
+
+ for (Hook::Iterator x(hook.begin()), x_end(hook.end()) ; x != x_end ; ++x)
+ cmd.with_setenv(x->first, x->second);
+
+ PStream s(cmd);
+ std::string deps((std::istreambuf_iterator<char>(s)), std::istreambuf_iterator<char>());
+
+ int exit_status(s.exit_status());
+ if (0 == exit_status)
+ {
+ Log::get_instance()->message(ll_debug, lc_no_context, "Hook dependencies for '" + stringify(file_name())
+ + "' returned success '" + stringify(exit_status) + "', result '" + deps + "'");
+
+ std::set<std::string> deps_s;
+ WhitespaceTokeniser::get_instance()->tokenise(deps, std::inserter(deps_s, deps_s.end()));
+
+ for (std::set<std::string>::const_iterator d(deps_s.begin()), d_end(deps_s.end()) ;
+ d != d_end ; ++d)
+ {
+ if (g.has_node(*d))
+ g.add_edge(strip_trailing_string(file_name().basename(), ".hook"), *d, 0);
+ else
+ Log::get_instance()->message(ll_warning, lc_context, "Hook dependency '" + stringify(*d) +
+ "' for '" + stringify(file_name()) + "' not found");
+ }
+ }
+ else
+ Log::get_instance()->message(ll_warning, lc_no_context, "Hook dependencies for '" + stringify(file_name())
+ + "' returned failure '" + stringify(exit_status) + "'");
+}
namespace paludis
{
@@ -39,8 +244,8 @@ namespace paludis
struct Implementation<Hooker>
{
const Environment * const env;
- mutable HookPresentCache hook_cache;
std::list<std::pair<FSEntry, bool> > dirs;
+ mutable std::map<std::string, std::list<std::tr1::shared_ptr<HookFile> > > hook_files;
Implementation(const Environment * const e) :
env(e)
@@ -49,6 +254,7 @@ namespace paludis
};
}
+
Hooker::Hooker(const Environment * const e) :
PrivateImplementationPattern<Hooker>(new Implementation<Hooker>(e))
{
@@ -61,7 +267,7 @@ Hooker::~Hooker()
void
Hooker::add_dir(const FSEntry & dir, const bool v)
{
- _imp->hook_cache.clear();
+ _imp->hook_files.clear();
_imp->dirs.push_back(std::make_pair(dir, v));
}
@@ -70,7 +276,7 @@ Hooker::perform_hook(const Hook & hook) const
{
int max_exit_status(0);
- Context context("When triggering hook '" + hook.name() + "'");
+ Context context("When triggering hook '" + hook.name() + "':");
Log::get_instance()->message(ll_debug, lc_no_context, "Starting hook '" + hook.name() + "'");
/* repo hooks first */
@@ -82,66 +288,73 @@ Hooker::perform_hook(const Hook & hook) const
/* file hooks, but only if necessary */
- HookPresentCache::iterator cache_entry(_imp->hook_cache.end());
+ std::map<std::string, std::list<std::tr1::shared_ptr<HookFile> > >::iterator h(_imp->hook_files.find(hook.name()));
- if (_imp->hook_cache.end() != ((cache_entry = _imp->hook_cache.find(hook.name()))))
- if (! cache_entry->second)
- return max_exit_status;
-
- bool had_hook(false);
- for (std::list<std::pair<FSEntry, bool> >::const_iterator h(_imp->dirs.begin()), h_end(_imp->dirs.end()) ;
- h != h_end ; ++h)
+ if (h == _imp->hook_files.end())
{
- FSEntry hh(h->first / hook.name());
- if (! hh.is_directory())
- continue;
-
- std::list<FSEntry> hooks;
- std::copy(DirIterator(hh), DirIterator(),
- filter_inserter(std::back_inserter(hooks), IsFileWithExtension(".bash")));
+ h = _imp->hook_files.insert(std::make_pair(hook.name(), std::list<std::tr1::shared_ptr<HookFile> >())).first;
- if (! hooks.empty())
- had_hook = true;
+ std::map<std::string, std::tr1::shared_ptr<HookFile> > hook_files;
- for (std::list<FSEntry>::const_iterator hk(hooks.begin()),
- hk_end(hooks.end()) ; hk != hk_end ; ++hk)
+ for (std::list<std::pair<FSEntry, bool> >::const_iterator d(_imp->dirs.begin()), d_end(_imp->dirs.end()) ;
+ d != d_end ; ++d)
{
- Context c("When running hook script '" + stringify(*hk) +
- "' for hook '" + hook.name() + "':");
- Log::get_instance()->message(ll_debug, lc_no_context, "Starting hook script '" +
- stringify(hh) + "' for '" + hook.name() + "'");
-
- Command cmd(Command("bash '" + stringify(*hk) + "'")
- .with_setenv("ROOT", stringify(_imp->env->root()))
- .with_setenv("HOOK", hook.name())
- .with_setenv("HOOK_LOG_LEVEL", stringify(Log::get_instance()->log_level()))
- .with_setenv("PALUDIS_EBUILD_DIR", getenv_with_default("PALUDIS_EBUILD_DIR", LIBEXECDIR "/paludis"))
- .with_setenv("PALUDIS_REDUCED_GID", stringify(_imp->env->reduced_gid()))
- .with_setenv("PALUDIS_REDUCED_UID", stringify(_imp->env->reduced_uid()))
- .with_setenv("PALUDIS_COMMAND", _imp->env->paludis_command()));
-
- if (h->second)
- cmd
- .with_stdout_prefix(strip_trailing_string(hk->basename(), ".bash") + "> ")
- .with_stderr_prefix(strip_trailing_string(hk->basename(), ".bash") + "> ");
-
- for (Hook::Iterator x(hook.begin()), x_end(hook.end()) ; x != x_end ; ++x)
- cmd.with_setenv(x->first, x->second);
-
- int exit_status(run_command(cmd));
- if (0 == exit_status)
- Log::get_instance()->message(ll_debug, lc_no_context, "Hook '" + stringify(*hk)
- + "' returned success '" + stringify(exit_status) + "'");
- else
- Log::get_instance()->message(ll_warning, lc_no_context, "Hook '" + stringify(*hk)
- + "' returned failure '" + stringify(exit_status) + "'");
+ if (! (d->first / hook.name()).is_directory())
+ continue;
+
+ for (DirIterator e(d->first / hook.name()), e_end ; e != e_end ; ++e)
+ {
+ if (IsFileWithExtension(".bash")(*e))
+ if (! hook_files.insert(std::make_pair(strip_trailing_string(e->basename(), ".bash"),
+ std::tr1::shared_ptr<HookFile>(new BashHookFile(*e, d->second, _imp->env)))).second)
+ Log::get_instance()->message(ll_warning, lc_context, "Discarding hook file '" + stringify(*e)
+ + "' because of naming conflict with '" + stringify(
+ hook_files.find(stringify(strip_trailing_string(e->basename(), ".bash")))->second->file_name()) + "'");
+
+ if (IsFileWithExtension(".hook")(*e))
+ if (! hook_files.insert(std::make_pair(strip_trailing_string(e->basename(), ".hook"),
+ std::tr1::shared_ptr<HookFile>(new FancyHookFile(*e, d->second, _imp->env)))).second)
+ Log::get_instance()->message(ll_warning, lc_context, "Discarding hook file '" + stringify(*e)
+ + "' because of naming conflict with '" + stringify(
+ hook_files.find(stringify(strip_trailing_string(e->basename(), ".hook")))->second->file_name()) + "'");
+ }
+ }
- max_exit_status = std::max(max_exit_status, exit_status);
+ Context context_local("When determining hook execution order for '" + hook.name() + "':");
+
+ DirectedGraph<std::string, int> hook_deps;
+ for (std::map<std::string, std::tr1::shared_ptr<HookFile> >::const_iterator f(hook_files.begin()), f_end(hook_files.end()) ;
+ f != f_end ; ++f)
+ hook_deps.add_node(f->first);
+
+ for (std::map<std::string, std::tr1::shared_ptr<HookFile> >::const_iterator f(hook_files.begin()), f_end(hook_files.end()) ;
+ f != f_end ; ++f)
+ f->second->add_dependencies(hook, hook_deps);
+
+ std::list<std::string> ordered;
+ try
+ {
+ hook_deps.topological_sort(std::back_inserter(ordered));
}
+ catch (const NoGraphTopologicalOrderExistsError & e)
+ {
+ Log::get_instance()->message(ll_warning, lc_context, "Could not resolve dependency order for hook '"
+ + hook.name() + "' due to exception '" + e.message() + "' (" + e.what() + "'), skipping hooks '" +
+ join(e.remaining_nodes()->begin(), e.remaining_nodes()->end(), "', '") + "' and using hooks '" + join(ordered.begin(),
+ ordered.end(), "', '") + "' in that order");;
+ }
+
+ for (std::list<std::string>::const_iterator o(ordered.begin()), o_end(ordered.end()) ;
+ o != o_end ; ++o)
+ h->second.push_back(hook_files.find(*o)->second);
}
- if (_imp->hook_cache.end() == cache_entry)
- _imp->hook_cache.insert(std::make_pair(hook.name(), had_hook));
+ if (! h->second.empty())
+ {
+ for (std::list<std::tr1::shared_ptr<HookFile> >::const_iterator f(h->second.begin()), f_end(h->second.end()) ;
+ f != f_end ; ++f)
+ max_exit_status = std::max(max_exit_status, (*f)->run(hook));
+ }
return max_exit_status;
}
diff --git a/paludis/hooker_TEST.cc b/paludis/hooker_TEST.cc
new file mode 100644
index 0000000..abda16d
--- /dev/null
+++ b/paludis/hooker_TEST.cc
@@ -0,0 +1,89 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2007 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis 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., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <paludis/hooker.hh>
+#include <paludis/environments/test/test_environment.hh>
+#include <test/test_runner.hh>
+#include <test/test_framework.hh>
+#include <fstream>
+#include <iterator>
+
+using namespace test;
+using namespace paludis;
+
+namespace test_cases
+{
+ struct HookerTest : TestCase
+ {
+ HookerTest() : TestCase("hooker") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ Hooker hooker(&env);
+
+ hooker.add_dir(FSEntry("hooker_TEST_dir/"), false);
+ TEST_CHECK_EQUAL(hooker.perform_hook(Hook("simple_hook")), 3);
+ TEST_CHECK_EQUAL(hooker.perform_hook(Hook("fancy_hook")), 5);
+ TEST_CHECK_EQUAL(hooker.perform_hook(Hook("several_hooks")), 7);
+ }
+ } test_hooker;
+
+ struct HookerOrderingTest : TestCase
+ {
+ HookerOrderingTest() : TestCase("hooker ordering") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ Hooker hooker(&env);
+
+ FSEntry("hooker_TEST_dir/ordering.out").unlink();
+
+ hooker.add_dir(FSEntry("hooker_TEST_dir/"), false);
+ TEST_CHECK_EQUAL(hooker.perform_hook(Hook("ordering")), 0);
+
+ std::ifstream f(stringify(FSEntry("hooker_TEST_dir/ordering.out")).c_str());
+ std::string line((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
+
+ TEST_CHECK_EQUAL(line, "e\nc\nf\nd\nb\na\ng\ni\nh\nk\nj\n");
+ }
+ } test_hooker_ordering;
+
+ struct HookerBadHooksTest : TestCase
+ {
+ HookerBadHooksTest() : TestCase("hooker bad hooks") { }
+
+ void run()
+ {
+
+ }
+ } test_hooker_bad_hooks;
+
+ struct HookerCyclesTest : TestCase
+ {
+ HookerCyclesTest() : TestCase("hooker cycles") { }
+
+ void run()
+ {
+
+ }
+ } test_hooker_cycles;
+}
+
diff --git a/paludis/hooker_TEST_cleanup.sh b/paludis/hooker_TEST_cleanup.sh
new file mode 100755
index 0000000..6cf70d1
--- /dev/null
+++ b/paludis/hooker_TEST_cleanup.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# vim: set ft=sh sw=4 sts=4 et :
+
+if [ -d hooker_TEST_dir ] ; then
+ rm -fr hooker_TEST_dir
+else
+ true
+fi
+
diff --git a/paludis/hooker_TEST_setup.sh b/paludis/hooker_TEST_setup.sh
new file mode 100755
index 0000000..8348283
--- /dev/null
+++ b/paludis/hooker_TEST_setup.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+# vim: set ft=sh sw=4 sts=4 et :
+
+mkdir hooker_TEST_dir || exit 2
+cd hooker_TEST_dir || exit 3
+
+mkdir simple_hook
+cat <<"END" > simple_hook/one.bash
+exit 3
+END
+chmod +x simple_hook/one.bash
+
+mkdir fancy_hook
+cat <<"END" > fancy_hook/one.hook
+hook_run_fancy_hook() {
+ return 5
+}
+
+hook_depend_fancy_hook() {
+ echo
+}
+END
+chmod +x fancy_hook/one.hook
+
+mkdir several_hooks
+cat <<"END" > several_hooks/one.hook
+hook_run_several_hooks() {
+ return 4
+}
+
+hook_depend_several_hooks() {
+ echo
+}
+END
+chmod +x several_hooks/one.hook
+
+cat <<"END" > several_hooks/two.hook
+hook_run_several_hooks() {
+ return 6
+}
+
+hook_depend_several_hooks() {
+ echo
+}
+END
+chmod +x several_hooks/two.hook
+
+cat <<"END" > several_hooks/three.hook
+hook_run_several_hooks() {
+ return 7
+}
+
+hook_depend_several_hooks() {
+ echo
+}
+END
+chmod +x several_hooks/three.hook
+
+mkdir ordering
+cat <<"END" > ordering.common
+hook_run_ordering() {
+ basename ${HOOK_FILE} | sed -e 's,\.hook$,,' >> hooker_TEST_dir/ordering.out
+}
+
+hook_depend_ordering() {
+ case $(basename ${HOOK_FILE} | sed -e 's,\.hook$,,' ) in
+ a)
+ echo b
+ ;;
+ b)
+ echo c d
+ ;;
+ c)
+ echo e
+ ;;
+ d)
+ echo e f
+ ;;
+ h)
+ echo i
+ ;;
+ j)
+ echo k
+ ;;
+ esac
+}
+END
+chmod +x ordering.common
+
+for a in a b c d e f g h i j k ; do
+ ln -s ../ordering.common ordering/${a}.hook
+done
+