/* * \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 Affero General Public License version 3. */ #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()); if (condition.has_attribute("value")) { Value const value = condition.attribute_value("value", Value()); return node.attribute_value(name.string(), Value()) == value; } if (condition.has_attribute("higher")) { size_t const value = condition.attribute_value("higher", Number_of_bytes()); return (size_t)node.attribute_value(name.string(), Number_of_bytes()) > value; } if (condition.has_attribute("lower")) { size_t const value = condition.attribute_value("lower", Number_of_bytes()); return (size_t)node.attribute_value(name.string(), Number_of_bytes()) < value; } error("missing condition in node"); return false; } /** * 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; } if (condition.type() == "not") matches = matches && !xml_matches(condition, node); }); return matches; } struct Test::Log_message_handler : Interface { 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) override { /* strip known line delimiter from incoming message */ unsigned n = 0; static char const delim[] = "\033[0m\n"; static Genode::String const pattern(delim); 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 &) override { 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 }; bool _timer_scheduled = false; 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); node.with_raw_content([&] (char const *start, size_t length) { xml.append(start, length); }); }); } 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") { _expect_log_msg = _curr_step_xml().attribute_value("string", Log_message_handler::Message()); _expect_log = true; 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") { if (!_timer_scheduled) { uint64_t const timeout_ms = step.attribute_value("ms", (uint64_t)250); _timer.trigger_once(timeout_ms*1000); _timer_scheduled = true; } return; } error("unexpected step: ", step); throw Exception(); } } /** * Log_message_handler interface */ Result handle_log_message(Log_message_handler::Message const &message) override { if (!_expect_log) return IGNORED; if (message != _expect_log_msg) return IGNORED; _expect_log = false; _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(); } _timer_scheduled = false; _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 }; bool _expect_log = false; Log_message_handler::Message _expect_log_msg { }; 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); }