From 641fb08b5fad3e4bc1cbb075021ab8229f0a8a97 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Thu, 16 Feb 2017 13:51:24 +0100 Subject: [PATCH] Automated test for init --- repos/os/run/init.run | 129 ++++++++++++ repos/os/src/app/dummy/main.cc | 98 +++++++++ repos/os/src/app/dummy/target.mk | 3 + repos/os/src/test/init/main.cc | 329 +++++++++++++++++++++++++++++++ repos/os/src/test/init/target.mk | 3 + 5 files changed, 562 insertions(+) create mode 100644 repos/os/run/init.run create mode 100644 repos/os/src/app/dummy/main.cc create mode 100644 repos/os/src/app/dummy/target.mk create mode 100644 repos/os/src/test/init/main.cc create mode 100644 repos/os/src/test/init/target.mk diff --git a/repos/os/run/init.run b/repos/os/run/init.run new file mode 100644 index 0000000000..42ae61f89e --- /dev/null +++ b/repos/os/run/init.run @@ -0,0 +1,129 @@ +# +# Build +# + +set build_components { core init drivers/timer app/dummy test/init } + +build $build_components + +create_boot_directory + +# +# Generate config +# + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# +# Boot modules +# + +set boot_modules { core ld.lib.so init timer report_rom test-init dummy } + +build_boot_image $boot_modules + +append qemu_args " -nographic " + +run_genode_until {.*child "test-init" exited with exit value 0.*} 60 + diff --git a/repos/os/src/app/dummy/main.cc b/repos/os/src/app/dummy/main.cc new file mode 100644 index 0000000000..beba60f62e --- /dev/null +++ b/repos/os/src/app/dummy/main.cc @@ -0,0 +1,98 @@ +/* + * \brief Dummy component used for automated component-composition tests + * \author Norman Feske + * \date 2017-02-16 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + +namespace Dummy { + + struct Log_connections; + struct Main; + using namespace Genode; +} + + +struct Dummy::Log_connections +{ + Env &_env; + + Heap _heap { _env.ram(), _env.rm() }; + + typedef Registered Connection; + + Registry _connections; + + Log_connections(Env &env, Xml_node node) : _env(env) + { + unsigned const count = node.attribute_value("count", 0UL); + + log("going to create ", count, " LOG connections"); + + for (unsigned i = 0; i < count; i++) + new (_heap) Connection(_connections, _env, Session_label { i }); + + log("created all LOG connections"); + } + + ~Log_connections() + { + _connections.for_each([&] (Connection &c) { destroy(_heap, &c); }); + + log("destroyed all LOG connections"); + } +}; + + +struct Dummy::Main +{ + Env &_env; + + Constructible _timer; + + Attached_rom_dataspace _config { _env, "config" }; + + Constructible _log_connections; + + Main(Env &env) : _env(env) + { + _config.xml().for_each_sub_node([&] (Xml_node node) { + + if (node.type() == "create_log_connections") + _log_connections.construct(_env, node); + + if (node.type() == "destroy_log_connections") + _log_connections.destruct(); + + if (node.type() == "sleep") { + + if (!_timer.constructed()) + _timer.construct(_env); + + _timer->msleep(node.attribute_value("ms", 100UL)); + } + + if (node.type() == "log") + log(node.attribute_value("string", String<50>())); + }); + } +}; + + + + +void Component::construct(Genode::Env &env) { static Dummy::Main main(env); } diff --git a/repos/os/src/app/dummy/target.mk b/repos/os/src/app/dummy/target.mk new file mode 100644 index 0000000000..a8da11df29 --- /dev/null +++ b/repos/os/src/app/dummy/target.mk @@ -0,0 +1,3 @@ +TARGET = dummy +SRC_CC = main.cc +LIBS += base diff --git a/repos/os/src/test/init/main.cc b/repos/os/src/test/init/main.cc new file mode 100644 index 0000000000..fddceff22f --- /dev/null +++ b/repos/os/src/test/init/main.cc @@ -0,0 +1,329 @@ +/* + * \brief Test for the init component + * \author Norman Feske + * \date 2017-02-16 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Test { + + struct Log_message_handler; + class Log_session_component; + class Log_root; + struct Main; + + using namespace Genode; + + static bool xml_attribute_matches(Xml_node, Xml_node); + static bool xml_matches(Xml_node, Xml_node); +} + + +/*************** + ** Utilities ** + ***************/ + +static inline bool Test::xml_attribute_matches(Xml_node condition, Xml_node node) +{ + typedef String<32> Name; + typedef String<64> Value; + + Name const name = condition.attribute_value("name", Name()); + Value const value = condition.attribute_value("value", Value()); + + return node.attribute_value(name.string(), Value()) == value; +} + + +/** + * Return true if 'node' has expected content + * + * \expected description of the XML content expected in 'node' + */ +static inline bool Test::xml_matches(Xml_node expected, Xml_node node) +{ + bool matches = true; + expected.for_each_sub_node([&] (Xml_node condition) { + + if (condition.type() == "attribute") + matches = matches && xml_attribute_matches(condition, node); + + if (condition.type() == "node") { + + typedef String<32> Name; + Name const name = condition.attribute_value("name", Name()); + + bool at_least_one_sub_node_matches = false; + node.for_each_sub_node(name.string(), [&] (Xml_node sub_node) { + if (xml_matches(condition, sub_node)) + at_least_one_sub_node_matches = true; }); + + matches = matches && at_least_one_sub_node_matches; + } + }); + return matches; +} + + +struct Test::Log_message_handler +{ + typedef String Message; + + enum Result { EXPECTED, UNEXPECTED, IGNORED }; + + virtual Result handle_log_message(Message const &message) = 0; +}; + + +namespace Genode +{ + static inline void print(Output &output, Test::Log_message_handler::Result result) + { + using Genode::print; + switch (result) { + case Test::Log_message_handler::EXPECTED: print(output, "expected"); break; + case Test::Log_message_handler::UNEXPECTED: print(output, "expected"); break; + case Test::Log_message_handler::IGNORED: print(output, "ignored"); break; + } + } +} + + +class Test::Log_session_component : public Rpc_object +{ + private: + + Session_label const _label; + + Log_message_handler &_handler; + + public: + + Log_session_component(Session_label const &label, Log_message_handler &handler) + : + _label(label), _handler(handler) + { } + + size_t write(String const &string) + { + /* strip known line delimiter from incoming message */ + unsigned n = 0; + Genode::String<16> const pattern("\033[0m\n"); + for (char const *s = string.string(); s[n] && pattern != s + n; n++); + + Log_message_handler::Message const + message("[", _label, "] ", Cstring(string.string(), n)); + + Log_message_handler::Result const result = + _handler.handle_log_message(message); + + log(message, " (", result, ")"); + + return strlen(string.string()); + } +}; + + +class Test::Log_root : public Root_component +{ + private: + + Log_message_handler &_handler; + + public: + + Log_root(Entrypoint &ep, Allocator &md_alloc, Log_message_handler &handler) + : + Root_component(ep, md_alloc), _handler(handler) + { } + + Log_session_component *_create_session(const char *args, Affinity const &) + { + Session_label const label = label_from_args(args); + + return new (md_alloc()) Log_session_component(label, _handler); + } +}; + + +struct Test::Main : Log_message_handler +{ + Env &_env; + + Timer::Connection _timer { _env }; + + Reporter _init_config_reporter { _env, "config", "init.config" }; + + Attached_rom_dataspace _config { _env, "config" }; + + void _publish_report(Reporter &reporter, Xml_node node) + { + typedef String<64> Version; + Version const version = node.attribute_value("version", Version()); + + Reporter::Xml_generator xml(reporter, [&] () { + + if (version.valid()) + xml.attribute("version", version); + + xml.append(node.content_base(), node.content_size()); + }); + } + + Log_message_handler::Message _expected_log_message; + + unsigned const _num_steps = _config.xml().num_sub_nodes(); + unsigned _curr_step = 0; + + Xml_node _curr_step_xml() const { return _config.xml().sub_node(_curr_step); } + + /* + * Handling of state reports generated by init + */ + Attached_rom_dataspace _init_state { _env, "state" }; + + Signal_handler
_init_state_handler { + _env.ep(), *this, &Main::_handle_init_state }; + + void _handle_init_state() + { + _init_state.update(); + _execute_curr_step(); + } + + void _advance_step() + { + _curr_step++; + + /* exit when reaching the end of the sequence */ + if (_curr_step == _num_steps) { + _env.parent().exit(0); + sleep_forever(); + } + }; + + void _execute_curr_step() + { + for (;;) { + Xml_node const step = _curr_step_xml(); + + log("step ", _curr_step, " (", step.type(), ")"); + + if (step.type() == "expect_log") + return; + + if (step.type() == "expect_init_state") { + if (xml_matches(step, _init_state.xml())) { + _advance_step(); + continue; + } else { + warning("init state does not match: ", _init_state.xml()); + warning("expected condition: ", step); + } + return; + } + + if (step.type() == "init_config") { + _publish_report(_init_config_reporter, step); + _advance_step(); + continue; + } + + if (step.type() == "message") { + typedef String<80> Message; + Message const message = step.attribute_value("string", Message()); + log("\n--- ", message, " ---"); + _advance_step(); + continue; + } + + if (step.type() == "nop") { + _advance_step(); + continue; + } + + if (step.type() == "sleep") { + unsigned long const timeout_ms = step.attribute_value("ms", 250UL); + _timer.trigger_once(timeout_ms*1000); + return; + } + + error("unexpected step: ", step); + throw Exception(); + } + } + + /** + * Log_message_handler interface + */ + Result handle_log_message(Log_message_handler::Message const &message) override + { + typedef Log_message_handler::Message Message; + + if (_curr_step_xml().type() != "expect_log") + return IGNORED; + + Message const expected = _curr_step_xml().attribute_value("string", Message()); + + if (message != expected) + return IGNORED; + + _advance_step(); + _execute_curr_step(); + return EXPECTED; + } + + /* + * Timer handling + */ + Signal_handler
_timer_handler { _env.ep(), *this, &Main::_handle_timer }; + + void _handle_timer() + { + if (_curr_step_xml().type() != "sleep") { + error("got spurious timeout signal"); + throw Exception(); + } + + _advance_step(); + _execute_curr_step(); + } + + /* + * LOG service provided to init + */ + Sliced_heap _sliced_heap { _env.ram(), _env.rm() }; + + Log_root _log_root { _env.ep(), _sliced_heap, *this }; + + + Main(Env &env) : _env(env) + { + _timer.sigh(_timer_handler); + _init_config_reporter.enabled(true); + _init_state.sigh(_init_state_handler); + _execute_curr_step(); + + _env.parent().announce(_env.ep().manage(_log_root)); + } +}; + + +void Component::construct(Genode::Env &env) { static Test::Main main(env); } + diff --git a/repos/os/src/test/init/target.mk b/repos/os/src/test/init/target.mk new file mode 100644 index 0000000000..cd7d34b848 --- /dev/null +++ b/repos/os/src/test/init/target.mk @@ -0,0 +1,3 @@ +TARGET = test-init +SRC_CC = main.cc +LIBS += base