aboutsummaryrefslogtreecommitdiffstats
path: root/utils/config
diff options
context:
space:
mode:
authorBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
committerBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
commit08334c51dbb99d9ecd2bb86a2d94ed06da9e167a (patch)
treec43eb24d59bd5c963583a5190caef80fc8387322 /utils/config
downloadsrc-08334c51dbb99d9ecd2bb86a2d94ed06da9e167a.tar.gz
src-08334c51dbb99d9ecd2bb86a2d94ed06da9e167a.zip
Import the kyua testing framework for infrastructure softwarevendor/kyua/0.13-a685f91vendor/kyua
Imported at 0.13 plus assumulated changes to git hash a685f91. Obtained from: https://github.com/jmmv/kyua Sponsored by: DARPA
Notes
Notes: svn path=/vendor/kyua/dist/; revision=359042 svn path=/vendor/kyua/0.13-a685f91/; revision=359043; tag=vendor/kyua/0.13-a685f91
Diffstat (limited to 'utils/config')
-rw-r--r--utils/config/Kyuafile10
-rw-r--r--utils/config/Makefile.am.inc87
-rw-r--r--utils/config/exceptions.cpp149
-rw-r--r--utils/config/exceptions.hpp106
-rw-r--r--utils/config/exceptions_test.cpp133
-rw-r--r--utils/config/keys.cpp70
-rw-r--r--utils/config/keys.hpp52
-rw-r--r--utils/config/keys_fwd.hpp51
-rw-r--r--utils/config/keys_test.cpp114
-rw-r--r--utils/config/lua_module.cpp282
-rw-r--r--utils/config/lua_module.hpp50
-rw-r--r--utils/config/lua_module_test.cpp474
-rw-r--r--utils/config/nodes.cpp589
-rw-r--r--utils/config/nodes.hpp272
-rw-r--r--utils/config/nodes.ipp408
-rw-r--r--utils/config/nodes_fwd.hpp70
-rw-r--r--utils/config/nodes_test.cpp695
-rw-r--r--utils/config/parser.cpp181
-rw-r--r--utils/config/parser.hpp95
-rw-r--r--utils/config/parser_fwd.hpp45
-rw-r--r--utils/config/parser_test.cpp252
-rw-r--r--utils/config/tree.cpp338
-rw-r--r--utils/config/tree.hpp128
-rw-r--r--utils/config/tree.ipp156
-rw-r--r--utils/config/tree_fwd.hpp52
-rw-r--r--utils/config/tree_test.cpp1086
26 files changed, 5945 insertions, 0 deletions
diff --git a/utils/config/Kyuafile b/utils/config/Kyuafile
new file mode 100644
index 000000000000..c607a1757275
--- /dev/null
+++ b/utils/config/Kyuafile
@@ -0,0 +1,10 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="keys_test"}
+atf_test_program{name="lua_module_test"}
+atf_test_program{name="nodes_test"}
+atf_test_program{name="parser_test"}
+atf_test_program{name="tree_test"}
diff --git a/utils/config/Makefile.am.inc b/utils/config/Makefile.am.inc
new file mode 100644
index 000000000000..7c276ec4e798
--- /dev/null
+++ b/utils/config/Makefile.am.inc
@@ -0,0 +1,87 @@
+# Copyright 2012 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+UTILS_CFLAGS += $(LUTOK_CFLAGS)
+UTILS_LIBS += $(LUTOK_LIBS)
+
+libutils_a_CPPFLAGS += $(LUTOK_CFLAGS)
+libutils_a_SOURCES += utils/config/exceptions.cpp
+libutils_a_SOURCES += utils/config/exceptions.hpp
+libutils_a_SOURCES += utils/config/keys.cpp
+libutils_a_SOURCES += utils/config/keys.hpp
+libutils_a_SOURCES += utils/config/keys_fwd.hpp
+libutils_a_SOURCES += utils/config/lua_module.cpp
+libutils_a_SOURCES += utils/config/lua_module.hpp
+libutils_a_SOURCES += utils/config/nodes.cpp
+libutils_a_SOURCES += utils/config/nodes.hpp
+libutils_a_SOURCES += utils/config/nodes.ipp
+libutils_a_SOURCES += utils/config/nodes_fwd.hpp
+libutils_a_SOURCES += utils/config/parser.cpp
+libutils_a_SOURCES += utils/config/parser.hpp
+libutils_a_SOURCES += utils/config/parser_fwd.hpp
+libutils_a_SOURCES += utils/config/tree.cpp
+libutils_a_SOURCES += utils/config/tree.hpp
+libutils_a_SOURCES += utils/config/tree.ipp
+libutils_a_SOURCES += utils/config/tree_fwd.hpp
+
+if WITH_ATF
+tests_utils_configdir = $(pkgtestsdir)/utils/config
+
+tests_utils_config_DATA = utils/config/Kyuafile
+EXTRA_DIST += $(tests_utils_config_DATA)
+
+tests_utils_config_PROGRAMS = utils/config/exceptions_test
+utils_config_exceptions_test_SOURCES = utils/config/exceptions_test.cpp
+utils_config_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/keys_test
+utils_config_keys_test_SOURCES = utils/config/keys_test.cpp
+utils_config_keys_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_keys_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/lua_module_test
+utils_config_lua_module_test_SOURCES = utils/config/lua_module_test.cpp
+utils_config_lua_module_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_lua_module_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/nodes_test
+utils_config_nodes_test_SOURCES = utils/config/nodes_test.cpp
+utils_config_nodes_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_nodes_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/parser_test
+utils_config_parser_test_SOURCES = utils/config/parser_test.cpp
+utils_config_parser_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_parser_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/tree_test
+utils_config_tree_test_SOURCES = utils/config/tree_test.cpp
+utils_config_tree_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_tree_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/config/exceptions.cpp b/utils/config/exceptions.cpp
new file mode 100644
index 000000000000..e9afdf7ea6f7
--- /dev/null
+++ b/utils/config/exceptions.cpp
@@ -0,0 +1,149 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/exceptions.hpp"
+
+#include "utils/config/tree.ipp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The key that caused the combination conflict.
+/// \param format The plain-text error message.
+config::bad_combination_error::bad_combination_error(
+ const detail::tree_key& key, const std::string& format) :
+ error(F(format.empty() ? "Combination conflict in key '%s'" : format) %
+ detail::flatten_key(key))
+{
+}
+
+
+/// Destructor for the error.
+config::bad_combination_error::~bad_combination_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::invalid_key_error::invalid_key_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::invalid_key_error::~invalid_key_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The unknown key.
+/// \param message The plain-text error message.
+config::invalid_key_value::invalid_key_value(const detail::tree_key& key,
+ const std::string& message) :
+ error(F("Invalid value for property '%s': %s")
+ % detail::flatten_key(key) % message)
+{
+}
+
+
+/// Destructor for the error.
+config::invalid_key_value::~invalid_key_value(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::syntax_error::syntax_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::syntax_error::~syntax_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The unknown key.
+/// \param format The message for the error. Must include a single "%s"
+/// placedholder, which will be replaced by the key itself.
+config::unknown_key_error::unknown_key_error(const detail::tree_key& key,
+ const std::string& format) :
+ error(F(format.empty() ? "Unknown configuration property '%s'" : format) %
+ detail::flatten_key(key))
+{
+}
+
+
+/// Destructor for the error.
+config::unknown_key_error::~unknown_key_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::value_error::value_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::value_error::~value_error(void) throw()
+{
+}
diff --git a/utils/config/exceptions.hpp b/utils/config/exceptions.hpp
new file mode 100644
index 000000000000..2096e67f43c8
--- /dev/null
+++ b/utils/config/exceptions.hpp
@@ -0,0 +1,106 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/exceptions.hpp
+/// Exception types raised by the config module.
+
+#if !defined(UTILS_CONFIG_EXCEPTIONS_HPP)
+#define UTILS_CONFIG_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/tree_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// Base exceptions for config errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Exception denoting that two trees cannot be combined.
+class bad_combination_error : public error {
+public:
+ explicit bad_combination_error(const detail::tree_key&,
+ const std::string&);
+ ~bad_combination_error(void) throw();
+};
+
+
+/// Exception denoting that a key was not found within a tree.
+class invalid_key_error : public error {
+public:
+ explicit invalid_key_error(const std::string&);
+ ~invalid_key_error(void) throw();
+};
+
+
+/// Exception denoting that a key was given an invalid value.
+class invalid_key_value : public error {
+public:
+ explicit invalid_key_value(const detail::tree_key&, const std::string&);
+ ~invalid_key_value(void) throw();
+};
+
+
+/// Exception denoting that a configuration file is invalid.
+class syntax_error : public error {
+public:
+ explicit syntax_error(const std::string&);
+ ~syntax_error(void) throw();
+};
+
+
+/// Exception denoting that a key was not found within a tree.
+class unknown_key_error : public error {
+public:
+ explicit unknown_key_error(const detail::tree_key&,
+ const std::string& = "");
+ ~unknown_key_error(void) throw();
+};
+
+
+/// Exception denoting that a value was invalid.
+class value_error : public error {
+public:
+ explicit value_error(const std::string&);
+ ~value_error(void) throw();
+};
+
+
+} // namespace config
+} // namespace utils
+
+
+#endif // !defined(UTILS_CONFIG_EXCEPTIONS_HPP)
diff --git a/utils/config/exceptions_test.cpp b/utils/config/exceptions_test.cpp
new file mode 100644
index 000000000000..a82fb9ea8f0c
--- /dev/null
+++ b/utils/config/exceptions_test.cpp
@@ -0,0 +1,133 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/config/tree.ipp"
+
+namespace config = utils::config;
+namespace detail = utils::config::detail;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const config::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bad_combination_error);
+ATF_TEST_CASE_BODY(bad_combination_error)
+{
+ detail::tree_key key;
+ key.push_back("first");
+ key.push_back("second");
+
+ const config::bad_combination_error e(key, "Failed to combine '%s'");
+ ATF_REQUIRE(std::strcmp("Failed to combine 'first.second'", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key_error);
+ATF_TEST_CASE_BODY(invalid_key_error)
+{
+ const config::invalid_key_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key_value);
+ATF_TEST_CASE_BODY(invalid_key_value)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::invalid_key_value e(key, "foo bar");
+ ATF_REQUIRE(std::strcmp("Invalid value for property '1.two': foo bar",
+ e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_error);
+ATF_TEST_CASE_BODY(syntax_error)
+{
+ const config::syntax_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key_error__default_message);
+ATF_TEST_CASE_BODY(unknown_key_error__default_message)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::unknown_key_error e(key);
+ ATF_REQUIRE(std::strcmp("Unknown configuration property '1.two'",
+ e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key_error__custom_message);
+ATF_TEST_CASE_BODY(unknown_key_error__custom_message)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::unknown_key_error e(key, "The test '%s' string");
+ ATF_REQUIRE(std::strcmp("The test '1.two' string", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ const config::value_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, bad_combination_error);
+ ATF_ADD_TEST_CASE(tcs, invalid_key_error);
+ ATF_ADD_TEST_CASE(tcs, invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, syntax_error);
+ ATF_ADD_TEST_CASE(tcs, unknown_key_error__default_message);
+ ATF_ADD_TEST_CASE(tcs, unknown_key_error__custom_message);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/config/keys.cpp b/utils/config/keys.cpp
new file mode 100644
index 000000000000..574eee14dcd2
--- /dev/null
+++ b/utils/config/keys.cpp
@@ -0,0 +1,70 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.ipp"
+
+#include "utils/config/exceptions.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/text/operations.hpp"
+
+namespace config = utils::config;
+namespace text = utils::text;
+
+
+/// Converts a key to its textual representation.
+///
+/// \param key The key to convert.
+///
+/// \return a flattened representation of \p key, "."-joined.
+std::string
+utils::config::detail::flatten_key(const tree_key& key)
+{
+ PRE(!key.empty());
+ return text::join(key, ".");
+}
+
+
+/// Parses and validates a textual key.
+///
+/// \param str The key to process in dotted notation.
+///
+/// \return The tokenized key if valid.
+///
+/// \throw invalid_key_error If the input key is empty or invalid for any other
+/// reason. Invalid does NOT mean unknown though.
+utils::config::detail::tree_key
+utils::config::detail::parse_key(const std::string& str)
+{
+ const tree_key key = text::split(str, '.');
+ if (key.empty())
+ throw invalid_key_error("Empty key");
+ for (tree_key::const_iterator iter = key.begin(); iter != key.end(); iter++)
+ if ((*iter).empty())
+ throw invalid_key_error(F("Empty component in key '%s'") % str);
+ return key;
+}
diff --git a/utils/config/keys.hpp b/utils/config/keys.hpp
new file mode 100644
index 000000000000..ad258d69fc08
--- /dev/null
+++ b/utils/config/keys.hpp
@@ -0,0 +1,52 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/keys.hpp
+/// Representation and manipulation of tree keys.
+
+#if !defined(UTILS_CONFIG_KEYS_HPP)
+#define UTILS_CONFIG_KEYS_HPP
+
+#include "utils/config/keys_fwd.hpp"
+
+#include <string>
+
+namespace utils {
+namespace config {
+namespace detail {
+
+
+std::string flatten_key(const tree_key&);
+tree_key parse_key(const std::string&);
+
+
+} // namespace detail
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_KEYS_HPP)
diff --git a/utils/config/keys_fwd.hpp b/utils/config/keys_fwd.hpp
new file mode 100644
index 000000000000..101272698b65
--- /dev/null
+++ b/utils/config/keys_fwd.hpp
@@ -0,0 +1,51 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/keys_fwd.hpp
+/// Forward declarations for utils/config/keys.hpp
+
+#if !defined(UTILS_CONFIG_KEYS_FWD_HPP)
+#define UTILS_CONFIG_KEYS_FWD_HPP
+
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace config {
+namespace detail {
+
+
+/// Representation of a valid, tokenized key.
+typedef std::vector< std::string > tree_key;
+
+
+} // namespace detail
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_KEYS_FWD_HPP)
diff --git a/utils/config/keys_test.cpp b/utils/config/keys_test.cpp
new file mode 100644
index 000000000000..dc30f0fc8806
--- /dev/null
+++ b/utils/config/keys_test.cpp
@@ -0,0 +1,114 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/keys.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/config/exceptions.hpp"
+
+namespace config = utils::config;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(flatten_key__one);
+ATF_TEST_CASE_BODY(flatten_key__one)
+{
+ config::detail::tree_key key;
+ key.push_back("foo");
+ ATF_REQUIRE_EQ("foo", config::detail::flatten_key(key));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(flatten_key__many);
+ATF_TEST_CASE_BODY(flatten_key__many)
+{
+ config::detail::tree_key key;
+ key.push_back("foo");
+ key.push_back("1");
+ key.push_back("bar");
+ ATF_REQUIRE_EQ("foo.1.bar", config::detail::flatten_key(key));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__one);
+ATF_TEST_CASE_BODY(parse_key__one)
+{
+ config::detail::tree_key exp_key;
+ exp_key.push_back("one");
+ ATF_REQUIRE(exp_key == config::detail::parse_key("one"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__many);
+ATF_TEST_CASE_BODY(parse_key__many)
+{
+ config::detail::tree_key exp_key;
+ exp_key.push_back("one");
+ exp_key.push_back("2");
+ exp_key.push_back("foo");
+ ATF_REQUIRE(exp_key == config::detail::parse_key("one.2.foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__empty_key);
+ATF_TEST_CASE_BODY(parse_key__empty_key)
+{
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty key",
+ config::detail::parse_key(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__empty_component);
+ATF_TEST_CASE_BODY(parse_key__empty_component)
+{
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key '.'",
+ config::detail::parse_key("."));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key 'a.'",
+ config::detail::parse_key("a."));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key '.b'",
+ config::detail::parse_key(".b"));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key 'a..b'",
+ config::detail::parse_key("a..b"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, flatten_key__one);
+ ATF_ADD_TEST_CASE(tcs, flatten_key__many);
+
+ ATF_ADD_TEST_CASE(tcs, parse_key__one);
+ ATF_ADD_TEST_CASE(tcs, parse_key__many);
+ ATF_ADD_TEST_CASE(tcs, parse_key__empty_key);
+ ATF_ADD_TEST_CASE(tcs, parse_key__empty_component);
+}
diff --git a/utils/config/lua_module.cpp b/utils/config/lua_module.cpp
new file mode 100644
index 000000000000..891f07302e0a
--- /dev/null
+++ b/utils/config/lua_module.cpp
@@ -0,0 +1,282 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/lua_module.hpp"
+
+#include <lutok/stack_cleaner.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/tree.ipp"
+
+namespace config = utils::config;
+namespace detail = utils::config::detail;
+
+
+namespace {
+
+
+/// Gets the tree singleton stored in the Lua state.
+///
+/// \param state The Lua state. The registry must contain a key named
+/// "tree" with a pointer to the singleton.
+///
+/// \return A reference to the tree associated with the Lua state.
+///
+/// \throw syntax_error If the tree cannot be located.
+config::tree&
+get_global_tree(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ state.push_value(lutok::registry_index);
+ state.push_string("tree");
+ state.get_table(-2);
+ if (state.is_nil(-1))
+ throw config::syntax_error("Cannot find tree singleton; global state "
+ "corrupted?");
+ config::tree& tree = **state.to_userdata< config::tree* >(-1);
+ state.pop(1);
+ return tree;
+}
+
+
+/// Gets a fully-qualified tree key from the state.
+///
+/// \param state The Lua state.
+/// \param table_index An index to the Lua stack pointing to the table being
+/// accessed. If this table contains a tree_key metadata property, this is
+/// considered to be the prefix of the tree key.
+/// \param field_index An index to the Lua stack pointing to the entry
+/// containing the name of the field being indexed.
+///
+/// \return A dotted key.
+///
+/// \throw invalid_key_error If the name of the key is invalid.
+static std::string
+get_tree_key(lutok::state& state, const int table_index, const int field_index)
+{
+ PRE(state.is_string(field_index));
+ const std::string field = state.to_string(field_index);
+ if (!field.empty() && field[0] == '_')
+ throw config::invalid_key_error(
+ F("Configuration key cannot have an underscore as a prefix; "
+ "found %s") % field);
+
+ std::string tree_key;
+ if (state.get_metafield(table_index, "tree_key")) {
+ tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1);
+ state.pop(1);
+ } else
+ tree_key = state.to_string(field_index);
+ return tree_key;
+}
+
+
+static int redirect_newindex(lutok::state&);
+static int redirect_index(lutok::state&);
+
+
+/// Creates a table for a new configuration inner node.
+///
+/// \post state(-1) Contains the new table.
+///
+/// \param state The Lua state in which to push the table.
+/// \param tree_key The key to which the new table corresponds.
+static void
+new_table_for_key(lutok::state& state, const std::string& tree_key)
+{
+ state.new_table();
+ {
+ state.new_table();
+ {
+ state.push_string("__index");
+ state.push_cxx_function(redirect_index);
+ state.set_table(-3);
+
+ state.push_string("__newindex");
+ state.push_cxx_function(redirect_newindex);
+ state.set_table(-3);
+
+ state.push_string("tree_key");
+ state.push_string(tree_key);
+ state.set_table(-3);
+ }
+ state.set_metatable(-2);
+ }
+}
+
+
+/// Sets the value of an configuration node.
+///
+/// \pre state(-3) The table to index. If this is not _G, then the table
+/// metadata must contain a tree_key property describing the path to
+/// current level.
+/// \pre state(-2) The field to index into the table. Must be a string.
+/// \pre state(-1) The value to set the indexed table field to.
+///
+/// \param state The Lua state in which to operate.
+///
+/// \return The number of result values on the Lua stack; always 0.
+///
+/// \throw invalid_key_error If the provided key is invalid.
+/// \throw unknown_key_error If the key cannot be located.
+/// \throw value_error If the value has an unsupported type or cannot be
+/// set on the key, or if the input table or index are invalid.
+static int
+redirect_newindex(lutok::state& state)
+{
+ if (!state.is_table(-3))
+ throw config::value_error("Indexed object is not a table");
+ if (!state.is_string(-2))
+ throw config::value_error("Invalid field in configuration object "
+ "reference; must be a string");
+
+ const std::string dotted_key = get_tree_key(state, -3, -2);
+ try {
+ config::tree& tree = get_global_tree(state);
+ tree.set_lua(dotted_key, state, -1);
+ } catch (const config::value_error& e) {
+ throw config::invalid_key_value(detail::parse_key(dotted_key),
+ e.what());
+ }
+
+ // Now really set the key in the Lua table, but prevent direct accesses from
+ // the user by prefixing it. We do this to ensure that re-setting the same
+ // key of the tree results in a call to __newindex instead of __index.
+ state.push_string("_" + state.to_string(-2));
+ state.push_value(-2);
+ state.raw_set(-5);
+
+ return 0;
+}
+
+
+/// Indexes a configuration node.
+///
+/// \pre state(-3) The table to index. If this is not _G, then the table
+/// metadata must contain a tree_key property describing the path to
+/// current level. If the field does not exist, a new table is created.
+/// \pre state(-1) The field to index into the table. Must be a string.
+///
+/// \param state The Lua state in which to operate.
+///
+/// \return The number of result values on the Lua stack; always 1.
+///
+/// \throw value_error If the input table or index are invalid.
+static int
+redirect_index(lutok::state& state)
+{
+ if (!state.is_table(-2))
+ throw config::value_error("Indexed object is not a table");
+ if (!state.is_string(-1))
+ throw config::value_error("Invalid field in configuration object "
+ "reference; must be a string");
+
+ // Query if the key has already been set by a call to redirect_newindex.
+ state.push_string("_" + state.to_string(-1));
+ state.raw_get(-3);
+ if (!state.is_nil(-1))
+ return 1;
+ state.pop(1);
+
+ state.push_value(-1); // Duplicate the field name.
+ state.raw_get(-3); // Get table[field] to see if it's defined.
+ if (state.is_nil(-1)) {
+ state.pop(1);
+
+ // The stack is now the same as when we entered the function, but we
+ // know that the field is undefined and thus have to create a new
+ // configuration table.
+ INV(state.is_table(-2));
+ INV(state.is_string(-1));
+
+ const config::tree& tree = get_global_tree(state);
+ const std::string tree_key = get_tree_key(state, -2, -1);
+ if (tree.is_set(tree_key)) {
+ // Publish the pre-recorded value in the tree to the Lua state,
+ // instead of considering this table key a new inner node.
+ tree.push_lua(tree_key, state);
+ } else {
+ state.push_string("_" + state.to_string(-1));
+ state.insert(-2);
+ state.pop(1);
+
+ new_table_for_key(state, tree_key);
+
+ // Duplicate the newly created table and place it deep in the stack
+ // so that the raw_set below leaves us with the return value of this
+ // function at the top of the stack.
+ state.push_value(-1);
+ state.insert(-4);
+
+ state.raw_set(-3);
+ state.pop(1);
+ }
+ }
+ return 1;
+}
+
+
+} // anonymous namespace
+
+
+/// Install wrappers for globals to set values in the configuration tree.
+///
+/// This function installs wrappers to capture all accesses to global variables.
+/// Such wrappers redirect the reads and writes to the out_tree, which is the
+/// entity that defines what configuration variables exist.
+///
+/// \param state The Lua state into which to install the wrappers.
+/// \param out_tree The tree with the layout definition and where the
+/// configuration settings will be collected.
+void
+config::redirect(lutok::state& state, tree& out_tree)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ state.get_global_table();
+ {
+ state.push_string("__index");
+ state.push_cxx_function(redirect_index);
+ state.set_table(-3);
+
+ state.push_string("__newindex");
+ state.push_cxx_function(redirect_newindex);
+ state.set_table(-3);
+ }
+ state.set_metatable(-1);
+
+ state.push_value(lutok::registry_index);
+ state.push_string("tree");
+ config::tree** tree = state.new_userdata< config::tree* >();
+ *tree = &out_tree;
+ state.set_table(-3);
+ state.pop(1);
+}
diff --git a/utils/config/lua_module.hpp b/utils/config/lua_module.hpp
new file mode 100644
index 000000000000..7f0d5d0b4c5f
--- /dev/null
+++ b/utils/config/lua_module.hpp
@@ -0,0 +1,50 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/lua_module.hpp
+/// Bindings to expose a configuration tree to Lua.
+
+#if !defined(UTILS_CONFIG_LUA_MODULE_HPP)
+#define UTILS_CONFIG_LUA_MODULE_HPP
+
+#include <string>
+
+#include "lutok/state.hpp"
+#include "utils/config/tree_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+void redirect(lutok::state&, tree&);
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_LUA_MODULE_HPP)
diff --git a/utils/config/lua_module_test.cpp b/utils/config/lua_module_test.cpp
new file mode 100644
index 000000000000..484d129c4021
--- /dev/null
+++ b/utils/config/lua_module_test.cpp
@@ -0,0 +1,474 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/lua_module.hpp"
+
+#include <atf-c++.hpp>
+
+#include <lutok/exceptions.hpp>
+#include <lutok/operations.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/tree.ipp"
+#include "utils/defs.hpp"
+
+namespace config = utils::config;
+
+
+namespace {
+
+
+/// Non-native type to use as a leaf node.
+struct custom_type {
+ /// The value recorded in the object.
+ int value;
+
+ /// Constructs a new object.
+ ///
+ /// \param value_ The value to store in the object.
+ explicit custom_type(const int value_) :
+ value(value_)
+ {
+ }
+};
+
+
+/// Custom implementation of a node type for testing purposes.
+class custom_node : public config::typed_leaf_node< custom_type > {
+public:
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node*
+ deep_copy(void) const
+ {
+ std::auto_ptr< custom_node > new_node(new custom_node());
+ new_node->_value = _value;
+ return new_node.release();
+ }
+
+ /// Pushes the node's value onto the Lua stack.
+ ///
+ /// \param state The Lua state onto which to push the value.
+ void
+ push_lua(lutok::state& state) const
+ {
+ state.push_integer(value().value * 5);
+ }
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ ///
+ /// \param state The Lua state from which to get the value.
+ /// \param value_index The stack index in which the value resides.
+ void
+ set_lua(lutok::state& state, const int value_index)
+ {
+ ATF_REQUIRE(state.is_number(value_index));
+ set(custom_type(state.to_integer(value_index) * 2));
+ }
+
+ /// Sets the value of the node from a raw string representation.
+ ///
+ /// \post The test case is marked as failed, as this function is not
+ /// supposed to be invoked by the lua_module code.
+ void
+ set_string(const std::string& /* raw_value */)
+ {
+ ATF_FAIL("Should not be used");
+ }
+
+ /// Converts the contents of the node to a string.
+ ///
+ /// \post The test case is marked as failed, as this function is not
+ /// supposed to be invoked by the lua_module code.
+ ///
+ /// \return Nothing.
+ std::string
+ to_string(void) const
+ {
+ ATF_FAIL("Should not be used");
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__valid_types);
+ATF_TEST_CASE_BODY(top__valid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("top_boolean");
+ tree.define< config::int_node >("top_integer");
+ tree.define< config::string_node >("top_string");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "top_boolean = true\n"
+ "top_integer = 12345\n"
+ "top_string = 'a foo'\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("top_integer"));
+ ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("top_string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__invalid_types);
+ATF_TEST_CASE_BODY(top__invalid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("top_boolean");
+ tree.define< config::int_node >("top_integer");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(
+ lutok::error,
+ "Invalid value for property 'top_boolean': Not a boolean",
+ lutok::do_string(state,
+ "top_boolean = true\n"
+ "top_integer = 8\n"
+ "top_boolean = 'foo'\n",
+ 0, 0, 0));
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean"));
+ ATF_REQUIRE_EQ(8, tree.lookup< config::int_node >("top_integer"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__reuse);
+ATF_TEST_CASE_BODY(top__reuse)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+ tree.define< config::int_node >("second");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = 100; second = first * 2", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("first"));
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("second"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__reset);
+ATF_TEST_CASE_BODY(top__reset)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = 100; first = 200", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__already_set_on_entry);
+ATF_TEST_CASE_BODY(top__already_set_on_entry)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+ tree.set< config::int_node >("first", 100);
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = first * 15", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__valid_types);
+ATF_TEST_CASE_BODY(subtree__valid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("root.boolean");
+ tree.define< config::int_node >("root.a.integer");
+ tree.define< config::string_node >("root.string");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "root.boolean = true\n"
+ "root.a.integer = 12345\n"
+ "root.string = 'a foo'\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("root.boolean"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("root.a.integer"));
+ ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("root.string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__reuse);
+ATF_TEST_CASE_BODY(subtree__reuse)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+ tree.define< config::int_node >("a.second");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = 100; a.second = a.first * 2",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("a.first"));
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.second"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__reset);
+ATF_TEST_CASE_BODY(subtree__reset)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = 100; a.first = 200", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__already_set_on_entry);
+ATF_TEST_CASE_BODY(subtree__already_set_on_entry)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+ tree.set< config::int_node >("a.first", 100);
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = a.first * 15", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("a.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__override_inner);
+ATF_TEST_CASE_BODY(subtree__override_inner)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "root.test = 'a'", 0, 0, 0);
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'root'",
+ lutok::do_string(state, "root = 'b'", 0, 0, 0));
+ // Ensure that the previous assignment to 'root' did not cause any
+ // inconsistencies in the environment that would prevent a new
+ // assignment from working.
+ lutok::do_string(state, "root.test2 = 'c'", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ("a", tree.lookup< config::string_node >("root.test"));
+ ATF_REQUIRE_EQ("c", tree.lookup< config::string_node >("root.test2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__strings);
+ATF_TEST_CASE_BODY(dynamic_subtree__strings)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "root.key1 = 1234\n"
+ "root.a.b.key2 = 'foo bar'\n",
+ 0, 0, 0);
+
+ ATF_REQUIRE_EQ("1234", tree.lookup< config::string_node >("root.key1"));
+ ATF_REQUIRE_EQ("foo bar",
+ tree.lookup< config::string_node >("root.a.b.key2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__invalid_types);
+ATF_TEST_CASE_BODY(dynamic_subtree__invalid_types)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'root.boolean': "
+ "Not a string",
+ lutok::do_string(state, "root.boolean = true",
+ 0, 0, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'root.table': "
+ "Not a string",
+ lutok::do_string(state, "root.table = {}",
+ 0, 0, 0));
+ ATF_REQUIRE(!tree.is_set("root.boolean"));
+ ATF_REQUIRE(!tree.is_set("root.table"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(locals);
+ATF_TEST_CASE_BODY(locals)
+{
+ config::tree tree;
+ tree.define< config::int_node >("the_key");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "local function generate()\n"
+ " return 15\n"
+ "end\n"
+ "local test_var = 20\n"
+ "the_key = generate() + test_var\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(35, tree.lookup< config::int_node >("the_key"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(custom_node);
+ATF_TEST_CASE_BODY(custom_node)
+{
+ config::tree tree;
+ tree.define< custom_node >("key1");
+ tree.define< custom_node >("key2");
+ tree.set< custom_node >("key2", custom_type(10));
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "key1 = 512\n", 0, 0, 0);
+ lutok::do_string(state, "key2 = key2 * 2\n", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1024, tree.lookup< custom_node >("key1").value);
+ ATF_REQUIRE_EQ(200, tree.lookup< custom_node >("key2").value);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key);
+ATF_TEST_CASE_BODY(invalid_key)
+{
+ config::tree tree;
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error, "Empty component in key 'root.'",
+ lutok::do_string(state, "root['']['a'] = 12345\n",
+ 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key);
+ATF_TEST_CASE_BODY(unknown_key)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("static.bool");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Unknown configuration property 'static.int'",
+ lutok::do_string(state,
+ "static.int = 12345\n",
+ 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("a.b");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'a.b': Not a boolean",
+ lutok::do_string(state, "a.b = 12345\n", 0, 0, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'a': ",
+ lutok::do_string(state, "a = 1\n", 0, 0, 0));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, top__valid_types);
+ ATF_ADD_TEST_CASE(tcs, top__invalid_types);
+ ATF_ADD_TEST_CASE(tcs, top__reuse);
+ ATF_ADD_TEST_CASE(tcs, top__reset);
+ ATF_ADD_TEST_CASE(tcs, top__already_set_on_entry);
+
+ ATF_ADD_TEST_CASE(tcs, subtree__valid_types);
+ ATF_ADD_TEST_CASE(tcs, subtree__reuse);
+ ATF_ADD_TEST_CASE(tcs, subtree__reset);
+ ATF_ADD_TEST_CASE(tcs, subtree__already_set_on_entry);
+ ATF_ADD_TEST_CASE(tcs, subtree__override_inner);
+
+ ATF_ADD_TEST_CASE(tcs, dynamic_subtree__strings);
+ ATF_ADD_TEST_CASE(tcs, dynamic_subtree__invalid_types);
+
+ ATF_ADD_TEST_CASE(tcs, locals);
+ ATF_ADD_TEST_CASE(tcs, custom_node);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_key);
+ ATF_ADD_TEST_CASE(tcs, unknown_key);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/config/nodes.cpp b/utils/config/nodes.cpp
new file mode 100644
index 000000000000..1c6e848daf07
--- /dev/null
+++ b/utils/config/nodes.cpp
@@ -0,0 +1,589 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/nodes.ipp"
+
+#include <memory>
+
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Destructor.
+config::detail::base_node::~base_node(void)
+{
+}
+
+
+/// Constructor.
+///
+/// \param dynamic_ Whether the node is dynamic or not.
+config::detail::inner_node::inner_node(const bool dynamic_) :
+ _dynamic(dynamic_)
+{
+}
+
+
+/// Destructor.
+config::detail::inner_node::~inner_node(void)
+{
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter)
+ delete (*iter).second;
+}
+
+
+/// Fills the given node with a copy of this node's data.
+///
+/// \param node The node to fill. Should be the fresh return value of a
+/// deep_copy() operation.
+void
+config::detail::inner_node::copy_into(inner_node* node) const
+{
+ node->_dynamic = _dynamic;
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter) {
+ base_node* new_node = (*iter).second->deep_copy();
+ try {
+ node->_children[(*iter).first] = new_node;
+ } catch (...) {
+ delete new_node;
+ throw;
+ }
+ }
+}
+
+
+/// Combines two children sets, preferring the keys in the first set only.
+///
+/// This operation is not symmetrical on c1 and c2. The caller is responsible
+/// for invoking this twice so that the two key sets are combined if they happen
+/// to differ.
+///
+/// \param key Key to this node.
+/// \param c1 First children set.
+/// \param c2 First children set.
+/// \param [in,out] node The node to combine into.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+void
+config::detail::inner_node::combine_children_into(
+ const tree_key& key,
+ const children_map& c1, const children_map& c2,
+ inner_node* node) const
+{
+ for (children_map::const_iterator iter1 = c1.begin();
+ iter1 != c1.end(); ++iter1) {
+ const std::string& name = (*iter1).first;
+
+ if (node->_children.find(name) != node->_children.end()) {
+ continue;
+ }
+
+ std::auto_ptr< base_node > new_node;
+
+ children_map::const_iterator iter2 = c2.find(name);
+ if (iter2 == c2.end()) {
+ new_node.reset((*iter1).second->deep_copy());
+ } else {
+ tree_key child_key = key;
+ child_key.push_back(name);
+ new_node.reset((*iter1).second->combine(child_key,
+ (*iter2).second));
+ }
+
+ node->_children[name] = new_node.release();
+ }
+}
+
+
+/// Combines this inner node with another inner node onto a new node.
+///
+/// The "dynamic" property is inherited by the new node if either of the two
+/// nodes are dynamic.
+///
+/// \param key Key to this node.
+/// \param other_base The node to combine with.
+/// \param [in,out] node The node to combine into.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+void
+config::detail::inner_node::combine_into(const tree_key& key,
+ const base_node* other_base,
+ inner_node* node) const
+{
+ try {
+ const inner_node& other = dynamic_cast< const inner_node& >(
+ *other_base);
+
+ node->_dynamic = _dynamic || other._dynamic;
+
+ combine_children_into(key, _children, other._children, node);
+ combine_children_into(key, other._children, _children, node);
+ } catch (const std::bad_cast& unused_e) {
+ throw config::bad_combination_error(
+ key, "'%s' is an inner node in the base tree but a leaf node in "
+ "the overrides treee");
+ }
+}
+
+
+/// Finds a node without creating it if not found.
+///
+/// This recursive algorithm traverses the tree searching for a particular key.
+/// The returned node is constant, so this can only be used for querying
+/// purposes. For this reason, this algorithm does not create intermediate
+/// nodes if they don't exist (as would be necessary to set a new node).
+///
+/// \param key The key to be queried.
+/// \param key_pos The current level within the key to be examined.
+///
+/// \return A reference to the located node, if successful.
+///
+/// \throw unknown_key_error If the provided key is unknown.
+const config::detail::base_node*
+config::detail::inner_node::lookup_ro(const tree_key& key,
+ const tree_key::size_type key_pos) const
+{
+ PRE(key_pos < key.size());
+
+ const children_map::const_iterator child_iter = _children.find(
+ key[key_pos]);
+ if (child_iter == _children.end())
+ throw unknown_key_error(key);
+
+ if (key_pos == key.size() - 1) {
+ return (*child_iter).second;
+ } else {
+ PRE(key_pos < key.size() - 1);
+ try {
+ const inner_node& child = dynamic_cast< const inner_node& >(
+ *(*child_iter).second);
+ return child.lookup_ro(key, key_pos + 1);
+ } catch (const std::bad_cast& e) {
+ throw unknown_key_error(
+ key, "Cannot address incomplete configuration property '%s'");
+ }
+ }
+}
+
+
+/// Finds a node and creates it if not found.
+///
+/// This recursive algorithm traverses the tree searching for a particular key,
+/// creating any intermediate nodes if they do not already exist (for the case
+/// of dynamic inner nodes). The returned node is non-constant, so this can be
+/// used by the algorithms that set key values.
+///
+/// \param key The key to be queried.
+/// \param key_pos The current level within the key to be examined.
+/// \param new_node A function that returns a new leaf node of the desired
+/// type. This is only called if the leaf cannot be found, but it has
+/// already been defined.
+///
+/// \return A reference to the located node, if successful.
+///
+/// \throw invalid_key_value If the resulting node of the search would be an
+/// inner node.
+/// \throw unknown_key_error If the provided key is unknown.
+config::leaf_node*
+config::detail::inner_node::lookup_rw(const tree_key& key,
+ const tree_key::size_type key_pos,
+ new_node_hook new_node)
+{
+ PRE(key_pos < key.size());
+
+ children_map::const_iterator child_iter = _children.find(key[key_pos]);
+ if (child_iter == _children.end()) {
+ if (_dynamic) {
+ base_node* const child = (key_pos == key.size() - 1) ?
+ static_cast< base_node* >(new_node()) :
+ static_cast< base_node* >(new dynamic_inner_node());
+ _children.insert(children_map::value_type(key[key_pos], child));
+ child_iter = _children.find(key[key_pos]);
+ } else {
+ throw unknown_key_error(key);
+ }
+ }
+
+ if (key_pos == key.size() - 1) {
+ try {
+ leaf_node& child = dynamic_cast< leaf_node& >(
+ *(*child_iter).second);
+ return &child;
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+ } else {
+ PRE(key_pos < key.size() - 1);
+ try {
+ inner_node& child = dynamic_cast< inner_node& >(
+ *(*child_iter).second);
+ return child.lookup_rw(key, key_pos + 1, new_node);
+ } catch (const std::bad_cast& e) {
+ throw unknown_key_error(
+ key, "Cannot address incomplete configuration property '%s'");
+ }
+ }
+}
+
+
+/// Converts the subtree to a collection of key/value string pairs.
+///
+/// \param [out] properties The accumulator for the generated properties. The
+/// contents of the map are only extended.
+/// \param key The path to the current node.
+void
+config::detail::inner_node::all_properties(properties_map& properties,
+ const tree_key& key) const
+{
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter) {
+ tree_key child_key = key;
+ child_key.push_back((*iter).first);
+ try {
+ leaf_node& child = dynamic_cast< leaf_node& >(*(*iter).second);
+ if (child.is_set())
+ properties[flatten_key(child_key)] = child.to_string();
+ } catch (const std::bad_cast& unused_error) {
+ inner_node& child = dynamic_cast< inner_node& >(*(*iter).second);
+ child.all_properties(properties, child_key);
+ }
+ }
+}
+
+
+/// Constructor.
+config::detail::static_inner_node::static_inner_node(void) :
+ inner_node(false)
+{
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::detail::static_inner_node::deep_copy(void) const
+{
+ std::auto_ptr< inner_node > new_node(new static_inner_node());
+ copy_into(new_node.get());
+ return new_node.release();
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::detail::static_inner_node::combine(const tree_key& key,
+ const base_node* other) const
+{
+ std::auto_ptr< inner_node > new_node(new static_inner_node());
+ combine_into(key, other, new_node.get());
+ return new_node.release();
+}
+
+
+/// Registers a key as valid and having a specific type.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \param key The key to be registered.
+/// \param key_pos The current level within the key to be examined.
+/// \param new_node A function that returns a new leaf node of the desired
+/// type.
+void
+config::detail::static_inner_node::define(const tree_key& key,
+ const tree_key::size_type key_pos,
+ new_node_hook new_node)
+{
+ PRE(key_pos < key.size());
+
+ if (key_pos == key.size() - 1) {
+ PRE_MSG(_children.find(key[key_pos]) == _children.end(),
+ "Key already defined");
+ _children.insert(children_map::value_type(key[key_pos], new_node()));
+ } else {
+ PRE(key_pos < key.size() - 1);
+ const children_map::const_iterator child_iter = _children.find(
+ key[key_pos]);
+
+ if (child_iter == _children.end()) {
+ static_inner_node* const child_ptr = new static_inner_node();
+ _children.insert(children_map::value_type(key[key_pos], child_ptr));
+ child_ptr->define(key, key_pos + 1, new_node);
+ } else {
+ try {
+ static_inner_node& child = dynamic_cast< static_inner_node& >(
+ *(*child_iter).second);
+ child.define(key, key_pos + 1, new_node);
+ } catch (const std::bad_cast& e) {
+ UNREACHABLE;
+ }
+ }
+ }
+}
+
+
+/// Constructor.
+config::detail::dynamic_inner_node::dynamic_inner_node(void) :
+ inner_node(true)
+{
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::detail::dynamic_inner_node::deep_copy(void) const
+{
+ std::auto_ptr< inner_node > new_node(new dynamic_inner_node());
+ copy_into(new_node.get());
+ return new_node.release();
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::detail::dynamic_inner_node::combine(const tree_key& key,
+ const base_node* other) const
+{
+ std::auto_ptr< inner_node > new_node(new dynamic_inner_node());
+ combine_into(key, other, new_node.get());
+ return new_node.release();
+}
+
+
+/// Destructor.
+config::leaf_node::~leaf_node(void)
+{
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other_base The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::leaf_node::combine(const detail::tree_key& key,
+ const base_node* other_base) const
+{
+ try {
+ const leaf_node& other = dynamic_cast< const leaf_node& >(*other_base);
+
+ if (other.is_set()) {
+ return other.deep_copy();
+ } else {
+ return deep_copy();
+ }
+ } catch (const std::bad_cast& unused_e) {
+ throw config::bad_combination_error(
+ key, "'%s' is a leaf node in the base tree but an inner node in "
+ "the overrides treee");
+ }
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::bool_node::deep_copy(void) const
+{
+ std::auto_ptr< bool_node > new_node(new bool_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::bool_node::push_lua(lutok::state& state) const
+{
+ state.push_boolean(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::bool_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_boolean(value_index))
+ set(state.to_boolean(value_index));
+ else
+ throw value_error("Not a boolean");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::int_node::deep_copy(void) const
+{
+ std::auto_ptr< int_node > new_node(new int_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::int_node::push_lua(lutok::state& state) const
+{
+ state.push_integer(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::int_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_number(value_index))
+ set(state.to_integer(value_index));
+ else
+ throw value_error("Not an integer");
+}
+
+
+/// Checks a given value for validity.
+///
+/// \param new_value The value to validate.
+///
+/// \throw value_error If the value is not valid.
+void
+config::positive_int_node::validate(const value_type& new_value) const
+{
+ if (new_value <= 0)
+ throw value_error("Must be a positive integer");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::string_node::deep_copy(void) const
+{
+ std::auto_ptr< string_node > new_node(new string_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::string_node::push_lua(lutok::state& state) const
+{
+ state.push_string(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::string_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_string(value_index))
+ set(state.to_string(value_index));
+ else
+ throw value_error("Not a string");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::strings_set_node::deep_copy(void) const
+{
+ std::auto_ptr< strings_set_node > new_node(new strings_set_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Converts a single word to the native type.
+///
+/// \param raw_value The value to parse.
+///
+/// \return The parsed value.
+std::string
+config::strings_set_node::parse_one(const std::string& raw_value) const
+{
+ return raw_value;
+}
diff --git a/utils/config/nodes.hpp b/utils/config/nodes.hpp
new file mode 100644
index 000000000000..6b766ff5d8f7
--- /dev/null
+++ b/utils/config/nodes.hpp
@@ -0,0 +1,272 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/nodes.hpp
+/// Representation of tree nodes.
+
+#if !defined(UTILS_CONFIG_NODES_HPP)
+#define UTILS_CONFIG_NODES_HPP
+
+#include "utils/config/nodes_fwd.hpp"
+
+#include <set>
+#include <string>
+
+#include <lutok/state.hpp>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/nodes_fwd.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.hpp"
+
+namespace utils {
+namespace config {
+
+
+namespace detail {
+
+
+/// Base representation of a node.
+///
+/// This abstract class provides the base type for every node in the tree. Due
+/// to the dynamic nature of our trees (each leaf being able to hold arbitrary
+/// data types), this base type is a necessity.
+class base_node : noncopyable {
+public:
+ virtual ~base_node(void) = 0;
+
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node* deep_copy(void) const = 0;
+
+ /// Combines this node with another one.
+ ///
+ /// \param key Key to this node.
+ /// \param other The node to combine with.
+ ///
+ /// \return A new node representing the combination.
+ ///
+ /// \throw bad_combination_error If the two nodes cannot be combined.
+ virtual base_node* combine(const tree_key& key, const base_node* other)
+ const = 0;
+};
+
+
+} // namespace detail
+
+
+/// Abstract leaf node without any specified type.
+///
+/// This base abstract type is necessary to have a common pointer type to which
+/// to cast any leaf. We later provide templated derivates of this class, and
+/// those cannot act in this manner.
+///
+/// It is important to understand that a leaf can exist without actually holding
+/// a value. Our trees are "strictly keyed": keys must have been pre-defined
+/// before a value can be set on them. This is to ensure that the end user is
+/// using valid key names and not making mistakes due to typos, for example. To
+/// represent this condition, we define an "empty" key in the tree to denote
+/// that the key is valid, yet it has not been set by the user. Only when an
+/// explicit set is performed on the key, it gets a value.
+class leaf_node : public detail::base_node {
+public:
+ virtual ~leaf_node(void);
+
+ virtual bool is_set(void) const = 0;
+
+ base_node* combine(const detail::tree_key&, const base_node*) const;
+
+ virtual void push_lua(lutok::state&) const = 0;
+ virtual void set_lua(lutok::state&, const int) = 0;
+
+ virtual void set_string(const std::string&) = 0;
+ virtual std::string to_string(void) const = 0;
+};
+
+
+/// Base leaf node for a single arbitrary type.
+///
+/// This templated leaf node holds a single object of any type. The conversion
+/// to/from string representations is undefined, as that depends on the
+/// particular type being processed. You should reimplement this class for any
+/// type that needs additional processing/validation during conversion.
+template< typename ValueType >
+class typed_leaf_node : public leaf_node {
+public:
+ /// The type of the value held by this node.
+ typedef ValueType value_type;
+
+ /// Constructs a new leaf node that contains no value.
+ typed_leaf_node(void);
+
+ /// Checks whether the node has been set by the user.
+ bool is_set(void) const;
+
+ /// Gets the value stored in the node.
+ const value_type& value(void) const;
+
+ /// Gets the read-write value stored in the node.
+ value_type& value(void);
+
+ /// Sets the value of the node.
+ void set(const value_type&);
+
+protected:
+ /// The value held by this node.
+ optional< value_type > _value;
+
+private:
+ virtual void validate(const value_type&) const;
+};
+
+
+/// Leaf node holding a native type.
+///
+/// This templated leaf node holds a native type. The conversion to/from string
+/// representations of the value happens by means of iostreams.
+template< typename ValueType >
+class native_leaf_node : public typed_leaf_node< ValueType > {
+public:
+ void set_string(const std::string&);
+ std::string to_string(void) const;
+};
+
+
+/// A leaf node that holds a boolean value.
+class bool_node : public native_leaf_node< bool > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// A leaf node that holds an integer value.
+class int_node : public native_leaf_node< int > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// A leaf node that holds a positive non-zero integer value.
+class positive_int_node : public int_node {
+ virtual void validate(const value_type&) const;
+};
+
+
+/// A leaf node that holds a string value.
+class string_node : public native_leaf_node< std::string > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// Base leaf node for a set of native types.
+///
+/// This is a base abstract class because there is no generic way to parse a
+/// single word in the textual representation of the set to the native value.
+template< typename ValueType >
+class base_set_node : public leaf_node {
+public:
+ /// The type of the value held by this node.
+ typedef std::set< ValueType > value_type;
+
+ base_set_node(void);
+
+ /// Checks whether the node has been set by the user.
+ ///
+ /// \return True if a value has been set in the node.
+ bool is_set(void) const;
+
+ /// Gets the value stored in the node.
+ ///
+ /// \pre The node must have a value.
+ ///
+ /// \return The value in the node.
+ const value_type& value(void) const;
+
+ /// Gets the read-write value stored in the node.
+ ///
+ /// \pre The node must have a value.
+ ///
+ /// \return The value in the node.
+ value_type& value(void);
+
+ /// Sets the value of the node.
+ void set(const value_type&);
+
+ /// Sets the value of the node from a raw string representation.
+ void set_string(const std::string&);
+
+ /// Converts the contents of the node to a string.
+ std::string to_string(void) const;
+
+ /// Pushes the node's value onto the Lua stack.
+ void push_lua(lutok::state&) const;
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ void set_lua(lutok::state&, const int);
+
+protected:
+ /// The value held by this node.
+ optional< value_type > _value;
+
+private:
+ /// Converts a single word to the native type.
+ ///
+ /// \return The parsed value.
+ ///
+ /// \throw value_error If the value is invalid.
+ virtual ValueType parse_one(const std::string&) const = 0;
+
+ virtual void validate(const value_type&) const;
+};
+
+
+/// A leaf node that holds a set of strings.
+class strings_set_node : public base_set_node< std::string > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+private:
+ std::string parse_one(const std::string&) const;
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_HPP)
diff --git a/utils/config/nodes.ipp b/utils/config/nodes.ipp
new file mode 100644
index 000000000000..9e0a1228cccd
--- /dev/null
+++ b/utils/config/nodes.ipp
@@ -0,0 +1,408 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/nodes.hpp"
+
+#if !defined(UTILS_CONFIG_NODES_IPP)
+#define UTILS_CONFIG_NODES_IPP
+
+#include <memory>
+#include <typeinfo>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+#include "utils/sanity.hpp"
+
+namespace utils {
+
+
+namespace config {
+namespace detail {
+
+
+/// Type of the new_node() family of functions.
+typedef base_node* (*new_node_hook)(void);
+
+
+/// Creates a new leaf node of a given type.
+///
+/// \tparam NodeType The type of the leaf node to create.
+///
+/// \return A pointer to the newly-created node.
+template< class NodeType >
+base_node*
+new_node(void)
+{
+ return new NodeType();
+}
+
+
+/// Internal node of the tree.
+///
+/// This abstract base class provides the mechanism to implement both static and
+/// dynamic nodes. Ideally, the implementation would be split in subclasses and
+/// this class would not include the knowledge of whether the node is dynamic or
+/// not. However, because the static/dynamic difference depends on the leaf
+/// types, we need to declare template functions and these cannot be virtual.
+class inner_node : public base_node {
+ /// Whether the node is dynamic or not.
+ bool _dynamic;
+
+protected:
+ /// Type to represent the collection of children of this node.
+ ///
+ /// Note that these are one-level keys. They cannot contain dots, and thus
+ /// is why we use a string rather than a tree_key.
+ typedef std::map< std::string, base_node* > children_map;
+
+ /// Mapping of keys to values that are descendants of this node.
+ children_map _children;
+
+ void copy_into(inner_node*) const;
+ void combine_into(const tree_key&, const base_node*, inner_node*) const;
+
+private:
+ void combine_children_into(const tree_key&,
+ const children_map&, const children_map&,
+ inner_node*) const;
+
+public:
+ inner_node(const bool);
+ virtual ~inner_node(void) = 0;
+
+ const base_node* lookup_ro(const tree_key&,
+ const tree_key::size_type) const;
+ leaf_node* lookup_rw(const tree_key&, const tree_key::size_type,
+ new_node_hook);
+
+ void all_properties(properties_map&, const tree_key&) const;
+};
+
+
+/// Static internal node of the tree.
+///
+/// The direct children of this node must be pre-defined by calls to define().
+/// Attempts to traverse this node and resolve a key that is not a pre-defined
+/// children will result in an "unknown key" error.
+class static_inner_node : public config::detail::inner_node {
+public:
+ static_inner_node(void);
+
+ virtual base_node* deep_copy(void) const;
+ virtual base_node* combine(const tree_key&, const base_node*) const;
+
+ void define(const tree_key&, const tree_key::size_type, new_node_hook);
+};
+
+
+/// Dynamic internal node of the tree.
+///
+/// The children of this node need not be pre-defined. Attempts to traverse
+/// this node and resolve a key will result in such key being created. Any
+/// intermediate non-existent nodes of the traversal will be created as dynamic
+/// inner nodes as well.
+class dynamic_inner_node : public config::detail::inner_node {
+public:
+ virtual base_node* deep_copy(void) const;
+ virtual base_node* combine(const tree_key&, const base_node*) const;
+
+ dynamic_inner_node(void);
+};
+
+
+} // namespace detail
+} // namespace config
+
+
+/// Constructor for a node with an undefined value.
+///
+/// This should only be called by the tree's define() method as a way to
+/// register a node as known but undefined. The node will then serve as a
+/// placeholder for future values.
+template< typename ValueType >
+config::typed_leaf_node< ValueType >::typed_leaf_node(void) :
+ _value(none)
+{
+}
+
+
+/// Checks whether the node has been set by the user.
+///
+/// Nodes of the tree are predefined by the caller to specify the valid
+/// types of the leaves. Such predefinition results in the creation of
+/// nodes within the tree, but these nodes have not yet been set.
+/// Traversing these nodes is invalid and should result in an "unknown key"
+/// error.
+///
+/// \return True if a value has been set in the node.
+template< typename ValueType >
+bool
+config::typed_leaf_node< ValueType >::is_set(void) const
+{
+ return static_cast< bool >(_value);
+}
+
+
+/// Gets the value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+const typename config::typed_leaf_node< ValueType >::value_type&
+config::typed_leaf_node< ValueType >::value(void) const
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Gets the read-write value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+typename config::typed_leaf_node< ValueType >::value_type&
+config::typed_leaf_node< ValueType >::value(void)
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Sets the value of the node.
+///
+/// \param value_ The new value to set the node to.
+///
+/// \throw value_error If the value is invalid, according to validate().
+template< typename ValueType >
+void
+config::typed_leaf_node< ValueType >::set(const value_type& value_)
+{
+ validate(value_);
+ _value = optional< value_type >(value_);
+}
+
+
+/// Checks a given value for validity.
+///
+/// This is called internally by the node right before updating the recorded
+/// value. This method can be redefined by subclasses.
+///
+/// \throw value_error If the value is not valid.
+template< typename ValueType >
+void
+config::typed_leaf_node< ValueType >::validate(
+ const value_type& /* new_value */) const
+{
+}
+
+
+/// Sets the value of the node from a raw string representation.
+///
+/// \param raw_value The value to set the node to.
+///
+/// \throw value_error If the value is invalid.
+template< typename ValueType >
+void
+config::native_leaf_node< ValueType >::set_string(const std::string& raw_value)
+{
+ try {
+ typed_leaf_node< ValueType >::set(text::to_type< ValueType >(
+ raw_value));
+ } catch (const text::value_error& e) {
+ throw config::value_error(F("Failed to convert string value '%s' to "
+ "the node's type") % raw_value);
+ }
+}
+
+
+/// Converts the contents of the node to a string.
+///
+/// \pre The node must have a value.
+///
+/// \return A string representation of the value held by the node.
+template< typename ValueType >
+std::string
+config::native_leaf_node< ValueType >::to_string(void) const
+{
+ PRE(typed_leaf_node< ValueType >::is_set());
+ return F("%s") % typed_leaf_node< ValueType >::value();
+}
+
+
+/// Constructor for a node with an undefined value.
+///
+/// This should only be called by the tree's define() method as a way to
+/// register a node as known but undefined. The node will then serve as a
+/// placeholder for future values.
+template< typename ValueType >
+config::base_set_node< ValueType >::base_set_node(void) :
+ _value(none)
+{
+}
+
+
+/// Checks whether the node has been set.
+///
+/// Remember that a node can exist before holding a value (i.e. when the node
+/// has been defined as "known" but not yet set by the user). This function
+/// checks whether the node laready holds a value.
+///
+/// \return True if a value has been set in the node.
+template< typename ValueType >
+bool
+config::base_set_node< ValueType >::is_set(void) const
+{
+ return static_cast< bool >(_value);
+}
+
+
+/// Gets the value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+const typename config::base_set_node< ValueType >::value_type&
+config::base_set_node< ValueType >::value(void) const
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Gets the read-write value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+typename config::base_set_node< ValueType >::value_type&
+config::base_set_node< ValueType >::value(void)
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Sets the value of the node.
+///
+/// \param value_ The new value to set the node to.
+///
+/// \throw value_error If the value is invalid, according to validate().
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set(const value_type& value_)
+{
+ validate(value_);
+ _value = optional< value_type >(value_);
+}
+
+
+/// Sets the value of the node from a raw string representation.
+///
+/// \param raw_value The value to set the node to.
+///
+/// \throw value_error If the value is invalid.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set_string(const std::string& raw_value)
+{
+ std::set< ValueType > new_value;
+
+ const std::vector< std::string > words = text::split(raw_value, ' ');
+ for (std::vector< std::string >::const_iterator iter = words.begin();
+ iter != words.end(); ++iter) {
+ if (!(*iter).empty())
+ new_value.insert(parse_one(*iter));
+ }
+
+ set(new_value);
+}
+
+
+/// Converts the contents of the node to a string.
+///
+/// \pre The node must have a value.
+///
+/// \return A string representation of the value held by the node.
+template< typename ValueType >
+std::string
+config::base_set_node< ValueType >::to_string(void) const
+{
+ PRE(is_set());
+ return text::join(_value.get(), " ");
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::push_lua(lutok::state& /* state */) const
+{
+ UNREACHABLE;
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set_lua(
+ lutok::state& /* state */,
+ const int /* value_index */)
+{
+ UNREACHABLE;
+}
+
+
+/// Checks a given value for validity.
+///
+/// This is called internally by the node right before updating the recorded
+/// value. This method can be redefined by subclasses.
+///
+/// \throw value_error If the value is not valid.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::validate(
+ const value_type& /* new_value */) const
+{
+}
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_IPP)
diff --git a/utils/config/nodes_fwd.hpp b/utils/config/nodes_fwd.hpp
new file mode 100644
index 000000000000..b03328e79e95
--- /dev/null
+++ b/utils/config/nodes_fwd.hpp
@@ -0,0 +1,70 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/nodes_fwd.hpp
+/// Forward declarations for utils/config/nodes.hpp
+
+#if !defined(UTILS_CONFIG_NODES_FWD_HPP)
+#define UTILS_CONFIG_NODES_FWD_HPP
+
+#include <map>
+#include <string>
+
+namespace utils {
+namespace config {
+
+
+/// Flat representation of all properties as strings.
+typedef std::map< std::string, std::string > properties_map;
+
+
+namespace detail {
+
+
+class base_node;
+class static_inner_node;
+
+
+} // namespace detail
+
+
+class leaf_node;
+template< typename > class typed_leaf_node;
+template< typename > class native_leaf_node;
+class bool_node;
+class int_node;
+class positive_int_node;
+class string_node;
+template< typename > class base_set_node;
+class strings_set_node;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_FWD_HPP)
diff --git a/utils/config/nodes_test.cpp b/utils/config/nodes_test.cpp
new file mode 100644
index 000000000000..e762d3aac38c
--- /dev/null
+++ b/utils/config/nodes_test.cpp
@@ -0,0 +1,695 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/nodes.ipp"
+
+#include <atf-c++.hpp>
+
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/defs.hpp"
+
+namespace config = utils::config;
+
+
+namespace {
+
+
+/// Typed leaf node that specializes the validate() method.
+class validation_node : public config::int_node {
+ /// Checks a given value for validity against a fake value.
+ ///
+ /// \param new_value The value to validate.
+ ///
+ /// \throw value_error If the value is not valid.
+ void
+ validate(const value_type& new_value) const
+ {
+ if (new_value == 12345)
+ throw config::value_error("Custom validate method");
+ }
+};
+
+
+/// Set node that specializes the validate() method.
+class set_validation_node : public config::strings_set_node {
+ /// Checks a given value for validity against a fake value.
+ ///
+ /// \param new_value The value to validate.
+ ///
+ /// \throw value_error If the value is not valid.
+ void
+ validate(const value_type& new_value) const
+ {
+ for (value_type::const_iterator iter = new_value.begin();
+ iter != new_value.end(); ++iter)
+ if (*iter == "throw")
+ throw config::value_error("Custom validate method");
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__deep_copy);
+ATF_TEST_CASE_BODY(bool_node__deep_copy)
+{
+ config::bool_node node;
+ node.set(true);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::bool_node* copy = static_cast< config::bool_node* >(raw_copy);
+ ATF_REQUIRE(copy->value());
+ copy->set(false);
+ ATF_REQUIRE(node.value());
+ ATF_REQUIRE(!copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__is_set_and_set);
+ATF_TEST_CASE_BODY(bool_node__is_set_and_set)
+{
+ config::bool_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(false);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__value_and_set);
+ATF_TEST_CASE_BODY(bool_node__value_and_set)
+{
+ config::bool_node node;
+ node.set(false);
+ ATF_REQUIRE(!node.value());
+ node.set(true);
+ ATF_REQUIRE( node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__push_lua);
+ATF_TEST_CASE_BODY(bool_node__push_lua)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ node.set(true);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_boolean(-1));
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_lua__ok);
+ATF_TEST_CASE_BODY(bool_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.push_boolean(false);
+ node.set_lua(state, -1);
+ state.pop(1);
+ ATF_REQUIRE(!node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(bool_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.push_string("foo bar");
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_string__ok);
+ATF_TEST_CASE_BODY(bool_node__set_string__ok)
+{
+ config::bool_node node;
+ node.set_string("false");
+ ATF_REQUIRE(!node.value());
+ node.set_string("true");
+ ATF_REQUIRE( node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(bool_node__set_string__invalid_value)
+{
+ config::bool_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("12345"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__to_string);
+ATF_TEST_CASE_BODY(bool_node__to_string)
+{
+ config::bool_node node;
+ node.set(false);
+ ATF_REQUIRE_EQ("false", node.to_string());
+ node.set(true);
+ ATF_REQUIRE_EQ("true", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__deep_copy);
+ATF_TEST_CASE_BODY(int_node__deep_copy)
+{
+ config::int_node node;
+ node.set(5);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::int_node* copy = static_cast< config::int_node* >(raw_copy);
+ ATF_REQUIRE_EQ(5, copy->value());
+ copy->set(10);
+ ATF_REQUIRE_EQ(5, node.value());
+ ATF_REQUIRE_EQ(10, copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__is_set_and_set);
+ATF_TEST_CASE_BODY(int_node__is_set_and_set)
+{
+ config::int_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(20);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__value_and_set);
+ATF_TEST_CASE_BODY(int_node__value_and_set)
+{
+ config::int_node node;
+ node.set(20);
+ ATF_REQUIRE_EQ(20, node.value());
+ node.set(0);
+ ATF_REQUIRE_EQ(0, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__push_lua);
+ATF_TEST_CASE_BODY(int_node__push_lua)
+{
+ lutok::state state;
+
+ config::int_node node;
+ node.set(754);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_number(-1));
+ ATF_REQUIRE_EQ(754, state.to_integer(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_lua__ok);
+ATF_TEST_CASE_BODY(int_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::int_node node;
+ state.push_integer(123);
+ state.push_string("456");
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ(123, node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ(456, node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(int_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::int_node node;
+ state.push_boolean(true);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_string__ok);
+ATF_TEST_CASE_BODY(int_node__set_string__ok)
+{
+ config::int_node node;
+ node.set_string("178");
+ ATF_REQUIRE_EQ(178, node.value());
+ node.set_string("-123");
+ ATF_REQUIRE_EQ(-123, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(int_node__set_string__invalid_value)
+{
+ config::int_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string(" 23"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__to_string);
+ATF_TEST_CASE_BODY(int_node__to_string)
+{
+ config::int_node node;
+ node.set(89);
+ ATF_REQUIRE_EQ("89", node.to_string());
+ node.set(-57);
+ ATF_REQUIRE_EQ("-57", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__deep_copy);
+ATF_TEST_CASE_BODY(positive_int_node__deep_copy)
+{
+ config::positive_int_node node;
+ node.set(5);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::positive_int_node* copy = static_cast< config::positive_int_node* >(
+ raw_copy);
+ ATF_REQUIRE_EQ(5, copy->value());
+ copy->set(10);
+ ATF_REQUIRE_EQ(5, node.value());
+ ATF_REQUIRE_EQ(10, copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__is_set_and_set);
+ATF_TEST_CASE_BODY(positive_int_node__is_set_and_set)
+{
+ config::positive_int_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(20);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__value_and_set);
+ATF_TEST_CASE_BODY(positive_int_node__value_and_set)
+{
+ config::positive_int_node node;
+ node.set(20);
+ ATF_REQUIRE_EQ(20, node.value());
+ node.set(1);
+ ATF_REQUIRE_EQ(1, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__push_lua);
+ATF_TEST_CASE_BODY(positive_int_node__push_lua)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ node.set(754);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_number(-1));
+ ATF_REQUIRE_EQ(754, state.to_integer(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_lua__ok);
+ATF_TEST_CASE_BODY(positive_int_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ state.push_integer(123);
+ state.push_string("456");
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ(123, node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ(456, node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(positive_int_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ state.push_boolean(true);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+ state.push_integer(0);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_string__ok);
+ATF_TEST_CASE_BODY(positive_int_node__set_string__ok)
+{
+ config::positive_int_node node;
+ node.set_string("1");
+ ATF_REQUIRE_EQ(1, node.value());
+ node.set_string("178");
+ ATF_REQUIRE_EQ(178, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(positive_int_node__set_string__invalid_value)
+{
+ config::positive_int_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string(" 23"));
+ ATF_REQUIRE(!node.is_set());
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("0"));
+ ATF_REQUIRE(!node.is_set());
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("-5"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__to_string);
+ATF_TEST_CASE_BODY(positive_int_node__to_string)
+{
+ config::positive_int_node node;
+ node.set(89);
+ ATF_REQUIRE_EQ("89", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__deep_copy);
+ATF_TEST_CASE_BODY(string_node__deep_copy)
+{
+ config::string_node node;
+ node.set("first");
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::string_node* copy = static_cast< config::string_node* >(raw_copy);
+ ATF_REQUIRE_EQ("first", copy->value());
+ copy->set("second");
+ ATF_REQUIRE_EQ("first", node.value());
+ ATF_REQUIRE_EQ("second", copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__is_set_and_set);
+ATF_TEST_CASE_BODY(string_node__is_set_and_set)
+{
+ config::string_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set("foo");
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__value_and_set);
+ATF_TEST_CASE_BODY(string_node__value_and_set)
+{
+ config::string_node node;
+ node.set("foo");
+ ATF_REQUIRE_EQ("foo", node.value());
+ node.set("");
+ ATF_REQUIRE_EQ("", node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__push_lua);
+ATF_TEST_CASE_BODY(string_node__push_lua)
+{
+ lutok::state state;
+
+ config::string_node node;
+ node.set("some message");
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_string(-1));
+ ATF_REQUIRE_EQ("some message", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_lua__ok);
+ATF_TEST_CASE_BODY(string_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::string_node node;
+ state.push_string("text 1");
+ state.push_integer(231);
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ("text 1", node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ("231", node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(string_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.new_table();
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_string);
+ATF_TEST_CASE_BODY(string_node__set_string)
+{
+ config::string_node node;
+ node.set_string("abcd efgh");
+ ATF_REQUIRE_EQ("abcd efgh", node.value());
+ node.set_string(" 1234 ");
+ ATF_REQUIRE_EQ(" 1234 ", node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__to_string);
+ATF_TEST_CASE_BODY(string_node__to_string)
+{
+ config::string_node node;
+ node.set("");
+ ATF_REQUIRE_EQ("", node.to_string());
+ node.set("aaa");
+ ATF_REQUIRE_EQ("aaa", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__deep_copy);
+ATF_TEST_CASE_BODY(strings_set_node__deep_copy)
+{
+ std::set< std::string > value;
+ config::strings_set_node node;
+ value.insert("foo");
+ node.set(value);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::strings_set_node* copy =
+ static_cast< config::strings_set_node* >(raw_copy);
+ value.insert("bar");
+ ATF_REQUIRE_EQ(1, copy->value().size());
+ copy->set(value);
+ ATF_REQUIRE_EQ(1, node.value().size());
+ ATF_REQUIRE_EQ(2, copy->value().size());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__is_set_and_set);
+ATF_TEST_CASE_BODY(strings_set_node__is_set_and_set)
+{
+ std::set< std::string > value;
+ value.insert("foo");
+
+ config::strings_set_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(value);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__value_and_set);
+ATF_TEST_CASE_BODY(strings_set_node__value_and_set)
+{
+ std::set< std::string > value;
+ value.insert("first");
+
+ config::strings_set_node node;
+ node.set(value);
+ ATF_REQUIRE(value == node.value());
+ value.clear();
+ node.set(value);
+ value.insert("second");
+ ATF_REQUIRE(node.value().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__set_string);
+ATF_TEST_CASE_BODY(strings_set_node__set_string)
+{
+ config::strings_set_node node;
+ {
+ std::set< std::string > expected;
+ expected.insert("abcd");
+ expected.insert("efgh");
+
+ node.set_string("abcd efgh");
+ ATF_REQUIRE(expected == node.value());
+ }
+ {
+ std::set< std::string > expected;
+ expected.insert("1234");
+
+ node.set_string(" 1234 ");
+ ATF_REQUIRE(expected == node.value());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__to_string);
+ATF_TEST_CASE_BODY(strings_set_node__to_string)
+{
+ std::set< std::string > value;
+ config::strings_set_node node;
+ value.insert("second");
+ value.insert("first");
+ node.set(value);
+ ATF_REQUIRE_EQ("first second", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(typed_leaf_node__validate_set);
+ATF_TEST_CASE_BODY(typed_leaf_node__validate_set)
+{
+ validation_node node;
+ node.set(1234);
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set(12345));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(typed_leaf_node__validate_set_string);
+ATF_TEST_CASE_BODY(typed_leaf_node__validate_set_string)
+{
+ validation_node node;
+ node.set_string("1234");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set_string("12345"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_set_node__validate_set);
+ATF_TEST_CASE_BODY(base_set_node__validate_set)
+{
+ set_validation_node node;
+ set_validation_node::value_type values;
+ values.insert("foo");
+ values.insert("bar");
+ node.set(values);
+ values.insert("throw");
+ values.insert("baz");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set(values));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_set_node__validate_set_string);
+ATF_TEST_CASE_BODY(base_set_node__validate_set_string)
+{
+ set_validation_node node;
+ node.set_string("foo bar");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set_string("foo bar throw baz"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, bool_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, bool_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, bool_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, bool_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, bool_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, int_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, int_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, int_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, int_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, int_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, string_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, string_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, string_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, string_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_string);
+ ATF_ADD_TEST_CASE(tcs, string_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__set_string);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, typed_leaf_node__validate_set);
+ ATF_ADD_TEST_CASE(tcs, typed_leaf_node__validate_set_string);
+ ATF_ADD_TEST_CASE(tcs, base_set_node__validate_set);
+ ATF_ADD_TEST_CASE(tcs, base_set_node__validate_set_string);
+}
diff --git a/utils/config/parser.cpp b/utils/config/parser.cpp
new file mode 100644
index 000000000000..7bfe5517fdd0
--- /dev/null
+++ b/utils/config/parser.cpp
@@ -0,0 +1,181 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/parser.hpp"
+
+#include <lutok/exceptions.hpp>
+#include <lutok/operations.hpp>
+#include <lutok/stack_cleaner.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/lua_module.hpp"
+#include "utils/config/tree.ipp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace config = utils::config;
+
+
+// History of configuration file versions:
+//
+// 2 - Changed the syntax() call to take only a version number, instead of the
+// word 'config' as the first argument and the version as the second one.
+// Files now start with syntax(2) instead of syntax('config', 1).
+//
+// 1 - Initial version.
+
+
+/// Internal implementation of the parser.
+struct utils::config::parser::impl : utils::noncopyable {
+ /// Pointer to the parent parser. Needed for callbacks.
+ parser* _parent;
+
+ /// The Lua state used by this parser to process the configuration file.
+ lutok::state _state;
+
+ /// The tree to be filed in by the configuration parameters, as provided by
+ /// the caller.
+ config::tree& _tree;
+
+ /// Whether syntax() has been called or not.
+ bool _syntax_called;
+
+ /// Constructs a new implementation.
+ ///
+ /// \param parent_ Pointer to the class being constructed.
+ /// \param config_tree_ The configuration tree provided by the user.
+ impl(parser* const parent_, tree& config_tree_) :
+ _parent(parent_), _tree(config_tree_), _syntax_called(false)
+ {
+ }
+
+ friend void lua_syntax(lutok::state&);
+
+ /// Callback executed by the Lua syntax() function.
+ ///
+ /// \param syntax_version The syntax format version as provided by the
+ /// configuration file in the call to syntax().
+ void
+ syntax_callback(const int syntax_version)
+ {
+ if (_syntax_called)
+ throw syntax_error("syntax() can only be called once");
+ _syntax_called = true;
+
+ // Allow the parser caller to populate the tree with its own schema
+ // depending on the format/version combination.
+ _parent->setup(_tree, syntax_version);
+
+ // Export the config module to the Lua state so that all global variable
+ // accesses are redirected to the configuration tree.
+ config::redirect(_state, _tree);
+ }
+};
+
+
+namespace {
+
+
+static int
+lua_syntax(lutok::state& state)
+{
+ if (!state.is_number(-1))
+ throw config::value_error("Last argument to syntax must be a number");
+ const int syntax_version = state.to_integer(-1);
+
+ if (syntax_version == 1) {
+ if (state.get_top() != 2)
+ throw config::value_error("Version 1 files need two arguments to "
+ "syntax()");
+ if (!state.is_string(-2) || state.to_string(-2) != "config")
+ throw config::value_error("First argument to syntax must be "
+ "'config' for version 1 files");
+ } else {
+ if (state.get_top() != 1)
+ throw config::value_error("syntax() only takes one argument");
+ }
+
+ state.get_global("_config_parser");
+ config::parser::impl* impl =
+ *state.to_userdata< config::parser::impl* >(-1);
+ state.pop(1);
+
+ impl->syntax_callback(syntax_version);
+
+ return 0;
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new parser.
+///
+/// \param [in,out] config_tree The configuration tree into which the values set
+/// in the configuration file will be stored.
+config::parser::parser(tree& config_tree) :
+ _pimpl(new impl(this, config_tree))
+{
+ lutok::stack_cleaner cleaner(_pimpl->_state);
+
+ _pimpl->_state.push_cxx_function(lua_syntax);
+ _pimpl->_state.set_global("syntax");
+ *_pimpl->_state.new_userdata< config::parser::impl* >() = _pimpl.get();
+ _pimpl->_state.set_global("_config_parser");
+}
+
+
+/// Destructor.
+config::parser::~parser(void)
+{
+}
+
+
+/// Parses a configuration file.
+///
+/// \post The tree registered during the construction of this class is updated
+/// to contain the values read from the configuration file. If the processing
+/// fails, the state of the output tree is undefined.
+///
+/// \param file The path to the file to process.
+///
+/// \throw syntax_error If there is any problem processing the file.
+void
+config::parser::parse(const fs::path& file)
+{
+ try {
+ lutok::do_file(_pimpl->_state, file.str(), 0, 0, 0);
+ } catch (const lutok::error& e) {
+ throw syntax_error(e.what());
+ }
+
+ if (!_pimpl->_syntax_called)
+ throw syntax_error("No syntax defined (no call to syntax() found)");
+}
diff --git a/utils/config/parser.hpp b/utils/config/parser.hpp
new file mode 100644
index 000000000000..cb69e756cbe8
--- /dev/null
+++ b/utils/config/parser.hpp
@@ -0,0 +1,95 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/parser.hpp
+/// Utilities to read a configuration file into memory.
+
+#if !defined(UTILS_CONFIG_PARSER_HPP)
+#define UTILS_CONFIG_PARSER_HPP
+
+#include "utils/config/parser_fwd.hpp"
+
+#include <memory>
+
+#include "utils/config/tree_fwd.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// A configuration parser.
+///
+/// This parser is a class rather than a function because we need to support
+/// callbacks to perform the initialization of the config file schema. The
+/// configuration files always start with a call to syntax(), which define the
+/// particular version of the schema being used. Depending on such version, the
+/// layout of the internal tree representation needs to be different.
+///
+/// A parser implementation must provide a setup() method to set up the
+/// configuration schema based on the particular combination of syntax format
+/// and version specified on the file.
+///
+/// Parser objects are not supposed to be reused, and specific trees are not
+/// supposed to be passed to multiple parsers (even if sequentially). Doing so
+/// will cause all kinds of inconsistencies in the managed tree itself or in the
+/// Lua state.
+class parser : noncopyable {
+public:
+ struct impl;
+
+private:
+ /// Pointer to the internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+ /// Hook to initialize the tree keys before reading the file.
+ ///
+ /// This hook gets called when the configuration file defines its specific
+ /// format by calling the syntax() function. We have to delay the tree
+ /// initialization until this point because, before we know what version of
+ /// a configuration file we are parsing, we cannot know what keys are valid.
+ ///
+ /// \param [in,out] config_tree The tree in which to define the key
+ /// structure.
+ /// \param syntax_version The version of the file format as specified in the
+ /// configuration file.
+ virtual void setup(tree& config_tree, const int syntax_version) = 0;
+
+public:
+ explicit parser(tree&);
+ virtual ~parser(void);
+
+ void parse(const fs::path&);
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_PARSER_HPP)
diff --git a/utils/config/parser_fwd.hpp b/utils/config/parser_fwd.hpp
new file mode 100644
index 000000000000..6278b6c95c12
--- /dev/null
+++ b/utils/config/parser_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/parser_fwd.hpp
+/// Forward declarations for utils/config/parser.hpp
+
+#if !defined(UTILS_CONFIG_PARSER_FWD_HPP)
+#define UTILS_CONFIG_PARSER_FWD_HPP
+
+namespace utils {
+namespace config {
+
+
+class parser;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_PARSER_FWD_HPP)
diff --git a/utils/config/parser_test.cpp b/utils/config/parser_test.cpp
new file mode 100644
index 000000000000..f5445f55c490
--- /dev/null
+++ b/utils/config/parser_test.cpp
@@ -0,0 +1,252 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/parser.hpp"
+
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/parser.hpp"
+#include "utils/config/tree.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+
+namespace config = utils::config;
+namespace fs = utils::fs;
+
+
+namespace {
+
+
+/// Implementation of a parser for testing purposes.
+class mock_parser : public config::parser {
+ /// Initializes the tree keys before reading the file.
+ ///
+ /// \param [in,out] tree The tree in which to define the key structure.
+ /// \param syntax_version The version of the file format as specified in the
+ /// configuration file.
+ void
+ setup(config::tree& tree, const int syntax_version)
+ {
+ if (syntax_version == 1) {
+ // Do nothing on config_tree.
+ } else if (syntax_version == 2) {
+ tree.define< config::string_node >("top_string");
+ tree.define< config::int_node >("inner.int");
+ tree.define_dynamic("inner.dynamic");
+ } else {
+ throw std::runtime_error(F("Unknown syntax version %s") %
+ syntax_version);
+ }
+ }
+
+public:
+ /// Initializes a parser.
+ ///
+ /// \param tree The mock config tree to parse.
+ mock_parser(config::tree& tree) :
+ config::parser(tree)
+ {
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_keys__ok);
+ATF_TEST_CASE_BODY(no_keys__ok)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "local foo = 'value'\n");
+
+ config::tree tree;
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::string_node >("foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_keys__unknown_key);
+ATF_TEST_CASE_BODY(no_keys__unknown_key)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "foo = 'value'\n");
+
+ config::tree tree;
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "foo",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__ok);
+ATF_TEST_CASE_BODY(some_keys__ok)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string = 'foo'\n"
+ "inner.int = 12345\n"
+ "inner.dynamic.foo = 78\n"
+ "inner.dynamic.bar = 'some text'\n");
+
+ config::tree tree;
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_EQ("foo", tree.lookup< config::string_node >("top_string"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("inner.int"));
+ ATF_REQUIRE_EQ("78",
+ tree.lookup< config::string_node >("inner.dynamic.foo"));
+ ATF_REQUIRE_EQ("some text",
+ tree.lookup< config::string_node >("inner.dynamic.bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__not_strict);
+ATF_TEST_CASE_BODY(some_keys__not_strict)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string = 'foo'\n"
+ "unknown_string = 'bar'\n"
+ "top_string = 'baz'\n");
+
+ config::tree tree(false);
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_EQ("baz", tree.lookup< config::string_node >("top_string"));
+ ATF_REQUIRE(!tree.is_set("unknown_string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__unknown_key);
+ATF_TEST_CASE_BODY(some_keys__unknown_key)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string2 = 'foo'\n");
+ config::tree tree1;
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown configuration property 'top_string2'",
+ mock_parser(tree1).parse(fs::path("output.lua")));
+
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "inner.int2 = 12345\n");
+ config::tree tree2;
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown configuration property 'inner.int2'",
+ mock_parser(tree2).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_syntax);
+ATF_TEST_CASE_BODY(invalid_syntax)
+{
+ config::tree tree;
+
+ atf::utils::create_file("output.lua", "syntax(56)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown syntax version 56",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_deprecated_format);
+ATF_TEST_CASE_BODY(syntax_deprecated_format)
+{
+ config::tree tree;
+
+ atf::utils::create_file("output.lua", "syntax('config', 1)\n");
+ (void)mock_parser(tree).parse(fs::path("output.lua"));
+
+ atf::utils::create_file("output.lua", "syntax('foo', 1)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "must be 'config'",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ atf::utils::create_file("output.lua", "syntax('config', 2)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "only takes one argument",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_not_called);
+ATF_TEST_CASE_BODY(syntax_not_called)
+{
+ config::tree tree;
+ tree.define< config::int_node >("var");
+
+ atf::utils::create_file("output.lua", "var = 3\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "No syntax defined",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ ATF_REQUIRE(!tree.is_set("var"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_called_more_than_once);
+ATF_TEST_CASE_BODY(syntax_called_more_than_once)
+{
+ config::tree tree;
+ tree.define< config::int_node >("var");
+
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "var = 3\n"
+ "syntax(2)\n"
+ "var = 5\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "syntax\\(\\) can only be called once",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ ATF_REQUIRE_EQ(3, tree.lookup< config::int_node >("var"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, no_keys__ok);
+ ATF_ADD_TEST_CASE(tcs, no_keys__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, some_keys__ok);
+ ATF_ADD_TEST_CASE(tcs, some_keys__not_strict);
+ ATF_ADD_TEST_CASE(tcs, some_keys__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_syntax);
+ ATF_ADD_TEST_CASE(tcs, syntax_deprecated_format);
+ ATF_ADD_TEST_CASE(tcs, syntax_not_called);
+ ATF_ADD_TEST_CASE(tcs, syntax_called_more_than_once);
+}
diff --git a/utils/config/tree.cpp b/utils/config/tree.cpp
new file mode 100644
index 000000000000..1aa2d85b89cd
--- /dev/null
+++ b/utils/config/tree.cpp
@@ -0,0 +1,338 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.ipp"
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Constructor.
+///
+/// \param strict Whether keys must be validated at "set" time.
+config::tree::tree(const bool strict) :
+ _strict(strict), _root(new detail::static_inner_node())
+{
+}
+
+
+/// Constructor with a non-empty root.
+///
+/// \param strict Whether keys must be validated at "set" time.
+/// \param root The root to the tree to be owned by this instance.
+config::tree::tree(const bool strict, detail::static_inner_node* root) :
+ _strict(strict), _root(root)
+{
+}
+
+
+/// Destructor.
+config::tree::~tree(void)
+{
+}
+
+
+/// Generates a deep copy of the input tree.
+///
+/// \return A new tree that is an exact copy of this tree.
+config::tree
+config::tree::deep_copy(void) const
+{
+ detail::static_inner_node* new_root =
+ dynamic_cast< detail::static_inner_node* >(_root->deep_copy());
+ return config::tree(_strict, new_root);
+}
+
+
+/// Combines two trees.
+///
+/// By combination we understand a new tree that contains the full key space of
+/// the two input trees and, for the keys that match, respects the value of the
+/// right-hand side (aka "other") tree.
+///
+/// Any nodes marked as dynamic "win" over non-dynamic nodes and the resulting
+/// tree will have the dynamic property set on those.
+///
+/// \param overrides The tree to use as value overrides.
+///
+/// \return The combined tree.
+///
+/// \throw bad_combination_error If the two trees cannot be combined; for
+/// example, if a single key represents an inner node in one tree but a leaf
+/// node in the other one.
+config::tree
+config::tree::combine(const tree& overrides) const
+{
+ const detail::static_inner_node* other_root =
+ dynamic_cast< const detail::static_inner_node * >(
+ overrides._root.get());
+
+ detail::static_inner_node* new_root =
+ dynamic_cast< detail::static_inner_node* >(
+ _root->combine(detail::tree_key(), other_root));
+ return config::tree(_strict, new_root);
+}
+
+
+/// Registers a node as being dynamic.
+///
+/// This operation creates the given key as an inner node. Further set
+/// operations that trespass this node will automatically create any missing
+/// keys.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \param dotted_key The key to be registered in dotted representation.
+void
+config::tree::define_dynamic(const std::string& dotted_key)
+{
+ try {
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ _root->define(key, 0, detail::new_node< detail::dynamic_inner_node >);
+ } catch (const error& e) {
+ UNREACHABLE_MSG("define() failing due to key errors is a programming "
+ "mistake: " + std::string(e.what()));
+ }
+}
+
+
+/// Checks if a given node is set.
+///
+/// \param dotted_key The key to be checked.
+///
+/// \return True if the key is set to a specific value (not just defined).
+/// False if the key is not set or if the key does not exist.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+bool
+config::tree::is_set(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(
+ *raw_node);
+ return child.is_set();
+ } catch (const std::bad_cast& unused_error) {
+ return false;
+ }
+ } catch (const unknown_key_error& unused_error) {
+ return false;
+ }
+}
+
+
+/// Pushes a leaf node's value onto the Lua stack.
+///
+/// \param dotted_key The key to be pushed.
+/// \param state The Lua state into which to push the key's value.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::push_lua(const std::string& dotted_key, lutok::state& state) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(*raw_node);
+ child.push_lua(state);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets a leaf node's value from a value in the Lua stack.
+///
+/// \param dotted_key The key to be set.
+/// \param state The Lua state from which to retrieve the value.
+/// \param value_index The position in the Lua stack holding the value.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::set_lua(const std::string& dotted_key, lutok::state& state,
+ const int value_index)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< string_node >);
+ leaf_node& child = dynamic_cast< leaf_node& >(*raw_node);
+ child.set_lua(state, value_index);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+/// Gets the value of a node as a plain string.
+///
+/// \param dotted_key The key to be looked up.
+///
+/// \return The value of the located node as a string.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+std::string
+config::tree::lookup_string(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(*raw_node);
+ return child.to_string();
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets the value of a leaf addressed by its key from a string value.
+///
+/// This respects the native types of all the nodes that have been predefined.
+/// For new nodes under a dynamic subtree, this has no mechanism of determining
+/// what type they need to have, so they are created as plain string nodes.
+///
+/// \param dotted_key The key to be registered in dotted representation.
+/// \param raw_value The string representation of the value to set the node to.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::set_string(const std::string& dotted_key,
+ const std::string& raw_value)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< string_node >);
+ leaf_node& child = dynamic_cast< leaf_node& >(*raw_node);
+ child.set_string(raw_value);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+/// Converts the tree to a collection of key/value string pairs.
+///
+/// \param dotted_key Subtree from which to start the export.
+/// \param strip_key If true, remove the dotted_key prefix from the resulting
+/// properties.
+///
+/// \return A map of keys to values in their textual representation.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+/// \throw value_error If the provided key points to a leaf.
+config::properties_map
+config::tree::all_properties(const std::string& dotted_key,
+ const bool strip_key) const
+{
+ PRE(!strip_key || !dotted_key.empty());
+
+ properties_map properties;
+
+ detail::tree_key key;
+ const detail::base_node* raw_node;
+ if (dotted_key.empty()) {
+ raw_node = _root.get();
+ } else {
+ key = detail::parse_key(dotted_key);
+ raw_node = _root->lookup_ro(key, 0);
+ }
+ try {
+ const detail::inner_node& child =
+ dynamic_cast< const detail::inner_node& >(*raw_node);
+ child.all_properties(properties, key);
+ } catch (const std::bad_cast& unused_error) {
+ INV(!dotted_key.empty());
+ throw value_error(F("Cannot export properties from a leaf node; "
+ "'%s' given") % dotted_key);
+ }
+
+ if (strip_key) {
+ properties_map stripped;
+ for (properties_map::const_iterator iter = properties.begin();
+ iter != properties.end(); ++iter) {
+ stripped[(*iter).first.substr(dotted_key.length() + 1)] =
+ (*iter).second;
+ }
+ properties = stripped;
+ }
+
+ return properties;
+}
+
+
+/// Equality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are equal; false otherwise.
+bool
+config::tree::operator==(const tree& other) const
+{
+ // TODO(jmmv): Would be nicer to perform the comparison directly on the
+ // nodes, instead of exporting the values to strings first.
+ return _root == other._root || all_properties() == other.all_properties();
+}
+
+
+/// Inequality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are different; false otherwise.
+bool
+config::tree::operator!=(const tree& other) const
+{
+ return !(*this == other);
+}
diff --git a/utils/config/tree.hpp b/utils/config/tree.hpp
new file mode 100644
index 000000000000..cad0a9b4fc0b
--- /dev/null
+++ b/utils/config/tree.hpp
@@ -0,0 +1,128 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/tree.hpp
+/// Data type to represent a tree of arbitrary values with string keys.
+
+#if !defined(UTILS_CONFIG_TREE_HPP)
+#define UTILS_CONFIG_TREE_HPP
+
+#include "utils/config/tree_fwd.hpp"
+
+#include <memory>
+#include <string>
+
+#include <lutok/state.hpp>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/nodes_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// Representation of a tree.
+///
+/// The string keys of the tree are in dotted notation and actually represent
+/// path traversals through the nodes.
+///
+/// Our trees are "strictly-keyed": keys must be defined as "existent" before
+/// their values can be set. Defining a key is a separate action from setting
+/// its value. The rationale is that we want to be able to control what keys
+/// get defined: because trees are used to hold configuration, we want to catch
+/// typos as early as possible. Also, users cannot set keys unless the types
+/// are known in advance because our leaf nodes are strictly typed.
+///
+/// However, there is an exception to the strict keys: the inner nodes of the
+/// tree can be static or dynamic. Static inner nodes have a known subset of
+/// children and attempting to set keys not previously defined will result in an
+/// error. Dynamic inner nodes do not have a predefined set of keys and can be
+/// used to accept arbitrary user input.
+///
+/// For simplicity reasons, we force the root of the tree to be a static inner
+/// node. In other words, the root can never contain a value by itself and this
+/// is not a problem because the root is not addressable by the key space.
+/// Additionally, the root is strict so all of its direct children must be
+/// explicitly defined.
+///
+/// This is, effectively, a simple wrapper around the node representing the
+/// root. Having a separate class aids in clearly representing the concept of a
+/// tree and all of its public methods. Also, the tree accepts dotted notations
+/// for the keys while the internal structures do not.
+///
+/// Note that trees are shallow-copied unless a deep copy is requested with
+/// deep_copy().
+class tree {
+ /// Whether keys must be validated at "set" time.
+ bool _strict;
+
+ /// The root of the tree.
+ std::shared_ptr< detail::static_inner_node > _root;
+
+ tree(const bool, detail::static_inner_node*);
+
+public:
+ tree(const bool = true);
+ ~tree(void);
+
+ tree deep_copy(void) const;
+ tree combine(const tree&) const;
+
+ template< class LeafType >
+ void define(const std::string&);
+
+ void define_dynamic(const std::string&);
+
+ bool is_set(const std::string&) const;
+
+ template< class LeafType >
+ const typename LeafType::value_type& lookup(const std::string&) const;
+ template< class LeafType >
+ typename LeafType::value_type& lookup_rw(const std::string&);
+
+ template< class LeafType >
+ void set(const std::string&, const typename LeafType::value_type&);
+
+ void push_lua(const std::string&, lutok::state&) const;
+ void set_lua(const std::string&, lutok::state&, const int);
+
+ std::string lookup_string(const std::string&) const;
+ void set_string(const std::string&, const std::string&);
+
+ properties_map all_properties(const std::string& = "",
+ const bool = false) const;
+
+ bool operator==(const tree&) const;
+ bool operator!=(const tree&) const;
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_HPP)
diff --git a/utils/config/tree.ipp b/utils/config/tree.ipp
new file mode 100644
index 000000000000..a79acc3be184
--- /dev/null
+++ b/utils/config/tree.ipp
@@ -0,0 +1,156 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.hpp"
+
+#if !defined(UTILS_CONFIG_TREE_IPP)
+#define UTILS_CONFIG_TREE_IPP
+
+#include <typeinfo>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace utils {
+
+
+/// Registers a key as valid and having a specific type.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \tparam LeafType The node type of the leaf we are defining.
+/// \param dotted_key The key to be registered in dotted representation.
+template< class LeafType >
+void
+config::tree::define(const std::string& dotted_key)
+{
+ try {
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ _root->define(key, 0, detail::new_node< LeafType >);
+ } catch (const error& e) {
+ UNREACHABLE_MSG(F("define() failing due to key errors is a programming "
+ "mistake: %s") % e.what());
+ }
+}
+
+
+/// Gets a read-only reference to the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are querying.
+/// \param dotted_key The key to be registered in dotted representation.
+///
+/// \return A reference to the value in the located leaf, if successful.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+const typename LeafType::value_type&
+config::tree::lookup(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const LeafType& child = dynamic_cast< const LeafType& >(*raw_node);
+ if (child.is_set())
+ return child.value();
+ else
+ throw unknown_key_error(key);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Gets a read-write reference to the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are querying.
+/// \param dotted_key The key to be registered in dotted representation.
+///
+/// \return A reference to the value in the located leaf, if successful.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+typename LeafType::value_type&
+config::tree::lookup_rw(const std::string& dotted_key)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< LeafType >);
+ try {
+ LeafType& child = dynamic_cast< LeafType& >(*raw_node);
+ if (child.is_set())
+ return child.value();
+ else
+ throw unknown_key_error(key);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are setting.
+/// \param dotted_key The key to be registered in dotted representation.
+/// \param value The value to set into the node.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+void
+config::tree::set(const std::string& dotted_key,
+ const typename LeafType::value_type& value)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ leaf_node* raw_node = _root->lookup_rw(key, 0,
+ detail::new_node< LeafType >);
+ LeafType& child = dynamic_cast< LeafType& >(*raw_node);
+ child.set(value);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_IPP)
diff --git a/utils/config/tree_fwd.hpp b/utils/config/tree_fwd.hpp
new file mode 100644
index 000000000000..e494d8c0f4ee
--- /dev/null
+++ b/utils/config/tree_fwd.hpp
@@ -0,0 +1,52 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/config/tree_fwd.hpp
+/// Forward declarations for utils/config/tree.hpp
+
+#if !defined(UTILS_CONFIG_TREE_FWD_HPP)
+#define UTILS_CONFIG_TREE_FWD_HPP
+
+#include <map>
+#include <string>
+
+namespace utils {
+namespace config {
+
+
+/// Flat representation of all properties as strings.
+typedef std::map< std::string, std::string > properties_map;
+
+
+class tree;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_FWD_HPP)
diff --git a/utils/config/tree_test.cpp b/utils/config/tree_test.cpp
new file mode 100644
index 000000000000..b6efd64a84a6
--- /dev/null
+++ b/utils/config/tree_test.cpp
@@ -0,0 +1,1086 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/config/tree.ipp"
+
+#include <atf-c++.hpp>
+
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/text/operations.ipp"
+
+namespace config = utils::config;
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Simple wrapper around an integer value without default constructors.
+///
+/// The purpose of this type is to have a simple class without default
+/// constructors to validate that we can use it as a leaf of a tree.
+class int_wrapper {
+ /// The wrapped integer value.
+ int _value;
+
+public:
+ /// Constructs a new wrapped integer.
+ ///
+ /// \param value_ The value to store in the object.
+ explicit int_wrapper(int value_) :
+ _value(value_)
+ {
+ }
+
+ /// \return The integer value stored by the object.
+ int
+ value(void) const
+ {
+ return _value;
+ }
+};
+
+
+/// Custom tree leaf type for an object without defualt constructors.
+class wrapped_int_node : public config::typed_leaf_node< int_wrapper > {
+public:
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node*
+ deep_copy(void) const
+ {
+ std::auto_ptr< wrapped_int_node > new_node(new wrapped_int_node());
+ new_node->_value = _value;
+ return new_node.release();
+ }
+
+ /// Pushes the node's value onto the Lua stack.
+ ///
+ /// \param state The Lua state onto which to push the value.
+ void
+ push_lua(lutok::state& state) const
+ {
+ state.push_integer(
+ config::typed_leaf_node< int_wrapper >::value().value());
+ }
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ ///
+ /// \param state The Lua state from which to get the value.
+ /// \param value_index The stack index in which the value resides.
+ void
+ set_lua(lutok::state& state, const int value_index)
+ {
+ ATF_REQUIRE(state.is_number(value_index));
+ int_wrapper new_value(state.to_integer(value_index));
+ config::typed_leaf_node< int_wrapper >::set(new_value);
+ }
+
+ /// Sets the value of the node from a raw string representation.
+ ///
+ /// \param raw_value The value to set the node to.
+ void
+ set_string(const std::string& raw_value)
+ {
+ int_wrapper new_value(text::to_type< int >(raw_value));
+ config::typed_leaf_node< int_wrapper >::set(new_value);
+ }
+
+ /// Converts the contents of the node to a string.
+ ///
+ /// \return A string representation of the value held by the node.
+ std::string
+ to_string(void) const
+ {
+ return F("%s") %
+ config::typed_leaf_node< int_wrapper >::value().value();
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__one_level);
+ATF_TEST_CASE_BODY(define_set_lookup__one_level)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("var2");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::string_node >("var2", "hello");
+ tree.set< config::bool_node >("var3", false);
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("var1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("var2"));
+ ATF_REQUIRE(!tree.lookup< config::bool_node >("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__multiple_levels);
+ATF_TEST_CASE_BODY(define_set_lookup__multiple_levels)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar.1");
+ tree.define< config::string_node >("foo.bar.2");
+ tree.define< config::bool_node >("foo.3");
+ tree.define_dynamic("sub.tree");
+
+ tree.set< config::int_node >("foo.bar.1", 42);
+ tree.set< config::string_node >("foo.bar.2", "hello");
+ tree.set< config::bool_node >("foo.3", true);
+ tree.set< config::string_node >("sub.tree.1", "bye");
+ tree.set< config::int_node >("sub.tree.2", 4);
+ tree.set< config::int_node >("sub.tree.3.4", 123);
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2"));
+ ATF_REQUIRE(tree.lookup< config::bool_node >("foo.3"));
+ ATF_REQUIRE_EQ(4, tree.lookup< config::int_node >("sub.tree.2"));
+ ATF_REQUIRE_EQ(123, tree.lookup< config::int_node >("sub.tree.3.4"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__empty);
+ATF_TEST_CASE_BODY(deep_copy__empty)
+{
+ config::tree tree1;
+ config::tree tree2 = tree1.deep_copy();
+
+ tree1.define< config::bool_node >("var1");
+ // This would crash if the copy shared the internal data.
+ tree2.define< config::int_node >("var1");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__some);
+ATF_TEST_CASE_BODY(deep_copy__some)
+{
+ config::tree tree1;
+ tree1.define< config::bool_node >("this.is.a.var");
+ tree1.set< config::bool_node >("this.is.a.var", true);
+ tree1.define< config::int_node >("this.is.another.var");
+ tree1.set< config::int_node >("this.is.another.var", 34);
+ tree1.define< config::int_node >("and.another");
+ tree1.set< config::int_node >("and.another", 123);
+
+ config::tree tree2 = tree1.deep_copy();
+ tree2.set< config::bool_node >("this.is.a.var", false);
+ tree2.set< config::int_node >("this.is.another.var", 43);
+
+ ATF_REQUIRE( tree1.lookup< config::bool_node >("this.is.a.var"));
+ ATF_REQUIRE(!tree2.lookup< config::bool_node >("this.is.a.var"));
+
+ ATF_REQUIRE_EQ(34, tree1.lookup< config::int_node >("this.is.another.var"));
+ ATF_REQUIRE_EQ(43, tree2.lookup< config::int_node >("this.is.another.var"));
+
+ ATF_REQUIRE_EQ(123, tree1.lookup< config::int_node >("and.another"));
+ ATF_REQUIRE_EQ(123, tree2.lookup< config::int_node >("and.another"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__empty);
+ATF_TEST_CASE_BODY(combine__empty)
+{
+ const config::tree t1, t2;
+ const config::tree combined = t1.combine(t2);
+
+ const config::tree expected;
+ ATF_REQUIRE(expected == combined);
+}
+
+
+static void
+init_tree_for_combine_test(config::tree& tree)
+{
+ tree.define< config::int_node >("int-node");
+ tree.define< config::string_node >("string-node");
+ tree.define< config::int_node >("unused.node");
+ tree.define< config::int_node >("deeper.int.node");
+ tree.define_dynamic("deeper.dynamic");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_overrides);
+ATF_TEST_CASE_BODY(combine__same_layout__no_overrides)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t1.set< config::int_node >("int-node", 3);
+ t1.set< config::string_node >("string-node", "foo");
+ t1.set< config::int_node >("deeper.int.node", 15);
+ t1.set_string("deeper.dynamic.first", "value1");
+ t1.set_string("deeper.dynamic.second", "value2");
+ const config::tree combined = t1.combine(t2);
+
+ ATF_REQUIRE(t1 == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_base);
+ATF_TEST_CASE_BODY(combine__same_layout__no_base)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t2.set< config::int_node >("int-node", 3);
+ t2.set< config::string_node >("string-node", "foo");
+ t2.set< config::int_node >("deeper.int.node", 15);
+ t2.set_string("deeper.dynamic.first", "value1");
+ t2.set_string("deeper.dynamic.second", "value2");
+ const config::tree combined = t1.combine(t2);
+
+ ATF_REQUIRE(t2 == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__mix);
+ATF_TEST_CASE_BODY(combine__same_layout__mix)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t1.set< config::int_node >("int-node", 3);
+ t2.set< config::int_node >("int-node", 5);
+ t1.set< config::string_node >("string-node", "foo");
+ t2.set< config::int_node >("deeper.int.node", 15);
+ t1.set_string("deeper.dynamic.first", "value1");
+ t1.set_string("deeper.dynamic.second", "value2.1");
+ t2.set_string("deeper.dynamic.second", "value2.2");
+ t2.set_string("deeper.dynamic.third", "value3");
+ const config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ init_tree_for_combine_test(expected);
+ expected.set< config::int_node >("int-node", 5);
+ expected.set< config::string_node >("string-node", "foo");
+ expected.set< config::int_node >("deeper.int.node", 15);
+ expected.set_string("deeper.dynamic.first", "value1");
+ expected.set_string("deeper.dynamic.second", "value2.2");
+ expected.set_string("deeper.dynamic.third", "value3");
+ ATF_REQUIRE(expected == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__different_layout);
+ATF_TEST_CASE_BODY(combine__different_layout)
+{
+ config::tree t1;
+ t1.define< config::int_node >("common.base1");
+ t1.define< config::int_node >("common.base2");
+ t1.define_dynamic("dynamic.base");
+ t1.define< config::int_node >("unset.base");
+
+ config::tree t2;
+ t2.define< config::int_node >("common.base2");
+ t2.define< config::int_node >("common.base3");
+ t2.define_dynamic("dynamic.other");
+ t2.define< config::int_node >("unset.other");
+
+ t1.set< config::int_node >("common.base1", 1);
+ t1.set< config::int_node >("common.base2", 2);
+ t1.set_string("dynamic.base.first", "foo");
+ t1.set_string("dynamic.base.second", "bar");
+
+ t2.set< config::int_node >("common.base2", 4);
+ t2.set< config::int_node >("common.base3", 3);
+ t2.set_string("dynamic.other.first", "FOO");
+ t2.set_string("dynamic.other.second", "BAR");
+
+ config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ expected.define< config::int_node >("common.base1");
+ expected.define< config::int_node >("common.base2");
+ expected.define< config::int_node >("common.base3");
+ expected.define_dynamic("dynamic.base");
+ expected.define_dynamic("dynamic.other");
+ expected.define< config::int_node >("unset.base");
+ expected.define< config::int_node >("unset.other");
+
+ expected.set< config::int_node >("common.base1", 1);
+ expected.set< config::int_node >("common.base2", 4);
+ expected.set< config::int_node >("common.base3", 3);
+ expected.set_string("dynamic.base.first", "foo");
+ expected.set_string("dynamic.base.second", "bar");
+ expected.set_string("dynamic.other.first", "FOO");
+ expected.set_string("dynamic.other.second", "BAR");
+
+ ATF_REQUIRE(expected == combined);
+
+ // The combined tree should have respected existing but unset nodes. Check
+ // that these calls do not crash.
+ combined.set< config::int_node >("unset.base", 5);
+ combined.set< config::int_node >("unset.other", 5);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__dynamic_wins);
+ATF_TEST_CASE_BODY(combine__dynamic_wins)
+{
+ config::tree t1;
+ t1.define< config::int_node >("inner.leaf1");
+ t1.set< config::int_node >("inner.leaf1", 3);
+
+ config::tree t2;
+ t2.define_dynamic("inner");
+ t2.set_string("inner.leaf2", "4");
+
+ config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ expected.define_dynamic("inner");
+ expected.set_string("inner.leaf1", "3");
+ expected.set_string("inner.leaf2", "4");
+
+ ATF_REQUIRE(expected == combined);
+
+ // The combined inner node should have become dynamic so this call should
+ // not fail.
+ combined.set_string("inner.leaf3", "5");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__inner_leaf_mismatch);
+ATF_TEST_CASE_BODY(combine__inner_leaf_mismatch)
+{
+ config::tree t1;
+ t1.define< config::int_node >("top.foo.bar");
+
+ config::tree t2;
+ t2.define< config::int_node >("top.foo");
+
+ ATF_REQUIRE_THROW_RE(config::bad_combination_error,
+ "'top.foo' is an inner node in the base tree but a "
+ "leaf node in the overrides tree",
+ t1.combine(t2));
+
+ ATF_REQUIRE_THROW_RE(config::bad_combination_error,
+ "'top.foo' is a leaf node in the base tree but an "
+ "inner node in the overrides tree",
+ t2.combine(t1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup__invalid_key);
+ATF_TEST_CASE_BODY(lookup__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error,
+ tree.lookup< config::int_node >("."));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup__unknown_key);
+ATF_TEST_CASE_BODY(lookup__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::int_node >("a.d.100", 0);
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("abc"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo.bar"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo.bar.baz"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.b"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.c"));
+ (void)tree.lookup< config::int_node >("a.b.c");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.b.c.d"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d"));
+ (void)tree.lookup< config::int_node >("a.d.100");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.101"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.100.3"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.e"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__one_level);
+ATF_TEST_CASE_BODY(is_set__one_level)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("var2");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::bool_node >("var3", false);
+
+ ATF_REQUIRE( tree.is_set("var1"));
+ ATF_REQUIRE(!tree.is_set("var2"));
+ ATF_REQUIRE( tree.is_set("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__multiple_levels);
+ATF_TEST_CASE_BODY(is_set__multiple_levels)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("a.b.var1");
+ tree.define< config::string_node >("a.b.var2");
+ tree.define< config::bool_node >("e.var3");
+
+ tree.set< config::int_node >("a.b.var1", 42);
+ tree.set< config::bool_node >("e.var3", false);
+
+ ATF_REQUIRE(!tree.is_set("a"));
+ ATF_REQUIRE(!tree.is_set("a.b"));
+ ATF_REQUIRE( tree.is_set("a.b.var1"));
+ ATF_REQUIRE(!tree.is_set("a.b.var1.trailing"));
+
+ ATF_REQUIRE(!tree.is_set("a"));
+ ATF_REQUIRE(!tree.is_set("a.b"));
+ ATF_REQUIRE(!tree.is_set("a.b.var2"));
+ ATF_REQUIRE(!tree.is_set("a.b.var2.trailing"));
+
+ ATF_REQUIRE(!tree.is_set("e"));
+ ATF_REQUIRE( tree.is_set("e.var3"));
+ ATF_REQUIRE(!tree.is_set("e.var3.trailing"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__invalid_key);
+ATF_TEST_CASE_BODY(is_set__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.is_set(".abc"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key);
+ATF_TEST_CASE_BODY(set__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error,
+ tree.set< config::int_node >("foo.", 54));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key_value);
+ATF_TEST_CASE_BODY(set__invalid_key_value)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define_dynamic("a.d");
+
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set< config::int_node >("foo", 3));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set< config::int_node >("a", -10));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key);
+ATF_TEST_CASE_BODY(set__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::string_node >("a.d.3", "foo");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("abc", 2));
+
+ tree.set< config::int_node >("foo.bar", 15);
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("foo.bar.baz", 0));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.c", 100));
+ tree.set< config::int_node >("a.b.c", -3);
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.b.c.d", 82));
+ tree.set< config::string_node >("a.d.3", "bar");
+ tree.set< config::string_node >("a.d.4", "bar");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.d.4.5", 82));
+ tree.set< config::int_node >("a.d.5.6", 82);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key_not_strict);
+ATF_TEST_CASE_BODY(set__unknown_key_not_strict)
+{
+ config::tree tree(false);
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::string_node >("a.d.3", "foo");
+
+ tree.set< config::int_node >("abc", 2);
+ ATF_REQUIRE(!tree.is_set("abc"));
+
+ tree.set< config::int_node >("foo.bar", 15);
+ tree.set< config::int_node >("foo.bar.baz", 0);
+ ATF_REQUIRE(!tree.is_set("foo.bar.baz"));
+
+ tree.set< config::int_node >("a.c", 100);
+ ATF_REQUIRE(!tree.is_set("a.c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(push_lua__ok);
+ATF_TEST_CASE_BODY(push_lua__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("top.integer");
+ tree.define< wrapped_int_node >("top.custom");
+ tree.define_dynamic("dynamic");
+ tree.set< config::int_node >("top.integer", 5);
+ tree.set< wrapped_int_node >("top.custom", int_wrapper(10));
+ tree.set_string("dynamic.first", "foo");
+
+ lutok::state state;
+ tree.push_lua("top.integer", state);
+ tree.push_lua("top.custom", state);
+ tree.push_lua("dynamic.first", state);
+ ATF_REQUIRE(state.is_number(-3));
+ ATF_REQUIRE_EQ(5, state.to_integer(-3));
+ ATF_REQUIRE(state.is_number(-2));
+ ATF_REQUIRE_EQ(10, state.to_integer(-2));
+ ATF_REQUIRE(state.is_string(-1));
+ ATF_REQUIRE_EQ("foo", state.to_string(-1));
+ state.pop(3);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_lua__ok);
+ATF_TEST_CASE_BODY(set_lua__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("top.integer");
+ tree.define< wrapped_int_node >("top.custom");
+ tree.define_dynamic("dynamic");
+
+ {
+ lutok::state state;
+ state.push_integer(5);
+ state.push_integer(10);
+ state.push_string("foo");
+ tree.set_lua("top.integer", state, -3);
+ tree.set_lua("top.custom", state, -2);
+ tree.set_lua("dynamic.first", state, -1);
+ state.pop(3);
+ }
+
+ ATF_REQUIRE_EQ(5, tree.lookup< config::int_node >("top.integer"));
+ ATF_REQUIRE_EQ(10, tree.lookup< wrapped_int_node >("top.custom").value());
+ ATF_REQUIRE_EQ("foo", tree.lookup< config::string_node >("dynamic.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_rw);
+ATF_TEST_CASE_BODY(lookup_rw)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::bool_node >("var3", false);
+
+ tree.lookup_rw< config::int_node >("var1") += 10;
+ ATF_REQUIRE_EQ(52, tree.lookup< config::int_node >("var1"));
+ ATF_REQUIRE(!tree.lookup< config::bool_node >("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__ok);
+ATF_TEST_CASE_BODY(lookup_string__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("b.var2");
+ tree.define< config::bool_node >("c.d.var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::string_node >("b.var2", "hello");
+ tree.set< config::bool_node >("c.d.var3", false);
+
+ ATF_REQUIRE_EQ("42", tree.lookup_string("var1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup_string("b.var2"));
+ ATF_REQUIRE_EQ("false", tree.lookup_string("c.d.var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__invalid_key);
+ATF_TEST_CASE_BODY(lookup_string__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.lookup_string(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__unknown_key);
+ATF_TEST_CASE_BODY(lookup_string__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("a.b.c");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b"));
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b.c.d"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__ok);
+ATF_TEST_CASE_BODY(set_string__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar.1");
+ tree.define< config::string_node >("foo.bar.2");
+ tree.define_dynamic("sub.tree");
+
+ tree.set_string("foo.bar.1", "42");
+ tree.set_string("foo.bar.2", "hello");
+ tree.set_string("sub.tree.2", "15");
+ tree.set_string("sub.tree.3.4", "bye");
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2"));
+ ATF_REQUIRE_EQ("15", tree.lookup< config::string_node >("sub.tree.2"));
+ ATF_REQUIRE_EQ("bye", tree.lookup< config::string_node >("sub.tree.3.4"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key);
+ATF_TEST_CASE_BODY(set_string__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.set_string(".", "foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key_value);
+ATF_TEST_CASE_BODY(set_string__invalid_key_value)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo", "abc"));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo.bar", " -3"));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo.bar", "3 "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key);
+ATF_TEST_CASE_BODY(set_string__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set_string("a.b.c", "123");
+ tree.set_string("a.d.3", "foo");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("abc", "2"));
+
+ tree.set_string("foo.bar", "15");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("foo.bar.baz", "0"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.c", "100"));
+ tree.set_string("a.b.c", "-3");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.b.c.d", "82"));
+ tree.set_string("a.d.3", "bar");
+ tree.set_string("a.d.4", "bar");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.d.4.5", "82"));
+ tree.set_string("a.d.5.6", "82");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key_not_strict);
+ATF_TEST_CASE_BODY(set_string__unknown_key_not_strict)
+{
+ config::tree tree(false);
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set_string("a.b.c", "123");
+ tree.set_string("a.d.3", "foo");
+
+ tree.set_string("abc", "2");
+ ATF_REQUIRE(!tree.is_set("abc"));
+
+ tree.set_string("foo.bar", "15");
+ tree.set_string("foo.bar.baz", "0");
+ ATF_REQUIRE(!tree.is_set("foo.bar.baz"));
+
+ tree.set_string("a.c", "100");
+ ATF_REQUIRE(!tree.is_set("a.c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__none);
+ATF_TEST_CASE_BODY(all_properties__none)
+{
+ const config::tree tree;
+ ATF_REQUIRE(tree.all_properties().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__all_set);
+ATF_TEST_CASE_BODY(all_properties__all_set)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("plain");
+ tree.set< config::int_node >("plain", 1234);
+
+ tree.define< config::int_node >("static.first");
+ tree.set< config::int_node >("static.first", -3);
+ tree.define< config::string_node >("static.second");
+ tree.set< config::string_node >("static.second", "some text");
+
+ tree.define_dynamic("dynamic");
+ tree.set< config::string_node >("dynamic.first", "hello");
+ tree.set< config::string_node >("dynamic.second", "bye");
+
+ config::properties_map exp_properties;
+ exp_properties["plain"] = "1234";
+ exp_properties["static.first"] = "-3";
+ exp_properties["static.second"] = "some text";
+ exp_properties["dynamic.first"] = "hello";
+ exp_properties["dynamic.second"] = "bye";
+
+ const config::properties_map properties = tree.all_properties();
+ ATF_REQUIRE(exp_properties == properties);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__some_unset);
+ATF_TEST_CASE_BODY(all_properties__some_unset)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("static.first");
+ tree.set< config::int_node >("static.first", -3);
+ tree.define< config::string_node >("static.second");
+
+ tree.define_dynamic("dynamic");
+
+ config::properties_map exp_properties;
+ exp_properties["static.first"] = "-3";
+
+ const config::properties_map properties = tree.all_properties();
+ ATF_REQUIRE(exp_properties == properties);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__inner);
+ATF_TEST_CASE_BODY(all_properties__subtree__inner)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.define< config::int_node >("root.a.b.c.second");
+ tree.define< config::int_node >("root.a.d.first");
+
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.set< config::int_node >("root.a.b.c.second", 2);
+ tree.set< config::int_node >("root.a.d.first", 3);
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.b.c.first"] = "1";
+ exp_properties["root.a.b.c.second"] = "2";
+ exp_properties["root.a.d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root"));
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a"));
+ }
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.b.c.first"] = "1";
+ exp_properties["root.a.b.c.second"] = "2";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b"));
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b.c"));
+ }
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.d"));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__leaf);
+ATF_TEST_CASE_BODY(all_properties__subtree__leaf)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf",
+ tree.all_properties("root.a.b.c.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__strip_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__strip_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.define< config::int_node >("root.a.b.c.second");
+ tree.define< config::int_node >("root.a.d.first");
+
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.set< config::int_node >("root.a.b.c.second", 2);
+ tree.set< config::int_node >("root.a.d.first", 3);
+
+ config::properties_map exp_properties;
+ exp_properties["b.c.first"] = "1";
+ exp_properties["b.c.second"] = "2";
+ exp_properties["d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a", true));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__invalid_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.all_properties("."));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__unknown_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.define< config::int_node >("root.a.b.c.unset");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.all_properties("root.a.b.c.first.foo"));
+ ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf",
+ tree.all_properties("root.a.b.c.unset"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__empty);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__empty)
+{
+ config::tree t1;
+ config::tree t2;
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__shallow_copy);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__shallow_copy)
+{
+ config::tree t1;
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ config::tree t2 = t1;
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__deep_copy);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__deep_copy)
+{
+ config::tree t1;
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ config::tree t2 = t1.deep_copy();
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__some_contents);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__some_contents)
+{
+ config::tree t1, t2;
+
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.define< config::int_node >("root.a.b.c.first");
+ t2.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+
+ t1.set< config::int_node >("root.a.b.c.first", 2);
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.set< config::int_node >("root.a.b.c.first", 2);
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+
+ t1.define< config::string_node >("another.key");
+ t1.set< config::string_node >("another.key", "some text");
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.define< config::string_node >("another.key");
+ t2.set< config::string_node >("another.key", "some text");
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(custom_leaf__no_default_ctor);
+ATF_TEST_CASE_BODY(custom_leaf__no_default_ctor)
+{
+ config::tree tree;
+
+ tree.define< wrapped_int_node >("test1");
+ tree.define< wrapped_int_node >("test2");
+ tree.set< wrapped_int_node >("test1", int_wrapper(5));
+ tree.set< wrapped_int_node >("test2", int_wrapper(10));
+ const int_wrapper& test1 = tree.lookup< wrapped_int_node >("test1");
+ ATF_REQUIRE_EQ(5, test1.value());
+ const int_wrapper& test2 = tree.lookup< wrapped_int_node >("test2");
+ ATF_REQUIRE_EQ(10, test2.value());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, define_set_lookup__one_level);
+ ATF_ADD_TEST_CASE(tcs, define_set_lookup__multiple_levels);
+
+ ATF_ADD_TEST_CASE(tcs, deep_copy__empty);
+ ATF_ADD_TEST_CASE(tcs, deep_copy__some);
+
+ ATF_ADD_TEST_CASE(tcs, combine__empty);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_overrides);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_base);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__mix);
+ ATF_ADD_TEST_CASE(tcs, combine__different_layout);
+ ATF_ADD_TEST_CASE(tcs, combine__dynamic_wins);
+ ATF_ADD_TEST_CASE(tcs, combine__inner_leaf_mismatch);
+
+ ATF_ADD_TEST_CASE(tcs, lookup__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, lookup__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, is_set__one_level);
+ ATF_ADD_TEST_CASE(tcs, is_set__multiple_levels);
+ ATF_ADD_TEST_CASE(tcs, is_set__invalid_key);
+
+ ATF_ADD_TEST_CASE(tcs, set__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, set__invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, set__unknown_key);
+ ATF_ADD_TEST_CASE(tcs, set__unknown_key_not_strict);
+
+ ATF_ADD_TEST_CASE(tcs, push_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, set_lua__ok);
+
+ ATF_ADD_TEST_CASE(tcs, lookup_rw);
+
+ ATF_ADD_TEST_CASE(tcs, lookup_string__ok);
+ ATF_ADD_TEST_CASE(tcs, lookup_string__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, lookup_string__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, set_string__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, set_string__invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, set_string__unknown_key);
+ ATF_ADD_TEST_CASE(tcs, set_string__unknown_key_not_strict);
+
+ ATF_ADD_TEST_CASE(tcs, all_properties__none);
+ ATF_ADD_TEST_CASE(tcs, all_properties__all_set);
+ ATF_ADD_TEST_CASE(tcs, all_properties__some_unset);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__inner);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__leaf);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__strip_key);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__empty);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__shallow_copy);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__some_contents);
+
+ ATF_ADD_TEST_CASE(tcs, custom_leaf__no_default_ctor);
+}