diff options
author | Brooks Davis <brooks@FreeBSD.org> | 2020-03-17 16:56:50 +0000 |
---|---|---|
committer | Brooks Davis <brooks@FreeBSD.org> | 2020-03-17 16:56:50 +0000 |
commit | 08334c51dbb99d9ecd2bb86a2d94ed06da9e167a (patch) | |
tree | c43eb24d59bd5c963583a5190caef80fc8387322 /utils/config | |
download | src-vendor/kyua.tar.gz src-vendor/kyua.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')
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); +} |