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 /model | |
download | src-08334c51dbb99d9ecd2bb86a2d94ed06da9e167a.tar.gz src-08334c51dbb99d9ecd2bb86a2d94ed06da9e167a.zip |
Import the kyua testing framework for infrastructure softwarevendor/kyua/0.13-a685f91vendor/kyua
Imported at 0.13 plus assumulated changes to git hash a685f91.
Obtained from: https://github.com/jmmv/kyua
Sponsored by: DARPA
Notes
Notes:
svn path=/vendor/kyua/dist/; revision=359042
svn path=/vendor/kyua/0.13-a685f91/; revision=359043; tag=vendor/kyua/0.13-a685f91
Diffstat (limited to 'model')
-rw-r--r-- | model/Kyuafile | 10 | ||||
-rw-r--r-- | model/Makefile.am.inc | 89 | ||||
-rw-r--r-- | model/README | 11 | ||||
-rw-r--r-- | model/context.cpp | 159 | ||||
-rw-r--r-- | model/context.hpp | 76 | ||||
-rw-r--r-- | model/context_fwd.hpp | 43 | ||||
-rw-r--r-- | model/context_test.cpp | 106 | ||||
-rw-r--r-- | model/exceptions.cpp | 76 | ||||
-rw-r--r-- | model/exceptions.hpp | 71 | ||||
-rw-r--r-- | model/exceptions_test.cpp | 65 | ||||
-rw-r--r-- | model/metadata.cpp | 1068 | ||||
-rw-r--r-- | model/metadata.hpp | 130 | ||||
-rw-r--r-- | model/metadata_fwd.hpp | 44 | ||||
-rw-r--r-- | model/metadata_test.cpp | 461 | ||||
-rw-r--r-- | model/test_case.cpp | 339 | ||||
-rw-r--r-- | model/test_case.hpp | 98 | ||||
-rw-r--r-- | model/test_case_fwd.hpp | 51 | ||||
-rw-r--r-- | model/test_case_test.cpp | 263 | ||||
-rw-r--r-- | model/test_program.cpp | 452 | ||||
-rw-r--r-- | model/test_program.hpp | 110 | ||||
-rw-r--r-- | model/test_program_fwd.hpp | 55 | ||||
-rw-r--r-- | model/test_program_test.cpp | 711 | ||||
-rw-r--r-- | model/test_result.cpp | 142 | ||||
-rw-r--r-- | model/test_result.hpp | 79 | ||||
-rw-r--r-- | model/test_result_fwd.hpp | 53 | ||||
-rw-r--r-- | model/test_result_test.cpp | 185 | ||||
-rw-r--r-- | model/types.hpp | 61 |
27 files changed, 5008 insertions, 0 deletions
diff --git a/model/Kyuafile b/model/Kyuafile new file mode 100644 index 000000000000..9dae3b9c64ce --- /dev/null +++ b/model/Kyuafile @@ -0,0 +1,10 @@ +syntax(2) + +test_suite("kyua") + +atf_test_program{name="context_test"} +atf_test_program{name="exceptions_test"} +atf_test_program{name="metadata_test"} +atf_test_program{name="test_case_test"} +atf_test_program{name="test_program_test"} +atf_test_program{name="test_result_test"} diff --git a/model/Makefile.am.inc b/model/Makefile.am.inc new file mode 100644 index 000000000000..2bd33914f680 --- /dev/null +++ b/model/Makefile.am.inc @@ -0,0 +1,89 @@ +# Copyright 2014 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. + +MODEL_CFLAGS = $(UTILS_CFLAGS) +MODEL_LIBS = libmodel.a $(UTILS_LIBS) + +noinst_LIBRARIES += libmodel.a +libmodel_a_CPPFLAGS = $(UTILS_CFLAGS) +libmodel_a_SOURCES = model/context.cpp +libmodel_a_SOURCES += model/context.hpp +libmodel_a_SOURCES += model/context_fwd.hpp +libmodel_a_SOURCES += model/exceptions.cpp +libmodel_a_SOURCES += model/exceptions.hpp +libmodel_a_SOURCES += model/metadata.cpp +libmodel_a_SOURCES += model/metadata.hpp +libmodel_a_SOURCES += model/metadata_fwd.hpp +libmodel_a_SOURCES += model/test_case.cpp +libmodel_a_SOURCES += model/test_case.hpp +libmodel_a_SOURCES += model/test_case_fwd.hpp +libmodel_a_SOURCES += model/test_program.cpp +libmodel_a_SOURCES += model/test_program.hpp +libmodel_a_SOURCES += model/test_program_fwd.hpp +libmodel_a_SOURCES += model/test_result.cpp +libmodel_a_SOURCES += model/test_result.hpp +libmodel_a_SOURCES += model/test_result_fwd.hpp +libmodel_a_SOURCES += model/types.hpp + +if WITH_ATF +tests_modeldir = $(pkgtestsdir)/model + +tests_model_DATA = model/Kyuafile +EXTRA_DIST += $(tests_model_DATA) + +tests_model_PROGRAMS = model/context_test +model_context_test_SOURCES = model/context_test.cpp +model_context_test_CXXFLAGS = $(MODEL_CFLAGS) $(ATF_CXX_CFLAGS) +model_context_test_LDADD = $(MODEL_LIBS) $(ATF_CXX_LIBS) + +tests_model_PROGRAMS += model/exceptions_test +model_exceptions_test_SOURCES = model/exceptions_test.cpp +model_exceptions_test_CXXFLAGS = $(MODEL_CFLAGS) $(ATF_CXX_CFLAGS) +model_exceptions_test_LDADD = $(MODEL_LIBS) $(ATF_CXX_LIBS) + +tests_model_PROGRAMS += model/metadata_test +model_metadata_test_SOURCES = model/metadata_test.cpp +model_metadata_test_CXXFLAGS = $(MODEL_CFLAGS) $(ATF_CXX_CFLAGS) +model_metadata_test_LDADD = $(MODEL_LIBS) $(ATF_CXX_LIBS) + +tests_model_PROGRAMS += model/test_case_test +model_test_case_test_SOURCES = model/test_case_test.cpp +model_test_case_test_CXXFLAGS = $(MODEL_CFLAGS) $(ATF_CXX_CFLAGS) +model_test_case_test_LDADD = $(MODEL_LIBS) $(ATF_CXX_LIBS) + +tests_model_PROGRAMS += model/test_program_test +model_test_program_test_SOURCES = model/test_program_test.cpp +model_test_program_test_CXXFLAGS = $(MODEL_CFLAGS) $(ATF_CXX_CFLAGS) +model_test_program_test_LDADD = $(MODEL_LIBS) $(ATF_CXX_LIBS) + +tests_model_PROGRAMS += model/test_result_test +model_test_result_test_SOURCES = model/test_result_test.cpp +model_test_result_test_CXXFLAGS = $(MODEL_CFLAGS) $(ATF_CXX_CFLAGS) +model_test_result_test_LDADD = $(MODEL_LIBS) $(ATF_CXX_LIBS) + +endif diff --git a/model/README b/model/README new file mode 100644 index 000000000000..cf13a82b7338 --- /dev/null +++ b/model/README @@ -0,0 +1,11 @@ +This directory contains the classes that form the data model of Kyua. + +The classes in this directory are intended to be pure data types without +any complex logic. As such, they are simple containers and support the +common operations you would expect from them: in particular, comparisons +and formatting for debugging purposes. + +All the classes in the data model have to have an on-disk representation +provided by the store module; if they don't, they don't belong in the +model. Some of these classes may also have special behavior at run-time, +and this is provided by the engine module. diff --git a/model/context.cpp b/model/context.cpp new file mode 100644 index 000000000000..5afe89759d94 --- /dev/null +++ b/model/context.cpp @@ -0,0 +1,159 @@ +// Copyright 2011 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 "model/context.hpp" + +#include "utils/format/macros.hpp" +#include "utils/fs/path.hpp" +#include "utils/noncopyable.hpp" +#include "utils/text/operations.ipp" + +namespace fs = utils::fs; +namespace text = utils::text; + + +/// Internal implementation of a context. +struct model::context::impl : utils::noncopyable { + /// The current working directory. + fs::path _cwd; + + /// The environment variables. + std::map< std::string, std::string > _env; + + /// Constructor. + /// + /// \param cwd_ The current working directory. + /// \param env_ The environment variables. + impl(const fs::path& cwd_, + const std::map< std::string, std::string >& env_) : + _cwd(cwd_), + _env(env_) + { + } + + /// Equality comparator. + /// + /// \param other The object to compare to. + /// + /// \return True if the two objects are equal; false otherwise. + bool + operator==(const impl& other) const + { + return _cwd == other._cwd && _env == other._env; + } +}; + + +/// Constructs a new context. +/// +/// \param cwd_ The current working directory. +/// \param env_ The environment variables. +model::context::context(const fs::path& cwd_, + const std::map< std::string, std::string >& env_) : + _pimpl(new impl(cwd_, env_)) +{ +} + + +/// Destructor. +model::context::~context(void) +{ +} + + +/// Returns the current working directory of the context. +/// +/// \return A path. +const fs::path& +model::context::cwd(void) const +{ + return _pimpl->_cwd; +} + + +/// Returns the environment variables of the context. +/// +/// \return A variable name to variable value mapping. +const std::map< std::string, std::string >& +model::context::env(void) const +{ + return _pimpl->_env; +} + + +/// Equality comparator. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object and other are equal; false otherwise. +bool +model::context::operator==(const context& other) const +{ + return *_pimpl == *other._pimpl; +} + + +/// Inequality comparator. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object and other are different; false otherwise. +bool +model::context::operator!=(const context& other) const +{ + return !(*this == other); +} + + +/// Injects the object into a stream. +/// +/// \param output The stream into which to inject the object. +/// \param object The object to format. +/// +/// \return The output stream. +std::ostream& +model::operator<<(std::ostream& output, const context& object) +{ + output << F("context{cwd=%s, env=[") + % text::quote(object.cwd().str(), '\''); + + const std::map< std::string, std::string >& env = object.env(); + bool first = true; + for (std::map< std::string, std::string >::const_iterator + iter = env.begin(); iter != env.end(); ++iter) { + if (!first) + output << ", "; + first = false; + + output << F("%s=%s") % (*iter).first + % text::quote((*iter).second, '\''); + } + + output << "]}"; + return output; +} diff --git a/model/context.hpp b/model/context.hpp new file mode 100644 index 000000000000..d11ae8ba80b9 --- /dev/null +++ b/model/context.hpp @@ -0,0 +1,76 @@ +// Copyright 2011 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 model/context.hpp +/// Representation of runtime contexts. + +#if !defined(MODEL_CONTEXT_HPP) +#define MODEL_CONTEXT_HPP + +#include "model/context_fwd.hpp" + +#include <map> +#include <memory> +#include <ostream> +#include <string> + +#include "utils/fs/path_fwd.hpp" + +namespace model { + + +/// Representation of a runtime context. +/// +/// The instances of this class are unique (i.e. copying the objects only yields +/// a shallow copy that shares the same internal implementation). This is a +/// requirement for the 'store' API model. +class context { + struct impl; + + /// Pointer to the shared internal implementation. + std::shared_ptr< impl > _pimpl; + +public: + context(const utils::fs::path&, + const std::map< std::string, std::string >&); + ~context(void); + + const utils::fs::path& cwd(void) const; + const std::map< std::string, std::string >& env(void) const; + + bool operator==(const context&) const; + bool operator!=(const context&) const; +}; + + +std::ostream& operator<<(std::ostream&, const context&); + + +} // namespace model + +#endif // !defined(MODEL_CONTEXT_HPP) diff --git a/model/context_fwd.hpp b/model/context_fwd.hpp new file mode 100644 index 000000000000..000ed864e948 --- /dev/null +++ b/model/context_fwd.hpp @@ -0,0 +1,43 @@ +// Copyright 2014 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 model/context_fwd.hpp +/// Forward declarations for model/context.hpp + +#if !defined(MODEL_CONTEXT_FWD_HPP) +#define MODEL_CONTEXT_FWD_HPP + +namespace model { + + +class context; + + +} // namespace model + +#endif // !defined(MODEL_CONTEXT_FWD_HPP) diff --git a/model/context_test.cpp b/model/context_test.cpp new file mode 100644 index 000000000000..8990990710f2 --- /dev/null +++ b/model/context_test.cpp @@ -0,0 +1,106 @@ +// Copyright 2011 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 "model/context.hpp" + +#include <map> +#include <sstream> +#include <string> + +#include <atf-c++.hpp> + +#include "utils/fs/path.hpp" + +namespace fs = utils::fs; + + +ATF_TEST_CASE_WITHOUT_HEAD(ctor_and_getters); +ATF_TEST_CASE_BODY(ctor_and_getters) +{ + std::map< std::string, std::string > env; + env["foo"] = "first"; + env["bar"] = "second"; + const model::context context(fs::path("/foo/bar"), env); + ATF_REQUIRE_EQ(fs::path("/foo/bar"), context.cwd()); + ATF_REQUIRE(env == context.env()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne); +ATF_TEST_CASE_BODY(operators_eq_and_ne) +{ + std::map< std::string, std::string > env; + env["foo"] = "first"; + const model::context context1(fs::path("/foo/bar"), env); + const model::context context2(fs::path("/foo/bar"), env); + const model::context context3(fs::path("/foo/baz"), env); + env["bar"] = "second"; + const model::context context4(fs::path("/foo/bar"), env); + ATF_REQUIRE( context1 == context2); + ATF_REQUIRE(!(context1 != context2)); + ATF_REQUIRE(!(context1 == context3)); + ATF_REQUIRE( context1 != context3); + ATF_REQUIRE(!(context1 == context4)); + ATF_REQUIRE( context1 != context4); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(output__empty_env); +ATF_TEST_CASE_BODY(output__empty_env) +{ + const std::map< std::string, std::string > env; + const model::context context(fs::path("/foo/bar"), env); + + std::ostringstream str; + str << context; + ATF_REQUIRE_EQ("context{cwd='/foo/bar', env=[]}", str.str()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(output__some_env); +ATF_TEST_CASE_BODY(output__some_env) +{ + std::map< std::string, std::string > env; + env["foo"] = "first"; + env["bar"] = "second' var"; + const model::context context(fs::path("/foo/bar"), env); + + std::ostringstream str; + str << context; + ATF_REQUIRE_EQ("context{cwd='/foo/bar', env=[bar='second\\' var', " + "foo='first']}", str.str()); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, ctor_and_getters); + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne); + ATF_ADD_TEST_CASE(tcs, output__empty_env); + ATF_ADD_TEST_CASE(tcs, output__some_env); +} diff --git a/model/exceptions.cpp b/model/exceptions.cpp new file mode 100644 index 000000000000..dc511a2b7e8f --- /dev/null +++ b/model/exceptions.cpp @@ -0,0 +1,76 @@ +// Copyright 2010 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 "model/exceptions.hpp" + +#include "utils/format/macros.hpp" + + +/// Constructs a new error with a plain-text message. +/// +/// \param message The plain-text error message. +model::error::error(const std::string& message) : + std::runtime_error(message) +{ +} + + +/// Destructor for the error. +model::error::~error(void) throw() +{ +} + + +/// Constructs a new format_error. +/// +/// \param message The plain-text error message. +model::format_error::format_error(const std::string& message) : + error(message) +{ +} + + +/// Destructor for the error. +model::format_error::~format_error(void) throw() +{ +} + + +/// Constructs a new not_found_error. +/// +/// \param message The plain-text error message. +model::not_found_error::not_found_error(const std::string& message) : + error(message) +{ +} + + +/// Destructor for the error. +model::not_found_error::~not_found_error(void) throw() +{ +} diff --git a/model/exceptions.hpp b/model/exceptions.hpp new file mode 100644 index 000000000000..ff4970fc37d7 --- /dev/null +++ b/model/exceptions.hpp @@ -0,0 +1,71 @@ +// Copyright 2010 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 model/exceptions.hpp +/// Exception types raised by the model module. +/// +/// There is no model/exceptions_fwd.hpp counterpart because this file is +/// inteded to be used only from within .cpp files to either raise or +/// handle raised exceptions, neither of which are possible with just +/// forward declarations. + +#if !defined(MODEL_EXCEPTIONS_HPP) +#define MODEL_EXCEPTIONS_HPP + +#include <stdexcept> + +namespace model { + + +/// Base exception for model errors. +class error : public std::runtime_error { +public: + explicit error(const std::string&); + virtual ~error(void) throw(); +}; + + +/// Error while parsing external data. +class format_error : public error { +public: + explicit format_error(const std::string&); + virtual ~format_error(void) throw(); +}; + + +/// A requested element could not be found. +class not_found_error : public error { +public: + explicit not_found_error(const std::string&); + virtual ~not_found_error(void) throw(); +}; + + +} // namespace model + +#endif // !defined(MODEL_EXCEPTIONS_HPP) diff --git a/model/exceptions_test.cpp b/model/exceptions_test.cpp new file mode 100644 index 000000000000..e9c17c0cc19a --- /dev/null +++ b/model/exceptions_test.cpp @@ -0,0 +1,65 @@ +// Copyright 2010 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 "model/exceptions.hpp" + +#include <cstring> + +#include <atf-c++.hpp> + + +ATF_TEST_CASE_WITHOUT_HEAD(error); +ATF_TEST_CASE_BODY(error) +{ + const model::error e("Some text"); + ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(format_error); +ATF_TEST_CASE_BODY(format_error) +{ + const model::format_error e("Some other text"); + ATF_REQUIRE(std::strcmp("Some other text", e.what()) == 0); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(not_found_error); +ATF_TEST_CASE_BODY(not_found_error) +{ + const model::not_found_error e("Missing foo"); + ATF_REQUIRE(std::strcmp("Missing foo", e.what()) == 0); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, error); + ATF_ADD_TEST_CASE(tcs, format_error); + ATF_ADD_TEST_CASE(tcs, not_found_error); +} diff --git a/model/metadata.cpp b/model/metadata.cpp new file mode 100644 index 000000000000..d27e3237dcf2 --- /dev/null +++ b/model/metadata.cpp @@ -0,0 +1,1068 @@ +// 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 "model/metadata.hpp" + +#include <memory> + +#include "model/exceptions.hpp" +#include "model/types.hpp" +#include "utils/config/exceptions.hpp" +#include "utils/config/nodes.ipp" +#include "utils/config/tree.ipp" +#include "utils/datetime.hpp" +#include "utils/defs.hpp" +#include "utils/format/macros.hpp" +#include "utils/fs/exceptions.hpp" +#include "utils/fs/path.hpp" +#include "utils/noncopyable.hpp" +#include "utils/optional.ipp" +#include "utils/sanity.hpp" +#include "utils/text/exceptions.hpp" +#include "utils/text/operations.hpp" +#include "utils/units.hpp" + +namespace config = utils::config; +namespace datetime = utils::datetime; +namespace fs = utils::fs; +namespace text = utils::text; +namespace units = utils::units; + +using utils::optional; + + +namespace { + + +/// Global instance of defaults. +/// +/// This exists so that the getters in metadata can return references instead +/// of object copies. Use get_defaults() to query. +static optional< config::tree > defaults; + + +/// A leaf node that holds a bytes quantity. +class bytes_node : public config::native_leaf_node< units::bytes > { +public: + /// Copies the node. + /// + /// \return A dynamically-allocated node. + virtual base_node* + deep_copy(void) const + { + std::auto_ptr< bytes_node > new_node(new bytes_node()); + new_node->_value = _value; + return new_node.release(); + } + + /// Pushes the node's value onto the Lua stack. + void + push_lua(lutok::state& /* state */) const + { + UNREACHABLE; + } + + /// Sets the value of the node from an entry in the Lua stack. + void + set_lua(lutok::state& /* state */, const int /* index */) + { + UNREACHABLE; + } +}; + + +/// A leaf node that holds a time delta. +class delta_node : public config::typed_leaf_node< datetime::delta > { +public: + /// Copies the node. + /// + /// \return A dynamically-allocated node. + virtual base_node* + deep_copy(void) const + { + std::auto_ptr< delta_node > new_node(new delta_node()); + new_node->_value = _value; + return new_node.release(); + } + + /// 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. + void + set_string(const std::string& raw_value) + { + unsigned int seconds; + try { + seconds = text::to_type< unsigned int >(raw_value); + } catch (const text::error& e) { + throw config::value_error(F("Invalid time delta %s") % raw_value); + } + set(datetime::delta(seconds, 0)); + } + + /// 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. + std::string + to_string(void) const + { + return F("%s") % value().seconds; + } + + /// Pushes the node's value onto the Lua stack. + void + push_lua(lutok::state& /* state */) const + { + UNREACHABLE; + } + + /// Sets the value of the node from an entry in the Lua stack. + void + set_lua(lutok::state& /* state */, const int /* index */) + { + UNREACHABLE; + } +}; + + +/// A leaf node that holds a "required user" property. +/// +/// This node is just a string, but it provides validation of the only allowed +/// values. +class user_node : public config::string_node { + /// Copies the node. + /// + /// \return A dynamically-allocated node. + virtual base_node* + deep_copy(void) const + { + std::auto_ptr< user_node > new_node(new user_node()); + new_node->_value = _value; + return new_node.release(); + } + + /// Checks a given user textual representation for validity. + /// + /// \param user The value to validate. + /// + /// \throw config::value_error If the value is not valid. + void + validate(const value_type& user) const + { + if (!user.empty() && user != "root" && user != "unprivileged") + throw config::value_error("Invalid required user value"); + } +}; + + +/// A leaf node that holds a set of paths. +/// +/// This node type is used to represent the value of the required files and +/// required programs, for example, and these do not allow relative paths. We +/// check this here. +class paths_set_node : public config::base_set_node< fs::path > { + /// Copies the node. + /// + /// \return A dynamically-allocated node. + virtual base_node* + deep_copy(void) const + { + std::auto_ptr< paths_set_node > new_node(new paths_set_node()); + new_node->_value = _value; + return new_node.release(); + } + + /// Converts a single path to the native type. + /// + /// \param raw_value The value to parse. + /// + /// \return The parsed value. + /// + /// \throw config::value_error If the value is invalid. + fs::path + parse_one(const std::string& raw_value) const + { + try { + return fs::path(raw_value); + } catch (const fs::error& e) { + throw config::value_error(e.what()); + } + } + + /// Checks a collection of paths for validity. + /// + /// \param paths The value to validate. + /// + /// \throw config::value_error If the value is not valid. + void + validate(const value_type& paths) const + { + for (value_type::const_iterator iter = paths.begin(); + iter != paths.end(); ++iter) { + const fs::path& path = *iter; + if (!path.is_absolute() && path.ncomponents() > 1) + throw config::value_error(F("Relative path '%s' not allowed") % + *iter); + } + } +}; + + +/// Initializes a tree to hold test case requirements. +/// +/// \param [in,out] tree The tree to initialize. +static void +init_tree(config::tree& tree) +{ + tree.define< config::strings_set_node >("allowed_architectures"); + tree.define< config::strings_set_node >("allowed_platforms"); + tree.define_dynamic("custom"); + tree.define< config::string_node >("description"); + tree.define< config::bool_node >("has_cleanup"); + tree.define< config::bool_node >("is_exclusive"); + tree.define< config::strings_set_node >("required_configs"); + tree.define< bytes_node >("required_disk_space"); + tree.define< paths_set_node >("required_files"); + tree.define< bytes_node >("required_memory"); + tree.define< paths_set_node >("required_programs"); + tree.define< user_node >("required_user"); + tree.define< delta_node >("timeout"); +} + + +/// Sets default values on a tree object. +/// +/// \param [in,out] tree The tree to configure. +static void +set_defaults(config::tree& tree) +{ + tree.set< config::strings_set_node >("allowed_architectures", + model::strings_set()); + tree.set< config::strings_set_node >("allowed_platforms", + model::strings_set()); + tree.set< config::string_node >("description", ""); + tree.set< config::bool_node >("has_cleanup", false); + tree.set< config::bool_node >("is_exclusive", false); + tree.set< config::strings_set_node >("required_configs", + model::strings_set()); + tree.set< bytes_node >("required_disk_space", units::bytes(0)); + tree.set< paths_set_node >("required_files", model::paths_set()); + tree.set< bytes_node >("required_memory", units::bytes(0)); + tree.set< paths_set_node >("required_programs", model::paths_set()); + tree.set< user_node >("required_user", ""); + // TODO(jmmv): We shouldn't be setting a default timeout like this. See + // Issue 5 for details. + tree.set< delta_node >("timeout", datetime::delta(300, 0)); +} + + +/// Queries the global defaults tree object with lazy initialization. +/// +/// \return A metadata tree. This object is statically allocated so it is +/// acceptable to obtain references to it and its members. +const config::tree& +get_defaults(void) +{ + if (!defaults) { + config::tree props; + init_tree(props); + set_defaults(props); + defaults = props; + } + return defaults.get(); +} + + +/// Looks up a value in a tree with error rewriting. +/// +/// \tparam NodeType The type of the node. +/// \param tree The tree in which to insert the value. +/// \param key The key to set. +/// +/// \return A read-write reference to the value in the node. +/// +/// \throw model::error If the key is not known or if the value is not valid. +template< class NodeType > +typename NodeType::value_type& +lookup_rw(config::tree& tree, const std::string& key) +{ + try { + return tree.lookup_rw< NodeType >(key); + } catch (const config::unknown_key_error& e) { + throw model::error(F("Unknown metadata property %s") % key); + } catch (const config::value_error& e) { + throw model::error(F("Invalid value for metadata property %s: %s") % + key % e.what()); + } +} + + +/// Sets a value in a tree with error rewriting. +/// +/// \tparam NodeType The type of the node. +/// \param tree The tree in which to insert the value. +/// \param key The key to set. +/// \param value The value to set the node to. +/// +/// \throw model::error If the key is not known or if the value is not valid. +template< class NodeType > +void +set(config::tree& tree, const std::string& key, + const typename NodeType::value_type& value) +{ + try { + tree.set< NodeType >(key, value); + } catch (const config::unknown_key_error& e) { + throw model::error(F("Unknown metadata property %s") % key); + } catch (const config::value_error& e) { + throw model::error(F("Invalid value for metadata property %s: %s") % + key % e.what()); + } +} + + +} // anonymous namespace + + +/// Internal implementation of the metadata class. +struct model::metadata::impl : utils::noncopyable { + /// Metadata properties. + config::tree props; + + /// Constructor. + /// + /// \param props_ Metadata properties of the test. + impl(const utils::config::tree& props_) : + props(props_) + { + } + + /// Equality comparator. + /// + /// \param other The other object to compare this one to. + /// + /// \return True if this object and other are equal; false otherwise. + bool + operator==(const impl& other) const + { + return (get_defaults().combine(props) == + get_defaults().combine(other.props)); + } +}; + + +/// Constructor. +/// +/// \param props Metadata properties of the test. +model::metadata::metadata(const utils::config::tree& props) : + _pimpl(new impl(props)) +{ +} + + +/// Destructor. +model::metadata::~metadata(void) +{ +} + + +/// Applies a set of overrides to this metadata object. +/// +/// \param overrides The overrides to apply. Any values explicitly set in this +/// other object will override any possible values set in this object. +/// +/// \return A new metadata object with the combination. +model::metadata +model::metadata::apply_overrides(const metadata& overrides) const +{ + return metadata(_pimpl->props.combine(overrides._pimpl->props)); +} + + +/// Returns the architectures allowed by the test. +/// +/// \return Set of architectures, or empty if this does not apply. +const model::strings_set& +model::metadata::allowed_architectures(void) const +{ + if (_pimpl->props.is_set("allowed_architectures")) { + return _pimpl->props.lookup< config::strings_set_node >( + "allowed_architectures"); + } else { + return get_defaults().lookup< config::strings_set_node >( + "allowed_architectures"); + } +} + + +/// Returns the platforms allowed by the test. +/// +/// \return Set of platforms, or empty if this does not apply. +const model::strings_set& +model::metadata::allowed_platforms(void) const +{ + if (_pimpl->props.is_set("allowed_platforms")) { + return _pimpl->props.lookup< config::strings_set_node >( + "allowed_platforms"); + } else { + return get_defaults().lookup< config::strings_set_node >( + "allowed_platforms"); + } +} + + +/// Returns all the user-defined metadata properties. +/// +/// \return A key/value map of properties. +model::properties_map +model::metadata::custom(void) const +{ + return _pimpl->props.all_properties("custom", true); +} + + +/// Returns the description of the test. +/// +/// \return Textual description; may be empty. +const std::string& +model::metadata::description(void) const +{ + if (_pimpl->props.is_set("description")) { + return _pimpl->props.lookup< config::string_node >("description"); + } else { + return get_defaults().lookup< config::string_node >("description"); + } +} + + +/// Returns whether the test has a cleanup part or not. +/// +/// \return True if there is a cleanup part; false otherwise. +bool +model::metadata::has_cleanup(void) const +{ + if (_pimpl->props.is_set("has_cleanup")) { + return _pimpl->props.lookup< config::bool_node >("has_cleanup"); + } else { + return get_defaults().lookup< config::bool_node >("has_cleanup"); + } +} + + +/// Returns whether the test is exclusive or not. +/// +/// \return True if the test has to be run on its own, not concurrently with any +/// other tests; false otherwise. +bool +model::metadata::is_exclusive(void) const +{ + if (_pimpl->props.is_set("is_exclusive")) { + return _pimpl->props.lookup< config::bool_node >("is_exclusive"); + } else { + return get_defaults().lookup< config::bool_node >("is_exclusive"); + } +} + + +/// Returns the list of configuration variables needed by the test. +/// +/// \return Set of configuration variables. +const model::strings_set& +model::metadata::required_configs(void) const +{ + if (_pimpl->props.is_set("required_configs")) { + return _pimpl->props.lookup< config::strings_set_node >( + "required_configs"); + } else { + return get_defaults().lookup< config::strings_set_node >( + "required_configs"); + } +} + + +/// Returns the amount of free disk space required by the test. +/// +/// \return Number of bytes, or 0 if this does not apply. +const units::bytes& +model::metadata::required_disk_space(void) const +{ + if (_pimpl->props.is_set("required_disk_space")) { + return _pimpl->props.lookup< bytes_node >("required_disk_space"); + } else { + return get_defaults().lookup< bytes_node >("required_disk_space"); + } +} + + +/// Returns the list of files needed by the test. +/// +/// \return Set of paths. +const model::paths_set& +model::metadata::required_files(void) const +{ + if (_pimpl->props.is_set("required_files")) { + return _pimpl->props.lookup< paths_set_node >("required_files"); + } else { + return get_defaults().lookup< paths_set_node >("required_files"); + } +} + + +/// Returns the amount of memory required by the test. +/// +/// \return Number of bytes, or 0 if this does not apply. +const units::bytes& +model::metadata::required_memory(void) const +{ + if (_pimpl->props.is_set("required_memory")) { + return _pimpl->props.lookup< bytes_node >("required_memory"); + } else { + return get_defaults().lookup< bytes_node >("required_memory"); + } +} + + +/// Returns the list of programs needed by the test. +/// +/// \return Set of paths. +const model::paths_set& +model::metadata::required_programs(void) const +{ + if (_pimpl->props.is_set("required_programs")) { + return _pimpl->props.lookup< paths_set_node >("required_programs"); + } else { + return get_defaults().lookup< paths_set_node >("required_programs"); + } +} + + +/// Returns the user required by the test. +/// +/// \return One of unprivileged, root or empty. +const std::string& +model::metadata::required_user(void) const +{ + if (_pimpl->props.is_set("required_user")) { + return _pimpl->props.lookup< user_node >("required_user"); + } else { + return get_defaults().lookup< user_node >("required_user"); + } +} + + +/// Returns the timeout of the test. +/// +/// \return A time delta; should be compared to default_timeout to see if it has +/// been overriden. +const datetime::delta& +model::metadata::timeout(void) const +{ + if (_pimpl->props.is_set("timeout")) { + return _pimpl->props.lookup< delta_node >("timeout"); + } else { + return get_defaults().lookup< delta_node >("timeout"); + } +} + + +/// Externalizes the metadata to a set of key/value textual pairs. +/// +/// \return A key/value representation of the metadata. +model::properties_map +model::metadata::to_properties(void) const +{ + const config::tree fully_specified = get_defaults().combine(_pimpl->props); + return fully_specified.all_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 +model::metadata::operator==(const metadata& other) const +{ + return _pimpl == other._pimpl || *_pimpl == *other._pimpl; +} + + +/// Inequality comparator. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object and other are different; false otherwise. +bool +model::metadata::operator!=(const metadata& other) const +{ + return !(*this == other); +} + + +/// Injects the object into a stream. +/// +/// \param output The stream into which to inject the object. +/// \param object The object to format. +/// +/// \return The output stream. +std::ostream& +model::operator<<(std::ostream& output, const metadata& object) +{ + output << "metadata{"; + + bool first = true; + const model::properties_map props = object.to_properties(); + for (model::properties_map::const_iterator iter = props.begin(); + iter != props.end(); ++iter) { + if (!first) + output << ", "; + output << F("%s=%s") % (*iter).first % + text::quote((*iter).second, '\''); + first = false; + } + + output << "}"; + return output; +} + + +/// Internal implementation of the metadata_builder class. +struct model::metadata_builder::impl : utils::noncopyable { + /// Collection of requirements. + config::tree props; + + /// Whether we have created a metadata object or not. + bool built; + + /// Constructor. + impl(void) : + built(false) + { + init_tree(props); + } + + /// Constructor. + /// + /// \param base The base model to construct a copy from. + impl(const model::metadata& base) : + props(base._pimpl->props.deep_copy()), + built(false) + { + } +}; + + +/// Constructor. +model::metadata_builder::metadata_builder(void) : + _pimpl(new impl()) +{ +} + + +/// Constructor. +model::metadata_builder::metadata_builder(const model::metadata& base) : + _pimpl(new impl(base)) +{ +} + + +/// Destructor. +model::metadata_builder::~metadata_builder(void) +{ +} + + +/// Accumulates an additional allowed architecture. +/// +/// \param arch The architecture. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::add_allowed_architecture(const std::string& arch) +{ + if (!_pimpl->props.is_set("allowed_architectures")) { + _pimpl->props.set< config::strings_set_node >( + "allowed_architectures", + get_defaults().lookup< config::strings_set_node >( + "allowed_architectures")); + } + lookup_rw< config::strings_set_node >( + _pimpl->props, "allowed_architectures").insert(arch); + return *this; +} + + +/// Accumulates an additional allowed platform. +/// +/// \param platform The platform. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::add_allowed_platform(const std::string& platform) +{ + if (!_pimpl->props.is_set("allowed_platforms")) { + _pimpl->props.set< config::strings_set_node >( + "allowed_platforms", + get_defaults().lookup< config::strings_set_node >( + "allowed_platforms")); + } + lookup_rw< config::strings_set_node >( + _pimpl->props, "allowed_platforms").insert(platform); + return *this; +} + + +/// Accumulates a single user-defined property. +/// +/// \param key Name of the property to define. +/// \param value Value of the property. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::add_custom(const std::string& key, + const std::string& value) +{ + _pimpl->props.set_string(F("custom.%s") % key, value); + return *this; +} + + +/// Accumulates an additional required configuration variable. +/// +/// \param var The name of the configuration variable. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::add_required_config(const std::string& var) +{ + if (!_pimpl->props.is_set("required_configs")) { + _pimpl->props.set< config::strings_set_node >( + "required_configs", + get_defaults().lookup< config::strings_set_node >( + "required_configs")); + } + lookup_rw< config::strings_set_node >( + _pimpl->props, "required_configs").insert(var); + return *this; +} + + +/// Accumulates an additional required file. +/// +/// \param path The path to the file. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::add_required_file(const fs::path& path) +{ + if (!_pimpl->props.is_set("required_files")) { + _pimpl->props.set< paths_set_node >( + "required_files", + get_defaults().lookup< paths_set_node >("required_files")); + } + lookup_rw< paths_set_node >(_pimpl->props, "required_files").insert(path); + return *this; +} + + +/// Accumulates an additional required program. +/// +/// \param path The path to the program. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::add_required_program(const fs::path& path) +{ + if (!_pimpl->props.is_set("required_programs")) { + _pimpl->props.set< paths_set_node >( + "required_programs", + get_defaults().lookup< paths_set_node >("required_programs")); + } + lookup_rw< paths_set_node >(_pimpl->props, + "required_programs").insert(path); + return *this; +} + + +/// Sets the architectures allowed by the test. +/// +/// \param as Set of architectures. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_allowed_architectures( + const model::strings_set& as) +{ + set< config::strings_set_node >(_pimpl->props, "allowed_architectures", as); + return *this; +} + + +/// Sets the platforms allowed by the test. +/// +/// \return ps Set of platforms. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_allowed_platforms(const model::strings_set& ps) +{ + set< config::strings_set_node >(_pimpl->props, "allowed_platforms", ps); + return *this; +} + + +/// Sets the user-defined properties. +/// +/// \param props The custom properties to set. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_custom(const model::properties_map& props) +{ + for (model::properties_map::const_iterator iter = props.begin(); + iter != props.end(); ++iter) + _pimpl->props.set_string(F("custom.%s") % (*iter).first, + (*iter).second); + return *this; +} + + +/// Sets the description of the test. +/// +/// \param description Textual description of the test. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_description(const std::string& description) +{ + set< config::string_node >(_pimpl->props, "description", description); + return *this; +} + + +/// Sets whether the test has a cleanup part or not. +/// +/// \param cleanup True if the test has a cleanup part; false otherwise. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_has_cleanup(const bool cleanup) +{ + set< config::bool_node >(_pimpl->props, "has_cleanup", cleanup); + return *this; +} + + +/// Sets whether the test is exclusive or not. +/// +/// \param exclusive True if the test is exclusive; false otherwise. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_is_exclusive(const bool exclusive) +{ + set< config::bool_node >(_pimpl->props, "is_exclusive", exclusive); + return *this; +} + + +/// Sets the list of configuration variables needed by the test. +/// +/// \param vars Set of configuration variables. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_required_configs(const model::strings_set& vars) +{ + set< config::strings_set_node >(_pimpl->props, "required_configs", vars); + return *this; +} + + +/// Sets the amount of free disk space required by the test. +/// +/// \param bytes Number of bytes. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_required_disk_space(const units::bytes& bytes) +{ + set< bytes_node >(_pimpl->props, "required_disk_space", bytes); + return *this; +} + + +/// Sets the list of files needed by the test. +/// +/// \param files Set of paths. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_required_files(const model::paths_set& files) +{ + set< paths_set_node >(_pimpl->props, "required_files", files); + return *this; +} + + +/// Sets the amount of memory required by the test. +/// +/// \param bytes Number of bytes. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_required_memory(const units::bytes& bytes) +{ + set< bytes_node >(_pimpl->props, "required_memory", bytes); + return *this; +} + + +/// Sets the list of programs needed by the test. +/// +/// \param progs Set of paths. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_required_programs(const model::paths_set& progs) +{ + set< paths_set_node >(_pimpl->props, "required_programs", progs); + return *this; +} + + +/// Sets the user required by the test. +/// +/// \param user One of unprivileged, root or empty. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_required_user(const std::string& user) +{ + set< user_node >(_pimpl->props, "required_user", user); + return *this; +} + + +/// Sets a metadata property by name from its textual representation. +/// +/// \param key The property to set. +/// \param value The value to set the property to. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid or the key does not exist. +model::metadata_builder& +model::metadata_builder::set_string(const std::string& key, + const std::string& value) +{ + try { + _pimpl->props.set_string(key, value); + } catch (const config::unknown_key_error& e) { + throw model::format_error(F("Unknown metadata property %s") % key); + } catch (const config::value_error& e) { + throw model::format_error( + F("Invalid value for metadata property %s: %s") % key % e.what()); + } + return *this; +} + + +/// Sets the timeout of the test. +/// +/// \param timeout The timeout to set. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_timeout(const datetime::delta& timeout) +{ + set< delta_node >(_pimpl->props, "timeout", timeout); + return *this; +} + + +/// Creates a new metadata object. +/// +/// \pre This has not yet been called. We only support calling this function +/// once due to the way the internal tree works: we pass around references, not +/// deep copies, so if we allowed a second build, we'd encourage reusing the +/// same builder to construct different metadata objects, and this could have +/// unintended consequences. +/// +/// \return The constructed metadata object. +model::metadata +model::metadata_builder::build(void) const +{ + PRE(!_pimpl->built); + _pimpl->built = true; + + return metadata(_pimpl->props); +} diff --git a/model/metadata.hpp b/model/metadata.hpp new file mode 100644 index 000000000000..c7dd4519f122 --- /dev/null +++ b/model/metadata.hpp @@ -0,0 +1,130 @@ +// 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 model/metadata.hpp +/// Definition of the "test metadata" concept. + +#if !defined(MODEL_METADATA_HPP) +#define MODEL_METADATA_HPP + +#include "model/metadata_fwd.hpp" + +#include <memory> +#include <ostream> +#include <string> + +#include "model/types.hpp" +#include "utils/config/tree_fwd.hpp" +#include "utils/datetime_fwd.hpp" +#include "utils/fs/path_fwd.hpp" +#include "utils/noncopyable.hpp" +#include "utils/units_fwd.hpp" + +namespace model { + + +/// Collection of metadata properties of a test. +class metadata { + struct impl; + + /// Pointer to the shared internal implementation. + std::shared_ptr< impl > _pimpl; + + friend class metadata_builder; + +public: + metadata(const utils::config::tree&); + ~metadata(void); + + metadata apply_overrides(const metadata&) const; + + const strings_set& allowed_architectures(void) const; + const strings_set& allowed_platforms(void) const; + model::properties_map custom(void) const; + const std::string& description(void) const; + bool has_cleanup(void) const; + bool is_exclusive(void) const; + const strings_set& required_configs(void) const; + const utils::units::bytes& required_disk_space(void) const; + const paths_set& required_files(void) const; + const utils::units::bytes& required_memory(void) const; + const paths_set& required_programs(void) const; + const std::string& required_user(void) const; + const utils::datetime::delta& timeout(void) const; + + model::properties_map to_properties(void) const; + + bool operator==(const metadata&) const; + bool operator!=(const metadata&) const; +}; + + +std::ostream& operator<<(std::ostream&, const metadata&); + + +/// Builder for a metadata object. +class metadata_builder : utils::noncopyable { + struct impl; + + /// Pointer to the shared internal implementation. + std::auto_ptr< impl > _pimpl; + +public: + metadata_builder(void); + explicit metadata_builder(const metadata&); + ~metadata_builder(void); + + metadata_builder& add_allowed_architecture(const std::string&); + metadata_builder& add_allowed_platform(const std::string&); + metadata_builder& add_custom(const std::string&, const std::string&); + metadata_builder& add_required_config(const std::string&); + metadata_builder& add_required_file(const utils::fs::path&); + metadata_builder& add_required_program(const utils::fs::path&); + + metadata_builder& set_allowed_architectures(const strings_set&); + metadata_builder& set_allowed_platforms(const strings_set&); + metadata_builder& set_custom(const model::properties_map&); + metadata_builder& set_description(const std::string&); + metadata_builder& set_has_cleanup(const bool); + metadata_builder& set_is_exclusive(const bool); + metadata_builder& set_required_configs(const strings_set&); + metadata_builder& set_required_disk_space(const utils::units::bytes&); + metadata_builder& set_required_files(const paths_set&); + metadata_builder& set_required_memory(const utils::units::bytes&); + metadata_builder& set_required_programs(const paths_set&); + metadata_builder& set_required_user(const std::string&); + metadata_builder& set_string(const std::string&, const std::string&); + metadata_builder& set_timeout(const utils::datetime::delta&); + + metadata build(void) const; +}; + + +} // namespace model + +#endif // !defined(MODEL_METADATA_HPP) diff --git a/model/metadata_fwd.hpp b/model/metadata_fwd.hpp new file mode 100644 index 000000000000..8a8c5c09d77b --- /dev/null +++ b/model/metadata_fwd.hpp @@ -0,0 +1,44 @@ +// Copyright 2014 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 model/metadata_fwd.hpp +/// Forward declarations for model/metadata.hpp + +#if !defined(MODEL_METADATA_FWD_HPP) +#define MODEL_METADATA_FWD_HPP + +namespace model { + + +class metadata; +class metadata_builder; + + +} // namespace model + +#endif // !defined(MODEL_METADATA_FWD_HPP) diff --git a/model/metadata_test.cpp b/model/metadata_test.cpp new file mode 100644 index 000000000000..7b22653ec1a2 --- /dev/null +++ b/model/metadata_test.cpp @@ -0,0 +1,461 @@ +// 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 "model/metadata.hpp" + +#include <sstream> + +#include <atf-c++.hpp> + +#include "model/types.hpp" +#include "utils/datetime.hpp" +#include "utils/format/containers.ipp" +#include "utils/fs/path.hpp" +#include "utils/units.hpp" + +namespace datetime = utils::datetime; +namespace fs = utils::fs; +namespace units = utils::units; + + +ATF_TEST_CASE_WITHOUT_HEAD(defaults); +ATF_TEST_CASE_BODY(defaults) +{ + const model::metadata md = model::metadata_builder().build(); + ATF_REQUIRE(md.allowed_architectures().empty()); + ATF_REQUIRE(md.allowed_platforms().empty()); + ATF_REQUIRE(md.allowed_platforms().empty()); + ATF_REQUIRE(md.custom().empty()); + ATF_REQUIRE(md.description().empty()); + ATF_REQUIRE(!md.has_cleanup()); + ATF_REQUIRE(!md.is_exclusive()); + ATF_REQUIRE(md.required_configs().empty()); + ATF_REQUIRE_EQ(units::bytes(0), md.required_disk_space()); + ATF_REQUIRE(md.required_files().empty()); + ATF_REQUIRE_EQ(units::bytes(0), md.required_memory()); + ATF_REQUIRE(md.required_programs().empty()); + ATF_REQUIRE(md.required_user().empty()); + ATF_REQUIRE(datetime::delta(300, 0) == md.timeout()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(add); +ATF_TEST_CASE_BODY(add) +{ + model::strings_set architectures; + architectures.insert("1-architecture"); + architectures.insert("2-architecture"); + + model::strings_set platforms; + platforms.insert("1-platform"); + platforms.insert("2-platform"); + + model::properties_map custom; + custom["1-custom"] = "first"; + custom["2-custom"] = "second"; + + model::strings_set configs; + configs.insert("1-config"); + configs.insert("2-config"); + + model::paths_set files; + files.insert(fs::path("1-file")); + files.insert(fs::path("2-file")); + + model::paths_set programs; + programs.insert(fs::path("1-program")); + programs.insert(fs::path("2-program")); + + const model::metadata md = model::metadata_builder() + .add_allowed_architecture("1-architecture") + .add_allowed_platform("1-platform") + .add_custom("1-custom", "first") + .add_custom("2-custom", "second") + .add_required_config("1-config") + .add_required_file(fs::path("1-file")) + .add_required_program(fs::path("1-program")) + .add_allowed_architecture("2-architecture") + .add_allowed_platform("2-platform") + .add_required_config("2-config") + .add_required_file(fs::path("2-file")) + .add_required_program(fs::path("2-program")) + .build(); + + ATF_REQUIRE(architectures == md.allowed_architectures()); + ATF_REQUIRE(platforms == md.allowed_platforms()); + ATF_REQUIRE(custom == md.custom()); + ATF_REQUIRE(configs == md.required_configs()); + ATF_REQUIRE(files == md.required_files()); + ATF_REQUIRE(programs == md.required_programs()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(copy); +ATF_TEST_CASE_BODY(copy) +{ + const model::metadata md1 = model::metadata_builder() + .add_allowed_architecture("1-architecture") + .add_allowed_platform("1-platform") + .build(); + + const model::metadata md2 = model::metadata_builder(md1) + .add_allowed_architecture("2-architecture") + .build(); + + ATF_REQUIRE_EQ(1, md1.allowed_architectures().size()); + ATF_REQUIRE_EQ(2, md2.allowed_architectures().size()); + ATF_REQUIRE_EQ(1, md1.allowed_platforms().size()); + ATF_REQUIRE_EQ(1, md2.allowed_platforms().size()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(apply_overrides); +ATF_TEST_CASE_BODY(apply_overrides) +{ + const model::metadata md1 = model::metadata_builder() + .add_allowed_architecture("1-architecture") + .add_allowed_platform("1-platform") + .set_description("Explicit description") + .build(); + + const model::metadata md2 = model::metadata_builder() + .add_allowed_architecture("2-architecture") + .set_description("") + .set_timeout(datetime::delta(500, 0)) + .build(); + + const model::metadata merge_1_2 = model::metadata_builder() + .add_allowed_architecture("2-architecture") + .add_allowed_platform("1-platform") + .set_description("") + .set_timeout(datetime::delta(500, 0)) + .build(); + ATF_REQUIRE_EQ(merge_1_2, md1.apply_overrides(md2)); + + const model::metadata merge_2_1 = model::metadata_builder() + .add_allowed_architecture("1-architecture") + .add_allowed_platform("1-platform") + .set_description("Explicit description") + .set_timeout(datetime::delta(500, 0)) + .build(); + ATF_REQUIRE_EQ(merge_2_1, md2.apply_overrides(md1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(override_all_with_setters); +ATF_TEST_CASE_BODY(override_all_with_setters) +{ + model::strings_set architectures; + architectures.insert("the-architecture"); + + model::strings_set platforms; + platforms.insert("the-platforms"); + + model::properties_map custom; + custom["first"] = "hello"; + custom["second"] = "bye"; + + const std::string description = "Some long text"; + + model::strings_set configs; + configs.insert("the-configs"); + + model::paths_set files; + files.insert(fs::path("the-files")); + + const units::bytes disk_space(6789); + + const units::bytes memory(12345); + + model::paths_set programs; + programs.insert(fs::path("the-programs")); + + const std::string user = "root"; + + const datetime::delta timeout(123, 0); + + const model::metadata md = model::metadata_builder() + .set_allowed_architectures(architectures) + .set_allowed_platforms(platforms) + .set_custom(custom) + .set_description(description) + .set_has_cleanup(true) + .set_is_exclusive(true) + .set_required_configs(configs) + .set_required_disk_space(disk_space) + .set_required_files(files) + .set_required_memory(memory) + .set_required_programs(programs) + .set_required_user(user) + .set_timeout(timeout) + .build(); + + ATF_REQUIRE(architectures == md.allowed_architectures()); + ATF_REQUIRE(platforms == md.allowed_platforms()); + ATF_REQUIRE(custom == md.custom()); + ATF_REQUIRE_EQ(description, md.description()); + ATF_REQUIRE(md.has_cleanup()); + ATF_REQUIRE(md.is_exclusive()); + ATF_REQUIRE(configs == md.required_configs()); + ATF_REQUIRE_EQ(disk_space, md.required_disk_space()); + ATF_REQUIRE(files == md.required_files()); + ATF_REQUIRE_EQ(memory, md.required_memory()); + ATF_REQUIRE(programs == md.required_programs()); + ATF_REQUIRE_EQ(user, md.required_user()); + ATF_REQUIRE(timeout == md.timeout()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(override_all_with_set_string); +ATF_TEST_CASE_BODY(override_all_with_set_string) +{ + model::strings_set architectures; + architectures.insert("a1"); + architectures.insert("a2"); + + model::strings_set platforms; + platforms.insert("p1"); + platforms.insert("p2"); + + model::properties_map custom; + custom["user-defined"] = "the-value"; + + const std::string description = "Another long text"; + + model::strings_set configs; + configs.insert("config-var"); + + model::paths_set files; + files.insert(fs::path("plain")); + files.insert(fs::path("/absolute/path")); + + const units::bytes disk_space( + static_cast< uint64_t >(16) * 1024 * 1024 * 1024); + + const units::bytes memory(1024 * 1024); + + model::paths_set programs; + programs.insert(fs::path("program")); + programs.insert(fs::path("/absolute/prog")); + + const std::string user = "unprivileged"; + + const datetime::delta timeout(45, 0); + + const model::metadata md = model::metadata_builder() + .set_string("allowed_architectures", "a1 a2") + .set_string("allowed_platforms", "p1 p2") + .set_string("custom.user-defined", "the-value") + .set_string("description", "Another long text") + .set_string("has_cleanup", "true") + .set_string("is_exclusive", "true") + .set_string("required_configs", "config-var") + .set_string("required_disk_space", "16G") + .set_string("required_files", "plain /absolute/path") + .set_string("required_memory", "1M") + .set_string("required_programs", "program /absolute/prog") + .set_string("required_user", "unprivileged") + .set_string("timeout", "45") + .build(); + + ATF_REQUIRE(architectures == md.allowed_architectures()); + ATF_REQUIRE(platforms == md.allowed_platforms()); + ATF_REQUIRE(custom == md.custom()); + ATF_REQUIRE_EQ(description, md.description()); + ATF_REQUIRE(md.has_cleanup()); + ATF_REQUIRE(md.is_exclusive()); + ATF_REQUIRE(configs == md.required_configs()); + ATF_REQUIRE_EQ(disk_space, md.required_disk_space()); + ATF_REQUIRE(files == md.required_files()); + ATF_REQUIRE_EQ(memory, md.required_memory()); + ATF_REQUIRE(programs == md.required_programs()); + ATF_REQUIRE_EQ(user, md.required_user()); + ATF_REQUIRE(timeout == md.timeout()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(to_properties); +ATF_TEST_CASE_BODY(to_properties) +{ + const model::metadata md = model::metadata_builder() + .add_allowed_architecture("abc") + .add_required_file(fs::path("foo")) + .add_required_file(fs::path("bar")) + .set_required_memory(units::bytes(1024)) + .add_custom("foo", "bar") + .build(); + + model::properties_map props; + props["allowed_architectures"] = "abc"; + props["allowed_platforms"] = ""; + props["custom.foo"] = "bar"; + props["description"] = ""; + props["has_cleanup"] = "false"; + props["is_exclusive"] = "false"; + props["required_configs"] = ""; + props["required_disk_space"] = "0"; + props["required_files"] = "bar foo"; + props["required_memory"] = "1.00K"; + props["required_programs"] = ""; + props["required_user"] = ""; + props["timeout"] = "300"; + ATF_REQUIRE_EQ(props, md.to_properties()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__empty); +ATF_TEST_CASE_BODY(operators_eq_and_ne__empty) +{ + const model::metadata md1 = model::metadata_builder().build(); + const model::metadata md2 = model::metadata_builder().build(); + ATF_REQUIRE( md1 == md2); + ATF_REQUIRE(!(md1 != md2)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__copy); +ATF_TEST_CASE_BODY(operators_eq_and_ne__copy) +{ + const model::metadata md1 = model::metadata_builder() + .add_custom("foo", "bar") + .build(); + const model::metadata md2 = md1; + ATF_REQUIRE( md1 == md2); + ATF_REQUIRE(!(md1 != md2)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__equal); +ATF_TEST_CASE_BODY(operators_eq_and_ne__equal) +{ + const model::metadata md1 = model::metadata_builder() + .add_allowed_architecture("a") + .add_allowed_architecture("b") + .add_custom("foo", "bar") + .build(); + const model::metadata md2 = model::metadata_builder() + .add_allowed_architecture("b") + .add_allowed_architecture("a") + .add_custom("foo", "bar") + .build(); + ATF_REQUIRE( md1 == md2); + ATF_REQUIRE(!(md1 != md2)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__equal_overriden_defaults); +ATF_TEST_CASE_BODY(operators_eq_and_ne__equal_overriden_defaults) +{ + const model::metadata defaults = model::metadata_builder().build(); + + const model::metadata md1 = model::metadata_builder() + .add_allowed_architecture("a") + .build(); + const model::metadata md2 = model::metadata_builder() + .add_allowed_architecture("a") + .set_timeout(defaults.timeout()) + .build(); + ATF_REQUIRE( md1 == md2); + ATF_REQUIRE(!(md1 != md2)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__different); +ATF_TEST_CASE_BODY(operators_eq_and_ne__different) +{ + const model::metadata md1 = model::metadata_builder() + .add_custom("foo", "bar") + .build(); + const model::metadata md2 = model::metadata_builder() + .add_custom("foo", "bar") + .add_custom("baz", "foo bar") + .build(); + ATF_REQUIRE(!(md1 == md2)); + ATF_REQUIRE( md1 != md2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(output__defaults); +ATF_TEST_CASE_BODY(output__defaults) +{ + std::ostringstream str; + str << model::metadata_builder().build(); + ATF_REQUIRE_EQ("metadata{allowed_architectures='', allowed_platforms='', " + "description='', has_cleanup='false', is_exclusive='false', " + "required_configs='', " + "required_disk_space='0', required_files='', " + "required_memory='0', " + "required_programs='', required_user='', timeout='300'}", + str.str()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(output__some_values); +ATF_TEST_CASE_BODY(output__some_values) +{ + std::ostringstream str; + str << model::metadata_builder() + .add_allowed_architecture("abc") + .add_required_file(fs::path("foo")) + .add_required_file(fs::path("bar")) + .set_is_exclusive(true) + .set_required_memory(units::bytes(1024)) + .build(); + ATF_REQUIRE_EQ( + "metadata{allowed_architectures='abc', allowed_platforms='', " + "description='', has_cleanup='false', is_exclusive='true', " + "required_configs='', " + "required_disk_space='0', required_files='bar foo', " + "required_memory='1.00K', " + "required_programs='', required_user='', timeout='300'}", + str.str()); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, defaults); + ATF_ADD_TEST_CASE(tcs, add); + ATF_ADD_TEST_CASE(tcs, copy); + ATF_ADD_TEST_CASE(tcs, apply_overrides); + ATF_ADD_TEST_CASE(tcs, override_all_with_setters); + ATF_ADD_TEST_CASE(tcs, override_all_with_set_string); + ATF_ADD_TEST_CASE(tcs, to_properties); + + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__empty); + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__copy); + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__equal); + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__equal_overriden_defaults); + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__different); + + ATF_ADD_TEST_CASE(tcs, output__defaults); + ATF_ADD_TEST_CASE(tcs, output__some_values); + + // TODO(jmmv): Add tests for error conditions (invalid keys and invalid + // values). +} diff --git a/model/test_case.cpp b/model/test_case.cpp new file mode 100644 index 000000000000..f5f6a979eed3 --- /dev/null +++ b/model/test_case.cpp @@ -0,0 +1,339 @@ +// Copyright 2010 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 "model/test_case.hpp" + +#include "model/metadata.hpp" +#include "model/test_result.hpp" +#include "utils/format/macros.hpp" +#include "utils/noncopyable.hpp" +#include "utils/optional.ipp" +#include "utils/text/operations.ipp" + +namespace text = utils::text; + +using utils::none; +using utils::optional; + + +/// Internal implementation for a test_case. +struct model::test_case::impl : utils::noncopyable { + /// Name of the test case; must be unique within the test program. + std::string name; + + /// Metadata of the container test program. + /// + /// Yes, this is a pointer. Yes, we do not own the object pointed to. + /// However, because this is only intended to point to the metadata object + /// of test programs _containing_ this test case, we can assume that the + /// referenced object will be alive for the lifetime of this test case. + const model::metadata* md_defaults; + + /// Test case metadata. + model::metadata md; + + /// Fake result to return instead of running the test case. + optional< model::test_result > fake_result; + + /// Constructor. + /// + /// \param name_ The name of the test case within the test program. + /// \param md_defaults_ Metadata of the container test program. + /// \param md_ Metadata of the test case. + /// \param fake_result_ Fake result to return instead of running the test + /// case. + impl(const std::string& name_, + const model::metadata* md_defaults_, + const model::metadata& md_, + const optional< model::test_result >& fake_result_) : + name(name_), + md_defaults(md_defaults_), + md(md_), + fake_result(fake_result_) + { + } + + /// Gets the test case metadata. + /// + /// This combines the test case's metadata with any possible test program + /// metadata, using the latter as defaults. + /// + /// \return The test case metadata. + model::metadata + get_metadata(void) const + { + if (md_defaults != NULL) { + return md_defaults->apply_overrides(md); + } else { + return md; + } + } + + /// Equality comparator. + /// + /// \param other The other object to compare this one to. + /// + /// \return True if this object and other are equal; false otherwise. + bool + operator==(const impl& other) const + { + return (name == other.name && + get_metadata() == other.get_metadata() && + fake_result == other.fake_result); + } +}; + + +/// Constructs a new test case from an already-built impl oject. +/// +/// \param pimpl_ The internal representation of the test case. +model::test_case::test_case(std::shared_ptr< impl > pimpl_) : + _pimpl(pimpl_) +{ +} + + +/// Constructs a new test case. +/// +/// \param name_ The name of the test case within the test program. +/// \param md_ Metadata of the test case. +model::test_case::test_case(const std::string& name_, + const model::metadata& md_) : + _pimpl(new impl(name_, NULL, md_, none)) +{ +} + + + +/// Constructs a new fake test case. +/// +/// A fake test case is a test case that is not really defined by the test +/// program. Such test cases have a name surrounded by '__' and, when executed, +/// they return a fixed, pre-recorded result. +/// +/// This is necessary for the cases where listing the test cases of a test +/// program fails. In this scenario, we generate a single test case within +/// the test program that unconditionally returns a failure. +/// +/// TODO(jmmv): Need to get rid of this. We should be able to report the +/// status of test programs independently of test cases, as some interfaces +/// don't know about the latter at all. +/// +/// \param name_ The name to give to this fake test case. This name has to be +/// prefixed and suffixed by '__' to clearly denote that this is internal. +/// \param description_ The description of the test case, if any. +/// \param test_result_ The fake result to return when this test case is run. +model::test_case::test_case( + const std::string& name_, + const std::string& description_, + const model::test_result& test_result_) : + _pimpl(new impl( + name_, + NULL, + model::metadata_builder().set_description(description_).build(), + utils::make_optional(test_result_))) +{ + PRE_MSG(name_.length() > 4 && name_.substr(0, 2) == "__" && + name_.substr(name_.length() - 2) == "__", + "Invalid fake name provided to fake test case"); +} + + +/// Destroys a test case. +model::test_case::~test_case(void) +{ +} + + +/// Constructs a new test case applying metadata defaults. +/// +/// This method is intended to be used by the container test program when +/// ownership of the test is given to it. At that point, the test case receives +/// the default metadata properties of the test program, not the global +/// defaults. +/// +/// \param defaults The metadata properties to use as defaults. The provided +/// object's lifetime MUST extend the lifetime of the test case. Because +/// this is only intended to point at the metadata of the test program +/// containing this test case, this assumption should hold. +/// +/// \return A new test case. +model::test_case +model::test_case::apply_metadata_defaults(const metadata* defaults) const +{ + return test_case(std::shared_ptr< impl >(new impl( + _pimpl->name, + defaults, + _pimpl->md, + _pimpl->fake_result))); +} + + +/// Gets the test case name. +/// +/// \return The test case name, relative to the test program. +const std::string& +model::test_case::name(void) const +{ + return _pimpl->name; +} + + +/// Gets the test case metadata. +/// +/// This combines the test case's metadata with any possible test program +/// metadata, using the latter as defaults. You should use this method in +/// generaland not get_raw_metadata(). +/// +/// \return The test case metadata. +model::metadata +model::test_case::get_metadata(void) const +{ + return _pimpl->get_metadata(); +} + + +/// Gets the original test case metadata without test program overrides. +/// +/// This method should be used for storage purposes as serialized test cases +/// should record exactly whatever the test case reported and not what the test +/// program may have provided. The final values will be reconstructed at load +/// time. +/// +/// \return The test case metadata. +const model::metadata& +model::test_case::get_raw_metadata(void) const +{ + return _pimpl->md; +} + + +/// Gets the fake result pre-stored for this test case. +/// +/// \return A fake result, or none if not defined. +optional< model::test_result > +model::test_case::fake_result(void) const +{ + return _pimpl->fake_result; +} + + +/// Equality comparator. +/// +/// \warning Because test cases reference their container test programs, and +/// test programs include test cases, we cannot perform a full comparison here: +/// otherwise, we'd enter an inifinte loop. Therefore, out of necessity, this +/// does NOT compare whether the container test programs of the affected test +/// cases are the same. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object and other are equal; false otherwise. +bool +model::test_case::operator==(const test_case& other) const +{ + return _pimpl == other._pimpl || *_pimpl == *other._pimpl; +} + + +/// Inequality comparator. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object and other are different; false otherwise. +bool +model::test_case::operator!=(const test_case& other) const +{ + return !(*this == other); +} + + +/// Injects the object into a stream. +/// +/// \param output The stream into which to inject the object. +/// \param object The object to format. +/// +/// \return The output stream. +std::ostream& +model::operator<<(std::ostream& output, const test_case& object) +{ + output << F("test_case{name=%s, metadata=%s}") + % text::quote(object.name(), '\'') + % object.get_metadata(); + return output; +} + + +/// Adds an already-constructed test case. +/// +/// \param test_case The test case to add. +/// +/// \return A reference to this builder. +model::test_cases_map_builder& +model::test_cases_map_builder::add(const test_case& test_case) +{ + _test_cases.insert( + test_cases_map::value_type(test_case.name(), test_case)); + return *this; +} + + +/// Constructs and adds a new test case with default metadata. +/// +/// \param test_case_name The name of the test case to add. +/// +/// \return A reference to this builder. +model::test_cases_map_builder& +model::test_cases_map_builder::add(const std::string& test_case_name) +{ + return add(test_case(test_case_name, metadata_builder().build())); +} + + +/// Constructs and adds a new test case with explicit metadata. +/// +/// \param test_case_name The name of the test case to add. +/// \param metadata The metadata of the test case. +/// +/// \return A reference to this builder. +model::test_cases_map_builder& +model::test_cases_map_builder::add(const std::string& test_case_name, + const metadata& metadata) +{ + return add(test_case(test_case_name, metadata)); +} + + +/// Creates a new test_cases_map. +/// +/// \return The constructed test_cases_map. +model::test_cases_map +model::test_cases_map_builder::build(void) const +{ + return _test_cases; +} diff --git a/model/test_case.hpp b/model/test_case.hpp new file mode 100644 index 000000000000..3c6fe32c8e62 --- /dev/null +++ b/model/test_case.hpp @@ -0,0 +1,98 @@ +// Copyright 2010 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 model/test_case.hpp +/// Definition of the "test case" concept. + +#if !defined(MODEL_TEST_CASE_HPP) +#define MODEL_TEST_CASE_HPP + +#include "model/test_case_fwd.hpp" + +#include <memory> +#include <ostream> +#include <string> + +#include "model/metadata_fwd.hpp" +#include "model/test_result_fwd.hpp" +#include "utils/noncopyable.hpp" +#include "utils/optional_fwd.hpp" + +namespace model { + + +/// Representation of a test case. +/// +/// Test cases, on their own, are useless. They only make sense in the context +/// of the container test program and as such this class should not be used +/// directly. +class test_case { + struct impl; + + /// Pointer to the shared internal implementation. + std::shared_ptr< impl > _pimpl; + + test_case(std::shared_ptr< impl >); + +public: + test_case(const std::string&, const metadata&); + test_case(const std::string&, const std::string&, const test_result&); + ~test_case(void); + + test_case apply_metadata_defaults(const metadata*) const; + + const std::string& name(void) const; + metadata get_metadata(void) const; + const metadata& get_raw_metadata(void) const; + utils::optional< test_result > fake_result(void) const; + + bool operator==(const test_case&) const; + bool operator!=(const test_case&) const; +}; + + +/// Builder for a test_cases_map object. +class test_cases_map_builder : utils::noncopyable { + /// Accumulator for the map being built. + test_cases_map _test_cases; + +public: + test_cases_map_builder& add(const test_case&); + test_cases_map_builder& add(const std::string&); + test_cases_map_builder& add(const std::string&, const metadata&); + + test_cases_map build(void) const; +}; + + +std::ostream& operator<<(std::ostream&, const test_case&); + + +} // namespace model + +#endif // !defined(MODEL_TEST_CASE_HPP) diff --git a/model/test_case_fwd.hpp b/model/test_case_fwd.hpp new file mode 100644 index 000000000000..72a40e513083 --- /dev/null +++ b/model/test_case_fwd.hpp @@ -0,0 +1,51 @@ +// Copyright 2014 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 model/test_case_fwd.hpp +/// Forward declarations for model/test_case.hpp + +#if !defined(MODEL_TEST_CASE_FWD_HPP) +#define MODEL_TEST_CASE_FWD_HPP + +#include <map> +#include <string> + +namespace model { + + +class test_case; +class test_cases_map_builder; + + +/// Collection of test cases keyed by their name. +typedef std::map< std::string, model::test_case > test_cases_map; + + +} // namespace model + +#endif // !defined(MODEL_TEST_CASE_FWD_HPP) diff --git a/model/test_case_test.cpp b/model/test_case_test.cpp new file mode 100644 index 000000000000..1a55de0fab42 --- /dev/null +++ b/model/test_case_test.cpp @@ -0,0 +1,263 @@ +// Copyright 2010 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 "model/test_case.hpp" + +#include <sstream> + +#include <atf-c++.hpp> + +#include "model/metadata.hpp" +#include "model/test_result.hpp" +#include "utils/datetime.hpp" +#include "utils/format/containers.ipp" +#include "utils/fs/path.hpp" +#include "utils/optional.ipp" + +namespace datetime = utils::datetime; +namespace fs = utils::fs; + + +ATF_TEST_CASE_WITHOUT_HEAD(test_case__ctor_and_getters) +ATF_TEST_CASE_BODY(test_case__ctor_and_getters) +{ + const model::metadata md = model::metadata_builder() + .add_custom("first", "value") + .build(); + const model::test_case test_case("foo", md); + ATF_REQUIRE_EQ("foo", test_case.name()); + ATF_REQUIRE_EQ(md, test_case.get_metadata()); + ATF_REQUIRE_EQ(md, test_case.get_raw_metadata()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(test_case__fake_result) +ATF_TEST_CASE_BODY(test_case__fake_result) +{ + const model::test_result result(model::test_result_skipped, + "Some reason"); + const model::test_case test_case("__foo__", "Some description", result); + ATF_REQUIRE_EQ("__foo__", test_case.name()); + ATF_REQUIRE_EQ(result, test_case.fake_result().get()); + + const model::metadata exp_metadata = model::metadata_builder() + .set_description("Some description") + .build(); + ATF_REQUIRE_EQ(exp_metadata, test_case.get_metadata()); + ATF_REQUIRE_EQ(exp_metadata, test_case.get_raw_metadata()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(test_case__apply_metadata_overrides__real_test_case) +ATF_TEST_CASE_BODY(test_case__apply_metadata_overrides__real_test_case) +{ + const model::metadata overrides = model::metadata_builder() + .add_required_config("the-variable") + .set_description("The test case") + .build(); + const model::test_case base_test_case("foo", overrides); + + const model::metadata defaults = model::metadata_builder() + .set_description("Default description") + .set_timeout(datetime::delta(10, 0)) + .build(); + + const model::test_case test_case = base_test_case.apply_metadata_defaults( + &defaults); + + const model::metadata expected = model::metadata_builder() + .add_required_config("the-variable") + .set_description("The test case") + .set_timeout(datetime::delta(10, 0)) + .build(); + ATF_REQUIRE_EQ(expected, test_case.get_metadata()); + ATF_REQUIRE_EQ(overrides, test_case.get_raw_metadata()); + + // Ensure the original (although immutable) test case was not touched. + ATF_REQUIRE_EQ(overrides, base_test_case.get_metadata()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(test_case__apply_metadata_overrides__fake_test_case) +ATF_TEST_CASE_BODY(test_case__apply_metadata_overrides__fake_test_case) +{ + const model::test_result result(model::test_result_skipped, "Irrelevant"); + const model::test_case base_test_case("__foo__", "Fake test", result); + + const model::metadata overrides = model::metadata_builder() + .set_description("Fake test") + .build(); + + const model::metadata defaults = model::metadata_builder() + .add_allowed_platform("some-value") + .set_description("Default description") + .build(); + + const model::test_case test_case = base_test_case.apply_metadata_defaults( + &defaults); + + const model::metadata expected = model::metadata_builder() + .add_allowed_platform("some-value") + .set_description("Fake test") + .build(); + ATF_REQUIRE_EQ(expected, test_case.get_metadata()); + ATF_REQUIRE_EQ(overrides, test_case.get_raw_metadata()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(test_case__operators_eq_and_ne__copy); +ATF_TEST_CASE_BODY(test_case__operators_eq_and_ne__copy) +{ + const model::test_case tc1("name", model::metadata_builder().build()); + const model::test_case tc2 = tc1; + ATF_REQUIRE( tc1 == tc2); + ATF_REQUIRE(!(tc1 != tc2)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(test_case__operators_eq_and_ne__not_copy); +ATF_TEST_CASE_BODY(test_case__operators_eq_and_ne__not_copy) +{ + const std::string base_name("name"); + const model::metadata base_metadata = model::metadata_builder() + .add_custom("foo", "bar") + .build(); + + const model::test_case base_tc(base_name, base_metadata); + + // Construct with all same values. + { + const model::test_case other_tc(base_name, base_metadata); + + ATF_REQUIRE( base_tc == other_tc); + ATF_REQUIRE(!(base_tc != other_tc)); + } + + // Construct with all same values but different metadata objects. + { + const model::metadata other_metadata = model::metadata_builder() + .add_custom("foo", "bar") + .set_timeout(base_metadata.timeout()) + .build(); + const model::test_case other_tc(base_name, other_metadata); + + ATF_REQUIRE( base_tc == other_tc); + ATF_REQUIRE(!(base_tc != other_tc)); + } + + // Different name. + { + const model::test_case other_tc("other", base_metadata); + + ATF_REQUIRE(!(base_tc == other_tc)); + ATF_REQUIRE( base_tc != other_tc); + } + + // Different metadata. + { + const model::test_case other_tc(base_name, + model::metadata_builder().build()); + + ATF_REQUIRE(!(base_tc == other_tc)); + ATF_REQUIRE( base_tc != other_tc); + } +} + + +ATF_TEST_CASE_WITHOUT_HEAD(test_case__output); +ATF_TEST_CASE_BODY(test_case__output) +{ + const model::test_case tc1( + "the-name", model::metadata_builder() + .add_allowed_platform("foo").add_custom("bar", "baz").build()); + std::ostringstream str; + str << tc1; + ATF_REQUIRE_EQ( + "test_case{name='the-name', " + "metadata=metadata{allowed_architectures='', allowed_platforms='foo', " + "custom.bar='baz', description='', has_cleanup='false', " + "is_exclusive='false', " + "required_configs='', required_disk_space='0', required_files='', " + "required_memory='0', " + "required_programs='', required_user='', timeout='300'}}", + str.str()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(test_cases_map__builder); +ATF_TEST_CASE_BODY(test_cases_map__builder) +{ + model::test_cases_map_builder builder; + model::test_cases_map exp_test_cases; + + ATF_REQUIRE_EQ(exp_test_cases, builder.build()); + + builder.add("default-metadata"); + { + const model::test_case tc1("default-metadata", + model::metadata_builder().build()); + exp_test_cases.insert( + model::test_cases_map::value_type(tc1.name(), tc1)); + } + ATF_REQUIRE_EQ(exp_test_cases, builder.build()); + + builder.add("with-metadata", + model::metadata_builder().set_description("text").build()); + { + const model::test_case tc1("with-metadata", + model::metadata_builder() + .set_description("text").build()); + exp_test_cases.insert( + model::test_cases_map::value_type(tc1.name(), tc1)); + } + ATF_REQUIRE_EQ(exp_test_cases, builder.build()); + + const model::test_case tc1("fully_built", + model::metadata_builder() + .set_description("something else").build()); + builder.add(tc1); + exp_test_cases.insert(model::test_cases_map::value_type(tc1.name(), tc1)); + ATF_REQUIRE_EQ(exp_test_cases, builder.build()); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, test_case__ctor_and_getters); + ATF_ADD_TEST_CASE(tcs, test_case__fake_result); + + ATF_ADD_TEST_CASE(tcs, test_case__apply_metadata_overrides__real_test_case); + ATF_ADD_TEST_CASE(tcs, test_case__apply_metadata_overrides__fake_test_case); + + ATF_ADD_TEST_CASE(tcs, test_case__operators_eq_and_ne__copy); + ATF_ADD_TEST_CASE(tcs, test_case__operators_eq_and_ne__not_copy); + + ATF_ADD_TEST_CASE(tcs, test_case__output); + + ATF_ADD_TEST_CASE(tcs, test_cases_map__builder); +} diff --git a/model/test_program.cpp b/model/test_program.cpp new file mode 100644 index 000000000000..b9181aa49537 --- /dev/null +++ b/model/test_program.cpp @@ -0,0 +1,452 @@ +// Copyright 2010 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 "model/test_program.hpp" + +#include <map> +#include <sstream> + +#include "model/exceptions.hpp" +#include "model/metadata.hpp" +#include "model/test_case.hpp" +#include "model/test_result.hpp" +#include "utils/format/containers.ipp" +#include "utils/format/macros.hpp" +#include "utils/fs/path.hpp" +#include "utils/noncopyable.hpp" +#include "utils/sanity.hpp" +#include "utils/text/operations.ipp" + +namespace fs = utils::fs; +namespace text = utils::text; + +using utils::none; + + +/// Internal implementation of a test_program. +struct model::test_program::impl : utils::noncopyable { + /// Name of the test program interface. + std::string interface_name; + + /// Name of the test program binary relative to root. + fs::path binary; + + /// Root of the test suite containing the test program. + fs::path root; + + /// Name of the test suite this program belongs to. + std::string test_suite_name; + + /// Metadata of the test program. + model::metadata md; + + /// List of test cases in the test program. + /// + /// Must be queried via the test_program::test_cases() method. + model::test_cases_map test_cases; + + /// Constructor. + /// + /// \param interface_name_ Name of the test program interface. + /// \param binary_ The name of the test program binary relative to root_. + /// \param root_ The root of the test suite containing the test program. + /// \param test_suite_name_ The name of the test suite this program + /// belongs to. + /// \param md_ Metadata of the test program. + /// \param test_cases_ The collection of test cases in the test program. + impl(const std::string& interface_name_, const fs::path& binary_, + const fs::path& root_, const std::string& test_suite_name_, + const model::metadata& md_, const model::test_cases_map& test_cases_) : + interface_name(interface_name_), + binary(binary_), + root(root_), + test_suite_name(test_suite_name_), + md(md_) + { + PRE_MSG(!binary.is_absolute(), + F("The program '%s' must be relative to the root of the test " + "suite '%s'") % binary % root); + + set_test_cases(test_cases_); + } + + /// Sets the list of test cases of the test program. + /// + /// \param test_cases_ The new list of test cases. + void + set_test_cases(const model::test_cases_map& test_cases_) + { + for (model::test_cases_map::const_iterator iter = test_cases_.begin(); + iter != test_cases_.end(); ++iter) { + const std::string& name = (*iter).first; + const model::test_case& test_case = (*iter).second; + + PRE_MSG(name == test_case.name(), + F("The test case '%s' has been registered with the " + "non-matching name '%s'") % name % test_case.name()); + + test_cases.insert(model::test_cases_map::value_type( + name, test_case.apply_metadata_defaults(&md))); + } + INV(test_cases.size() == test_cases_.size()); + } +}; + + +/// Constructs a new test program. +/// +/// \param interface_name_ Name of the test program interface. +/// \param binary_ The name of the test program binary relative to root_. +/// \param root_ The root of the test suite containing the test program. +/// \param test_suite_name_ The name of the test suite this program belongs to. +/// \param md_ Metadata of the test program. +/// \param test_cases_ The collection of test cases in the test program. +model::test_program::test_program(const std::string& interface_name_, + const fs::path& binary_, + const fs::path& root_, + const std::string& test_suite_name_, + const model::metadata& md_, + const model::test_cases_map& test_cases_) : + _pimpl(new impl(interface_name_, binary_, root_, test_suite_name_, md_, + test_cases_)) +{ +} + + +/// Destroys a test program. +model::test_program::~test_program(void) +{ +} + + +/// Gets the name of the test program interface. +/// +/// \return An interface name. +const std::string& +model::test_program::interface_name(void) const +{ + return _pimpl->interface_name; +} + + +/// Gets the path to the test program relative to the root of the test suite. +/// +/// \return The relative path to the test program binary. +const fs::path& +model::test_program::relative_path(void) const +{ + return _pimpl->binary; +} + + +/// Gets the absolute path to the test program. +/// +/// \return The absolute path to the test program binary. +const fs::path +model::test_program::absolute_path(void) const +{ + const fs::path full_path = _pimpl->root / _pimpl->binary; + return full_path.is_absolute() ? full_path : full_path.to_absolute(); +} + + +/// Gets the root of the test suite containing this test program. +/// +/// \return The path to the root of the test suite. +const fs::path& +model::test_program::root(void) const +{ + return _pimpl->root; +} + + +/// Gets the name of the test suite containing this test program. +/// +/// \return The name of the test suite. +const std::string& +model::test_program::test_suite_name(void) const +{ + return _pimpl->test_suite_name; +} + + +/// Gets the metadata of the test program. +/// +/// \return The metadata. +const model::metadata& +model::test_program::get_metadata(void) const +{ + return _pimpl->md; +} + + +/// Gets a test case by its name. +/// +/// \param name The name of the test case to locate. +/// +/// \return The requested test case. +/// +/// \throw not_found_error If the specified test case is not in the test +/// program. +const model::test_case& +model::test_program::find(const std::string& name) const +{ + const test_cases_map& tcs = test_cases(); + + const test_cases_map::const_iterator iter = tcs.find(name); + if (iter == tcs.end()) + throw not_found_error(F("Unknown test case %s in test program %s") % + name % relative_path()); + return (*iter).second; +} + + +/// Gets the list of test cases from the test program. +/// +/// \return The list of test cases provided by the test program. +const model::test_cases_map& +model::test_program::test_cases(void) const +{ + return _pimpl->test_cases; +} + + +/// Sets the list of test cases of the test program. +/// +/// This can only be called once and it may only be called from within +/// overridden test_cases() before that method ever returns a value for the +/// first time. Any other invocations will result in inconsistent program +/// state. +/// +/// \param test_cases_ The new list of test cases. +void +model::test_program::set_test_cases(const model::test_cases_map& test_cases_) +{ + PRE(_pimpl->test_cases.empty()); + _pimpl->set_test_cases(test_cases_); +} + + +/// Equality comparator. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object and other are equal; false otherwise. +bool +model::test_program::operator==(const test_program& other) const +{ + return _pimpl == other._pimpl || ( + _pimpl->interface_name == other._pimpl->interface_name && + _pimpl->binary == other._pimpl->binary && + _pimpl->root == other._pimpl->root && + _pimpl->test_suite_name == other._pimpl->test_suite_name && + _pimpl->md == other._pimpl->md && + test_cases() == other.test_cases()); +} + + +/// Inequality comparator. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object and other are different; false otherwise. +bool +model::test_program::operator!=(const test_program& other) const +{ + return !(*this == other); +} + + +/// Less-than comparator. +/// +/// A test program is considered to be less than another if and only if the +/// former's absolute path is less than the absolute path of the latter. In +/// other words, the absolute path is used here as the test program's +/// identifier. +/// +/// This simplistic less-than operator overload is provided so that test +/// programs can be held in sets and other containers. +/// +/// \param other The other object to compare this one to. +/// +/// \return True if this object sorts before the other object; false otherwise. +bool +model::test_program::operator<(const test_program& other) const +{ + return absolute_path() < other.absolute_path(); +} + + +/// Injects the object into a stream. +/// +/// \param output The stream into which to inject the object. +/// \param object The object to format. +/// +/// \return The output stream. +std::ostream& +model::operator<<(std::ostream& output, const test_program& object) +{ + output << F("test_program{interface=%s, binary=%s, root=%s, test_suite=%s, " + "metadata=%s, test_cases=%s}") + % text::quote(object.interface_name(), '\'') + % text::quote(object.relative_path().str(), '\'') + % text::quote(object.root().str(), '\'') + % text::quote(object.test_suite_name(), '\'') + % object.get_metadata() + % object.test_cases(); + return output; +} + + +/// Internal implementation of the test_program_builder class. +struct model::test_program_builder::impl : utils::noncopyable { + /// Partially-constructed program with only the required properties. + model::test_program prototype; + + /// Optional metadata for the test program. + model::metadata metadata; + + /// Collection of test cases. + model::test_cases_map test_cases; + + /// Whether we have created a test_program object or not. + bool built; + + /// Constructor. + /// + /// \param prototype_ The partially constructed program with only the + /// required properties. + impl(const model::test_program& prototype_) : + prototype(prototype_), + metadata(model::metadata_builder().build()), + built(false) + { + } +}; + + +/// Constructs a new builder with non-optional values. +/// +/// \param interface_name_ Name of the test program interface. +/// \param binary_ The name of the test program binary relative to root_. +/// \param root_ The root of the test suite containing the test program. +/// \param test_suite_name_ The name of the test suite this program belongs to. +model::test_program_builder::test_program_builder( + const std::string& interface_name_, const fs::path& binary_, + const fs::path& root_, const std::string& test_suite_name_) : + _pimpl(new impl(model::test_program(interface_name_, binary_, root_, + test_suite_name_, + model::metadata_builder().build(), + model::test_cases_map()))) +{ +} + + +/// Destructor. +model::test_program_builder::~test_program_builder(void) +{ +} + + +/// Accumulates an additional test case with default metadata. +/// +/// \param test_case_name The name of the test case. +/// +/// \return A reference to this builder. +model::test_program_builder& +model::test_program_builder::add_test_case(const std::string& test_case_name) +{ + return add_test_case(test_case_name, model::metadata_builder().build()); +} + + +/// Accumulates an additional test case. +/// +/// \param test_case_name The name of the test case. +/// \param metadata The test case metadata. +/// +/// \return A reference to this builder. +model::test_program_builder& +model::test_program_builder::add_test_case(const std::string& test_case_name, + const model::metadata& metadata) +{ + const model::test_case test_case(test_case_name, metadata); + PRE_MSG(_pimpl->test_cases.find(test_case_name) == _pimpl->test_cases.end(), + F("Attempted to re-register test case '%s'") % test_case_name); + _pimpl->test_cases.insert(model::test_cases_map::value_type( + test_case_name, test_case)); + return *this; +} + + +/// Sets the test program metadata. +/// +/// \return metadata The metadata for the test program. +/// +/// \return A reference to this builder. +model::test_program_builder& +model::test_program_builder::set_metadata(const model::metadata& metadata) +{ + _pimpl->metadata = metadata; + return *this; +} + + +/// Creates a new test_program object. +/// +/// \pre This has not yet been called. We only support calling this function +/// once. +/// +/// \return The constructed test_program object. +model::test_program +model::test_program_builder::build(void) const +{ + PRE(!_pimpl->built); + _pimpl->built = true; + + return test_program(_pimpl->prototype.interface_name(), + _pimpl->prototype.relative_path(), + _pimpl->prototype.root(), + _pimpl->prototype.test_suite_name(), + _pimpl->metadata, + _pimpl->test_cases); +} + + +/// Creates a new dynamically-allocated test_program object. +/// +/// \pre This has not yet been called. We only support calling this function +/// once. +/// +/// \return The constructed test_program object. +model::test_program_ptr +model::test_program_builder::build_ptr(void) const +{ + const test_program result = build(); + return test_program_ptr(new test_program(result)); +} diff --git a/model/test_program.hpp b/model/test_program.hpp new file mode 100644 index 000000000000..974ec2a12d19 --- /dev/null +++ b/model/test_program.hpp @@ -0,0 +1,110 @@ +// Copyright 2010 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 model/test_program.hpp +/// Definition of the "test program" concept. + +#if !defined(MODEL_TEST_PROGRAM_HPP) +#define MODEL_TEST_PROGRAM_HPP + +#include "model/test_program_fwd.hpp" + +#include <memory> +#include <ostream> +#include <string> +#include <vector> + +#include "model/metadata_fwd.hpp" +#include "model/test_case_fwd.hpp" +#include "utils/fs/path_fwd.hpp" +#include "utils/noncopyable.hpp" + +namespace model { + + +/// Representation of a test program. +class test_program { + struct impl; + + /// Pointer to the shared internal implementation. + std::shared_ptr< impl > _pimpl; + +protected: + void set_test_cases(const model::test_cases_map&); + +public: + test_program(const std::string&, const utils::fs::path&, + const utils::fs::path&, const std::string&, + const model::metadata&, const model::test_cases_map&); + virtual ~test_program(void); + + const std::string& interface_name(void) const; + const utils::fs::path& root(void) const; + const utils::fs::path& relative_path(void) const; + const utils::fs::path absolute_path(void) const; + const std::string& test_suite_name(void) const; + const model::metadata& get_metadata(void) const; + + const model::test_case& find(const std::string&) const; + virtual const model::test_cases_map& test_cases(void) const; + + bool operator==(const test_program&) const; + bool operator!=(const test_program&) const; + bool operator<(const test_program&) const; +}; + + +std::ostream& operator<<(std::ostream&, const test_program&); + + +/// Builder for a test_program object. +class test_program_builder : utils::noncopyable { + struct impl; + + /// Pointer to the shared internal implementation. + std::auto_ptr< impl > _pimpl; + +public: + test_program_builder(const std::string&, const utils::fs::path&, + const utils::fs::path&, const std::string&); + ~test_program_builder(void); + + test_program_builder& add_test_case(const std::string&); + test_program_builder& add_test_case(const std::string&, + const model::metadata&); + + test_program_builder& set_metadata(const model::metadata&); + + test_program build(void) const; + test_program_ptr build_ptr(void) const; +}; + + +} // namespace model + +#endif // !defined(MODEL_TEST_PROGRAM_HPP) diff --git a/model/test_program_fwd.hpp b/model/test_program_fwd.hpp new file mode 100644 index 000000000000..100f017c30a6 --- /dev/null +++ b/model/test_program_fwd.hpp @@ -0,0 +1,55 @@ +// Copyright 2014 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 model/test_program_fwd.hpp +/// Forward declarations for model/test_program.hpp + +#if !defined(MODEL_TEST_PROGRAM_FWD_HPP) +#define MODEL_TEST_PROGRAM_FWD_HPP + +#include <memory> +#include <vector> + + +namespace model { + + +class test_program; + + +/// Pointer to a test program. +typedef std::shared_ptr< test_program > test_program_ptr; + + +/// Collection of test programs. +typedef std::vector< test_program_ptr > test_programs_vector; + + +} // namespace model + +#endif // !defined(MODEL_TEST_PROGRAM_FWD_HPP) diff --git a/model/test_program_test.cpp b/model/test_program_test.cpp new file mode 100644 index 000000000000..f9a8f7e59da3 --- /dev/null +++ b/model/test_program_test.cpp @@ -0,0 +1,711 @@ +// Copyright 2010 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 "model/test_program.hpp" + +extern "C" { +#include <sys/stat.h> + +#include <signal.h> +} + +#include <set> +#include <sstream> + +#include <atf-c++.hpp> + +#include "model/exceptions.hpp" +#include "model/metadata.hpp" +#include "model/test_case.hpp" +#include "model/test_result.hpp" +#include "utils/env.hpp" +#include "utils/format/containers.ipp" +#include "utils/format/macros.hpp" +#include "utils/fs/operations.hpp" +#include "utils/fs/path.hpp" +#include "utils/optional.ipp" + +namespace fs = utils::fs; + + +namespace { + + +/// Test program that sets its test cases lazily. +/// +/// This test class exists to test the behavior of a test_program object when +/// the class is extended to offer lazy loading of test cases. We simulate such +/// lazy loading here by storing the list of test cases aside at construction +/// time and later setting it lazily the first time test_cases() is called. +class lazy_test_program : public model::test_program { + /// Whether set_test_cases() has yet been called or not. + mutable bool _set_test_cases_called; + + /// The list of test cases for this test program. + /// + /// Only use this in the call to set_test_cases(). All other reads of the + /// test cases list should happen via the parent class' test_cases() method. + model::test_cases_map _lazy_test_cases; + +public: + /// Constructs a new test program. + /// + /// \param interface_name_ Name of the test program interface. + /// \param binary_ The name of the test program binary relative to root_. + /// \param root_ The root of the test suite containing the test program. + /// \param test_suite_name_ The name of the test suite. + /// \param metadata_ Metadata of the test program. + /// \param test_cases_ The collection of test cases in the test program. + lazy_test_program(const std::string& interface_name_, + const utils::fs::path& binary_, + const utils::fs::path& root_, + const std::string& test_suite_name_, + const model::metadata& metadata_, + const model::test_cases_map& test_cases_) : + test_program(interface_name_, binary_, root_, test_suite_name_, + metadata_, model::test_cases_map()), + _set_test_cases_called(false), + _lazy_test_cases(test_cases_) + { + } + + /// Lazily sets the test cases on the parent and returns them. + /// + /// \return The list of test cases. + const model::test_cases_map& + test_cases(void) const + { + if (!_set_test_cases_called) { + const_cast< lazy_test_program* >(this)->set_test_cases( + _lazy_test_cases); + _set_test_cases_called = true; + } + return test_program::test_cases(); + } +}; + + +} // anonymous namespace + + +/// Runs a ctor_and_getters test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_ctor_and_getters(void) +{ + const model::metadata tp_md = model::metadata_builder() + .add_custom("first", "foo") + .add_custom("second", "bar") + .build(); + const model::metadata tc_md = model::metadata_builder() + .add_custom("first", "baz") + .build(); + + const TestProgram test_program( + "mock", fs::path("binary"), fs::path("root"), "suite-name", tp_md, + model::test_cases_map_builder().add("foo", tc_md).build()); + + + ATF_REQUIRE_EQ("mock", test_program.interface_name()); + ATF_REQUIRE_EQ(fs::path("binary"), test_program.relative_path()); + ATF_REQUIRE_EQ(fs::current_path() / "root/binary", + test_program.absolute_path()); + ATF_REQUIRE_EQ(fs::path("root"), test_program.root()); + ATF_REQUIRE_EQ("suite-name", test_program.test_suite_name()); + ATF_REQUIRE_EQ(tp_md, test_program.get_metadata()); + + const model::metadata exp_tc_md = model::metadata_builder() + .add_custom("first", "baz") + .add_custom("second", "bar") + .build(); + const model::test_cases_map exp_tcs = model::test_cases_map_builder() + .add("foo", exp_tc_md) + .build(); + ATF_REQUIRE_EQ(exp_tcs, test_program.test_cases()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(ctor_and_getters); +ATF_TEST_CASE_BODY(ctor_and_getters) +{ + check_ctor_and_getters< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__ctor_and_getters); +ATF_TEST_CASE_BODY(derived__ctor_and_getters) +{ + check_ctor_and_getters< lazy_test_program >(); +} + + +/// Runs a find_ok test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_find_ok(void) +{ + const model::test_case test_case("main", model::metadata_builder().build()); + + const TestProgram test_program( + "mock", fs::path("non-existent"), fs::path("."), "suite-name", + model::metadata_builder().build(), + model::test_cases_map_builder().add(test_case).build()); + + const model::test_case& found_test_case = test_program.find("main"); + ATF_REQUIRE_EQ(test_case, found_test_case); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(find__ok); +ATF_TEST_CASE_BODY(find__ok) +{ + check_find_ok< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__find__ok); +ATF_TEST_CASE_BODY(derived__find__ok) +{ + check_find_ok< lazy_test_program >(); +} + + +/// Runs a find_missing test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_find_missing(void) +{ + const TestProgram test_program( + "mock", fs::path("non-existent"), fs::path("."), "suite-name", + model::metadata_builder().build(), + model::test_cases_map_builder().add("main").build()); + + ATF_REQUIRE_THROW_RE(model::not_found_error, + "case.*abc.*program.*non-existent", + test_program.find("abc")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(find__missing); +ATF_TEST_CASE_BODY(find__missing) +{ + check_find_missing< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__find__missing); +ATF_TEST_CASE_BODY(derived__find__missing) +{ + check_find_missing< lazy_test_program >(); +} + + +/// Runs a metadata_inheritance test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_metadata_inheritance(void) +{ + const model::test_cases_map test_cases = model::test_cases_map_builder() + .add("inherit-all") + .add("inherit-some", + model::metadata_builder() + .set_description("Overriden description") + .build()) + .add("inherit-none", + model::metadata_builder() + .add_allowed_architecture("overriden-arch") + .add_allowed_platform("overriden-platform") + .set_description("Overriden description") + .build()) + .build(); + + const model::metadata metadata = model::metadata_builder() + .add_allowed_architecture("base-arch") + .set_description("Base description") + .build(); + const TestProgram test_program( + "plain", fs::path("non-existent"), fs::path("."), "suite-name", + metadata, test_cases); + + { + const model::metadata exp_metadata = model::metadata_builder() + .add_allowed_architecture("base-arch") + .set_description("Base description") + .build(); + ATF_REQUIRE_EQ(exp_metadata, + test_program.find("inherit-all").get_metadata()); + } + + { + const model::metadata exp_metadata = model::metadata_builder() + .add_allowed_architecture("base-arch") + .set_description("Overriden description") + .build(); + ATF_REQUIRE_EQ(exp_metadata, + test_program.find("inherit-some").get_metadata()); + } + + { + const model::metadata exp_metadata = model::metadata_builder() + .add_allowed_architecture("overriden-arch") + .add_allowed_platform("overriden-platform") + .set_description("Overriden description") + .build(); + ATF_REQUIRE_EQ(exp_metadata, + test_program.find("inherit-none").get_metadata()); + } +} + + +ATF_TEST_CASE_WITHOUT_HEAD(metadata_inheritance); +ATF_TEST_CASE_BODY(metadata_inheritance) +{ + check_metadata_inheritance< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__metadata_inheritance); +ATF_TEST_CASE_BODY(derived__metadata_inheritance) +{ + check_metadata_inheritance< lazy_test_program >(); +} + + +/// Runs a operators_eq_and_ne__copy test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_operators_eq_and_ne__copy(void) +{ + const TestProgram tp1( + "plain", fs::path("non-existent"), fs::path("."), "suite-name", + model::metadata_builder().build(), + model::test_cases_map()); + const TestProgram tp2 = tp1; + ATF_REQUIRE( tp1 == tp2); + ATF_REQUIRE(!(tp1 != tp2)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__copy); +ATF_TEST_CASE_BODY(operators_eq_and_ne__copy) +{ + check_operators_eq_and_ne__copy< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__operators_eq_and_ne__copy); +ATF_TEST_CASE_BODY(derived__operators_eq_and_ne__copy) +{ + check_operators_eq_and_ne__copy< lazy_test_program >(); +} + + +/// Runs a operators_eq_and_ne__not_copy test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_operators_eq_and_ne__not_copy(void) +{ + const std::string base_interface("plain"); + const fs::path base_relative_path("the/test/program"); + const fs::path base_root("/the/root"); + const std::string base_test_suite("suite-name"); + const model::metadata base_metadata = model::metadata_builder() + .add_custom("foo", "bar") + .build(); + + const model::test_cases_map base_tcs = model::test_cases_map_builder() + .add("main", model::metadata_builder() + .add_custom("second", "baz") + .build()) + .build(); + + const TestProgram base_tp( + base_interface, base_relative_path, base_root, base_test_suite, + base_metadata, base_tcs); + + // Construct with all same values. + { + const model::test_cases_map other_tcs = model::test_cases_map_builder() + .add("main", model::metadata_builder() + .add_custom("second", "baz") + .build()) + .build(); + + const TestProgram other_tp( + base_interface, base_relative_path, base_root, base_test_suite, + base_metadata, other_tcs); + + ATF_REQUIRE( base_tp == other_tp); + ATF_REQUIRE(!(base_tp != other_tp)); + } + + // Construct with same final metadata values but using a different + // intermediate representation. The original test program has one property + // in the base test program definition and another in the test case; here, + // we put both definitions explicitly in the test case. + { + const model::test_cases_map other_tcs = model::test_cases_map_builder() + .add("main", model::metadata_builder() + .add_custom("foo", "bar") + .add_custom("second", "baz") + .build()) + .build(); + + const TestProgram other_tp( + base_interface, base_relative_path, base_root, base_test_suite, + base_metadata, other_tcs); + + ATF_REQUIRE( base_tp == other_tp); + ATF_REQUIRE(!(base_tp != other_tp)); + } + + // Different interface. + { + const TestProgram other_tp( + "atf", base_relative_path, base_root, base_test_suite, + base_metadata, base_tcs); + + ATF_REQUIRE(!(base_tp == other_tp)); + ATF_REQUIRE( base_tp != other_tp); + } + + // Different relative path. + { + const TestProgram other_tp( + base_interface, fs::path("a/b/c"), base_root, base_test_suite, + base_metadata, base_tcs); + + ATF_REQUIRE(!(base_tp == other_tp)); + ATF_REQUIRE( base_tp != other_tp); + } + + // Different root. + { + const TestProgram other_tp( + base_interface, base_relative_path, fs::path("."), base_test_suite, + base_metadata, base_tcs); + + ATF_REQUIRE(!(base_tp == other_tp)); + ATF_REQUIRE( base_tp != other_tp); + } + + // Different test suite. + { + const TestProgram other_tp( + base_interface, base_relative_path, base_root, "different-suite", + base_metadata, base_tcs); + + ATF_REQUIRE(!(base_tp == other_tp)); + ATF_REQUIRE( base_tp != other_tp); + } + + // Different metadata. + { + const TestProgram other_tp( + base_interface, base_relative_path, base_root, base_test_suite, + model::metadata_builder().build(), base_tcs); + + ATF_REQUIRE(!(base_tp == other_tp)); + ATF_REQUIRE( base_tp != other_tp); + } + + // Different test cases. + { + const model::test_cases_map other_tcs = model::test_cases_map_builder() + .add("foo").build(); + + const TestProgram other_tp( + base_interface, base_relative_path, base_root, base_test_suite, + base_metadata, other_tcs); + + ATF_REQUIRE(!(base_tp == other_tp)); + ATF_REQUIRE( base_tp != other_tp); + } +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__not_copy); +ATF_TEST_CASE_BODY(operators_eq_and_ne__not_copy) +{ + check_operators_eq_and_ne__not_copy< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__operators_eq_and_ne__not_copy); +ATF_TEST_CASE_BODY(derived__operators_eq_and_ne__not_copy) +{ + check_operators_eq_and_ne__not_copy< lazy_test_program >(); +} + + +/// Runs a operator_lt test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_operator_lt(void) +{ + const TestProgram tp1( + "plain", fs::path("a/b/c"), fs::path("/foo/bar"), "suite-name", + model::metadata_builder().build(), + model::test_cases_map()); + const TestProgram tp2( + "atf", fs::path("c"), fs::path("/foo/bar"), "suite-name", + model::metadata_builder().build(), + model::test_cases_map()); + const TestProgram tp3( + "plain", fs::path("a/b/c"), fs::path("/abc"), "suite-name", + model::metadata_builder().build(), + model::test_cases_map()); + + ATF_REQUIRE(!(tp1 < tp1)); + + ATF_REQUIRE( tp1 < tp2); + ATF_REQUIRE(!(tp2 < tp1)); + + ATF_REQUIRE(!(tp1 < tp3)); + ATF_REQUIRE( tp3 < tp1); + + // And now, test the actual reason why we want to have an < overload by + // attempting to put the various programs in a set. + std::set< TestProgram > programs; + programs.insert(tp1); + programs.insert(tp2); + programs.insert(tp3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operator_lt); +ATF_TEST_CASE_BODY(operator_lt) +{ + check_operator_lt< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__operator_lt); +ATF_TEST_CASE_BODY(derived__operator_lt) +{ + check_operator_lt< lazy_test_program >(); +} + + +/// Runs a output__no_test_cases test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_output__no_test_cases(void) +{ + TestProgram tp( + "plain", fs::path("binary/path"), fs::path("/the/root"), "suite-name", + model::metadata_builder().add_allowed_architecture("a").build(), + model::test_cases_map()); + + std::ostringstream str; + str << tp; + ATF_REQUIRE_EQ( + "test_program{interface='plain', binary='binary/path', " + "root='/the/root', test_suite='suite-name', " + "metadata=metadata{allowed_architectures='a', allowed_platforms='', " + "description='', has_cleanup='false', is_exclusive='false', " + "required_configs='', required_disk_space='0', required_files='', " + "required_memory='0', " + "required_programs='', required_user='', timeout='300'}, " + "test_cases=map()}", + str.str()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(output__no_test_cases); +ATF_TEST_CASE_BODY(output__no_test_cases) +{ + check_output__no_test_cases< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__output__no_test_cases); +ATF_TEST_CASE_BODY(derived__output__no_test_cases) +{ + check_output__no_test_cases< lazy_test_program >(); +} + + +/// Runs a output__some_test_cases test. +/// +/// \tparam TestProgram Either model::test_program or lazy_test_program. +template< class TestProgram > +static void +check_output__some_test_cases(void) +{ + const model::test_cases_map test_cases = model::test_cases_map_builder() + .add("the-name", model::metadata_builder() + .add_allowed_platform("foo") + .add_custom("bar", "baz") + .build()) + .add("another-name") + .build(); + + const TestProgram tp = TestProgram( + "plain", fs::path("binary/path"), fs::path("/the/root"), "suite-name", + model::metadata_builder().add_allowed_architecture("a").build(), + test_cases); + + std::ostringstream str; + str << tp; + ATF_REQUIRE_EQ( + "test_program{interface='plain', binary='binary/path', " + "root='/the/root', test_suite='suite-name', " + "metadata=metadata{allowed_architectures='a', allowed_platforms='', " + "description='', has_cleanup='false', is_exclusive='false', " + "required_configs='', required_disk_space='0', required_files='', " + "required_memory='0', " + "required_programs='', required_user='', timeout='300'}, " + "test_cases=map(" + "another-name=test_case{name='another-name', " + "metadata=metadata{allowed_architectures='a', allowed_platforms='', " + "description='', has_cleanup='false', is_exclusive='false', " + "required_configs='', required_disk_space='0', required_files='', " + "required_memory='0', " + "required_programs='', required_user='', timeout='300'}}, " + "the-name=test_case{name='the-name', " + "metadata=metadata{allowed_architectures='a', allowed_platforms='foo', " + "custom.bar='baz', description='', has_cleanup='false', " + "is_exclusive='false', " + "required_configs='', required_disk_space='0', required_files='', " + "required_memory='0', " + "required_programs='', required_user='', timeout='300'}})}", + str.str()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(output__some_test_cases); +ATF_TEST_CASE_BODY(output__some_test_cases) +{ + check_output__some_test_cases< model::test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(derived__output__some_test_cases); +ATF_TEST_CASE_BODY(derived__output__some_test_cases) +{ + check_output__some_test_cases< lazy_test_program >(); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(builder__defaults); +ATF_TEST_CASE_BODY(builder__defaults) +{ + const model::test_program expected( + "mock", fs::path("non-existent"), fs::path("."), "suite-name", + model::metadata_builder().build(), model::test_cases_map()); + + const model::test_program built = model::test_program_builder( + "mock", fs::path("non-existent"), fs::path("."), "suite-name") + .build(); + + ATF_REQUIRE_EQ(built, expected); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(builder__overrides); +ATF_TEST_CASE_BODY(builder__overrides) +{ + const model::metadata md = model::metadata_builder() + .add_custom("foo", "bar") + .build(); + const model::test_cases_map tcs = model::test_cases_map_builder() + .add("first") + .add("second", md) + .build(); + const model::test_program expected( + "mock", fs::path("binary"), fs::path("root"), "suite-name", md, tcs); + + const model::test_program built = model::test_program_builder( + "mock", fs::path("binary"), fs::path("root"), "suite-name") + .add_test_case("first") + .add_test_case("second", md) + .set_metadata(md) + .build(); + + ATF_REQUIRE_EQ(built, expected); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(builder__ptr); +ATF_TEST_CASE_BODY(builder__ptr) +{ + const model::test_program expected( + "mock", fs::path("non-existent"), fs::path("."), "suite-name", + model::metadata_builder().build(), model::test_cases_map()); + + const model::test_program_ptr built = model::test_program_builder( + "mock", fs::path("non-existent"), fs::path("."), "suite-name") + .build_ptr(); + + ATF_REQUIRE_EQ(*built, expected); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, ctor_and_getters); + ATF_ADD_TEST_CASE(tcs, find__ok); + ATF_ADD_TEST_CASE(tcs, find__missing); + ATF_ADD_TEST_CASE(tcs, metadata_inheritance); + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__copy); + ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__not_copy); + ATF_ADD_TEST_CASE(tcs, operator_lt); + ATF_ADD_TEST_CASE(tcs, output__no_test_cases); + ATF_ADD_TEST_CASE(tcs, output__some_test_cases); + + ATF_ADD_TEST_CASE(tcs, derived__ctor_and_getters); + ATF_ADD_TEST_CASE(tcs, derived__find__ok); + ATF_ADD_TEST_CASE(tcs, derived__find__missing); + ATF_ADD_TEST_CASE(tcs, derived__metadata_inheritance); + ATF_ADD_TEST_CASE(tcs, derived__operators_eq_and_ne__copy); + ATF_ADD_TEST_CASE(tcs, derived__operators_eq_and_ne__not_copy); + ATF_ADD_TEST_CASE(tcs, derived__operator_lt); + ATF_ADD_TEST_CASE(tcs, derived__output__no_test_cases); + ATF_ADD_TEST_CASE(tcs, derived__output__some_test_cases); + + ATF_ADD_TEST_CASE(tcs, builder__defaults); + ATF_ADD_TEST_CASE(tcs, builder__overrides); + ATF_ADD_TEST_CASE(tcs, builder__ptr); +} diff --git a/model/test_result.cpp b/model/test_result.cpp new file mode 100644 index 000000000000..7392e77f5561 --- /dev/null +++ b/model/test_result.cpp @@ -0,0 +1,142 @@ +// Copyright 2014 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 "model/test_result.hpp" + +#include "utils/format/macros.hpp" +#include "utils/sanity.hpp" +#include "utils/text/operations.ipp" + +namespace text = utils::text; + + +/// Constructs a base result. +/// +/// \param type_ The type of the result. +/// \param reason_ The reason explaining the result, if any. It is OK for this +/// to be empty, which is actually the default. +model::test_result::test_result(const test_result_type type_, + const std::string& reason_) : + _type(type_), + _reason(reason_) +{ +} + + +/// Returns the type of the result. +/// +/// \return A result type. +model::test_result_type +model::test_result::type(void) const +{ + return _type; +} + + +/// Returns the reason explaining the result. +/// +/// \return A textual reason, possibly empty. +const std::string& +model::test_result::reason(void) const +{ + return _reason; +} + + +/// True if the test case result has a positive connotation. +/// +/// \return Whether the test case is good or not. +bool +model::test_result::good(void) const +{ + switch (_type) { + case test_result_expected_failure: + case test_result_passed: + case test_result_skipped: + return true; + + case test_result_broken: + case test_result_failed: + return false; + } + UNREACHABLE; +} + + +/// Equality comparator. +/// +/// \param other The test result to compare to. +/// +/// \return True if the other object is equal to this one, false otherwise. +bool +model::test_result::operator==(const test_result& other) const +{ + return _type == other._type && _reason == other._reason; +} + + +/// Inequality comparator. +/// +/// \param other The test result to compare to. +/// +/// \return True if the other object is different from this one, false +/// otherwise. +bool +model::test_result::operator!=(const test_result& other) const +{ + return !(*this == other); +} + + +/// Injects the object into a stream. +/// +/// \param output The stream into which to inject the object. +/// \param object The object to format. +/// +/// \return The output stream. +std::ostream& +model::operator<<(std::ostream& output, const test_result& object) +{ + std::string result_name; + switch (object.type()) { + case test_result_broken: result_name = "broken"; break; + case test_result_expected_failure: result_name = "expected_failure"; break; + case test_result_failed: result_name = "failed"; break; + case test_result_passed: result_name = "passed"; break; + case test_result_skipped: result_name = "skipped"; break; + } + const std::string& reason = object.reason(); + if (reason.empty()) { + output << F("model::test_result{type=%s}") + % text::quote(result_name, '\''); + } else { + output << F("model::test_result{type=%s, reason=%s}") + % text::quote(result_name, '\'') % text::quote(reason, '\''); + } + return output; +} diff --git a/model/test_result.hpp b/model/test_result.hpp new file mode 100644 index 000000000000..b9c439ce789a --- /dev/null +++ b/model/test_result.hpp @@ -0,0 +1,79 @@ +// Copyright 2014 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 model/test_result.hpp +/// Definition of the "test result" concept. + +#if !defined(MODEL_TEST_RESULT_HPP) +#define MODEL_TEST_RESULT_HPP + +#include "model/test_result_fwd.hpp" + +#include <ostream> +#include <string> + +namespace model { + + +/// Representation of a single test result. +/// +/// A test result is a simple pair of (type, reason). The type indicates the +/// semantics of the results, and the optional reason provides an extra +/// description of the result type. +/// +/// In general, a 'passed' result will not have a reason attached, because a +/// successful test case does not deserve any kind of explanation. We used to +/// special-case this with a very complex class hierarchy, but it proved to +/// result in an extremely-complex to maintain code base that provided no +/// benefits. As a result, we allow any test type to carry a reason. +class test_result { + /// The type of the result. + test_result_type _type; + + /// A description of the result; may be empty. + std::string _reason; + +public: + test_result(const test_result_type, const std::string& = ""); + + test_result_type type(void) const; + const std::string& reason(void) const; + + bool good(void) const; + + bool operator==(const test_result&) const; + bool operator!=(const test_result&) const; +}; + + +std::ostream& operator<<(std::ostream&, const test_result&); + + +} // namespace model + +#endif // !defined(MODEL_TEST_RESULT_HPP) diff --git a/model/test_result_fwd.hpp b/model/test_result_fwd.hpp new file mode 100644 index 000000000000..d7871e81d23e --- /dev/null +++ b/model/test_result_fwd.hpp @@ -0,0 +1,53 @@ +// Copyright 2014 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 model/test_result_fwd.hpp +/// Forward declarations for model/test_result.hpp + +#if !defined(MODEL_TEST_RESULT_FWD_HPP) +#define MODEL_TEST_RESULT_FWD_HPP + +namespace model { + + +/// Definitions for all possible test case results. +enum test_result_type { + test_result_broken, + test_result_expected_failure, + test_result_failed, + test_result_passed, + test_result_skipped, +}; + + +class test_result; + + +} // namespace model + +#endif // !defined(MODEL_TEST_RESULT_FWD_HPP) diff --git a/model/test_result_test.cpp b/model/test_result_test.cpp new file mode 100644 index 000000000000..355587d37aee --- /dev/null +++ b/model/test_result_test.cpp @@ -0,0 +1,185 @@ +// Copyright 2014 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 "model/test_result.hpp" + +#include <sstream> + +#include <atf-c++.hpp> + + +/// Creates a test case to validate the getters. +/// +/// \param name The name of the test case; "__getters" will be appended. +/// \param expected_type The expected type of the result. +/// \param expected_reason The expected reason for the result. +/// \param result The result to query. +#define GETTERS_TEST(name, expected_type, expected_reason, result) \ + ATF_TEST_CASE_WITHOUT_HEAD(name ## __getters); \ + ATF_TEST_CASE_BODY(name ## __getters) \ + { \ + ATF_REQUIRE(expected_type == result.type()); \ + ATF_REQUIRE_EQ(expected_reason, result.reason()); \ + } + + +/// Creates a test case to validate the good() method. +/// +/// \param name The name of the test case; "__good" will be appended. +/// \param expected The expected result of good(). +/// \param result_type The result type to check. +#define GOOD_TEST(name, expected, result_type) \ + ATF_TEST_CASE_WITHOUT_HEAD(name ## __good); \ + ATF_TEST_CASE_BODY(name ## __good) \ + { \ + ATF_REQUIRE_EQ(expected, model::test_result(result_type).good()); \ + } + + +/// Creates a test case to validate the operator<< method. +/// +/// \param name The name of the test case; "__output" will be appended. +/// \param expected The expected string in the output. +/// \param result The result to format. +#define OUTPUT_TEST(name, expected, result) \ + ATF_TEST_CASE_WITHOUT_HEAD(name ## __output); \ + ATF_TEST_CASE_BODY(name ## __output) \ + { \ + std::ostringstream output; \ + output << "prefix" << result << "suffix"; \ + ATF_REQUIRE_EQ("prefix" + std::string(expected) + "suffix", \ + output.str()); \ + } + + +GETTERS_TEST( + broken, + model::test_result_broken, + "The reason", + model::test_result(model::test_result_broken, "The reason")); +GETTERS_TEST( + expected_failure, + model::test_result_expected_failure, + "The reason", + model::test_result(model::test_result_expected_failure, "The reason")); +GETTERS_TEST( + failed, + model::test_result_failed, + "The reason", + model::test_result(model::test_result_failed, "The reason")); +GETTERS_TEST( + passed, + model::test_result_passed, + "", + model::test_result(model::test_result_passed)); +GETTERS_TEST( + skipped, + model::test_result_skipped, + "The reason", + model::test_result(model::test_result_skipped, "The reason")); + + +GOOD_TEST(broken, false, model::test_result_broken); +GOOD_TEST(expected_failure, true, model::test_result_expected_failure); +GOOD_TEST(failed, false, model::test_result_failed); +GOOD_TEST(passed, true, model::test_result_passed); +GOOD_TEST(skipped, true, model::test_result_skipped); + + +OUTPUT_TEST( + broken, + "model::test_result{type='broken', reason='foo'}", + model::test_result(model::test_result_broken, "foo")); +OUTPUT_TEST( + expected_failure, + "model::test_result{type='expected_failure', reason='abc def'}", + model::test_result(model::test_result_expected_failure, "abc def")); +OUTPUT_TEST( + failed, + "model::test_result{type='failed', reason='some \\'string'}", + model::test_result(model::test_result_failed, "some 'string")); +OUTPUT_TEST( + passed, + "model::test_result{type='passed'}", + model::test_result(model::test_result_passed, "")); +OUTPUT_TEST( + skipped, + "model::test_result{type='skipped', reason='last message'}", + model::test_result(model::test_result_skipped, "last message")); + + +ATF_TEST_CASE_WITHOUT_HEAD(operator_eq); +ATF_TEST_CASE_BODY(operator_eq) +{ + const model::test_result result1(model::test_result_broken, "Foo"); + const model::test_result result2(model::test_result_broken, "Foo"); + const model::test_result result3(model::test_result_broken, "Bar"); + const model::test_result result4(model::test_result_failed, "Foo"); + + ATF_REQUIRE( result1 == result1); + ATF_REQUIRE( result1 == result2); + ATF_REQUIRE(!(result1 == result3)); + ATF_REQUIRE(!(result1 == result4)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(operator_ne); +ATF_TEST_CASE_BODY(operator_ne) +{ + const model::test_result result1(model::test_result_broken, "Foo"); + const model::test_result result2(model::test_result_broken, "Foo"); + const model::test_result result3(model::test_result_broken, "Bar"); + const model::test_result result4(model::test_result_failed, "Foo"); + + ATF_REQUIRE(!(result1 != result1)); + ATF_REQUIRE(!(result1 != result2)); + ATF_REQUIRE( result1 != result3); + ATF_REQUIRE( result1 != result4); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, broken__getters); + ATF_ADD_TEST_CASE(tcs, broken__good); + ATF_ADD_TEST_CASE(tcs, broken__output); + ATF_ADD_TEST_CASE(tcs, expected_failure__getters); + ATF_ADD_TEST_CASE(tcs, expected_failure__good); + ATF_ADD_TEST_CASE(tcs, expected_failure__output); + ATF_ADD_TEST_CASE(tcs, failed__getters); + ATF_ADD_TEST_CASE(tcs, failed__good); + ATF_ADD_TEST_CASE(tcs, failed__output); + ATF_ADD_TEST_CASE(tcs, passed__getters); + ATF_ADD_TEST_CASE(tcs, passed__good); + ATF_ADD_TEST_CASE(tcs, passed__output); + ATF_ADD_TEST_CASE(tcs, skipped__getters); + ATF_ADD_TEST_CASE(tcs, skipped__good); + ATF_ADD_TEST_CASE(tcs, skipped__output); + ATF_ADD_TEST_CASE(tcs, operator_eq); + ATF_ADD_TEST_CASE(tcs, operator_ne); +} diff --git a/model/types.hpp b/model/types.hpp new file mode 100644 index 000000000000..e877b6f58d46 --- /dev/null +++ b/model/types.hpp @@ -0,0 +1,61 @@ +// Copyright 2014 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 model/types.hpp +/// Definition of miscellaneous base types required by our classes. +/// +/// We consider objects coming from the STL and from the utils module to be +/// base types. + +#if !defined(MODEL_TYPES_HPP) +#define MODEL_TYPES_HPP + +#include <map> +#include <set> +#include <string> + +#include "utils/fs/path_fwd.hpp" + +namespace model { + + +/// Collection of paths. +typedef std::set< utils::fs::path > paths_set; + + +/// Collection of strings. +typedef std::set< std::string > strings_set; + + +/// Collection of test properties in their textual form. +typedef std::map< std::string, std::string > properties_map; + + +} // namespace model + +#endif // !defined(MODEL_TYPES_HPP) |