depot_autiopilot: consider log_prefix attribute

The new 'log_prefix' attribute is effective when used in a tests runtime in
<succeed> or <fail> tags that have a non-empty content string. When matching
the log against the pattern given in the affected <succeed> or <fail> tag, the
Depot Autopilot will consider only those test-log lines that start with the
given prefix.

Ref #4922
This commit is contained in:
Martin Stein 2023-06-27 12:13:42 +02:00 committed by Christian Helmuth
parent c47a6b0830
commit 987dea5f7f
3 changed files with 103 additions and 12 deletions

View File

@ -225,6 +225,13 @@ Besides the mandatory package content, a test package is expected to provide a
characters '<', '&', '*' in the pattern must be escaped as "&lt;", "&amp;", characters '<', '&', '*' in the pattern must be escaped as "&lt;", "&amp;",
"&#42;". A character '*' in the pattern is treated as non-greedy wildcard. "&#42;". A character '*' in the pattern is treated as non-greedy wildcard.
:<fail log_prefix="[init -> test]">Error!</fail>:
:<succeed log_prefix="[init -> test]">Done!</succeed>:
When matching the log against the pattern given in the <succeed> or
<fail> tag, the Depot Autopilot will consider only those test-log lines that
start with the given prefix.
:<content>: :<content>:
Lists required files from the test-package build besides the root-component Lists required files from the test-package build besides the root-component

View File

@ -78,14 +78,15 @@ static Filter const *filter_to_apply(FILTERS const &filters,
static size_t sanitize_pattern(char *const base, static size_t sanitize_pattern(char *const base,
size_t size) size_t size)
{ {
static Filters<5> pattern_filters static Filters<6> pattern_filters
{ {
{ {
{ "\x9", "" }, { "\x9", "" },
{ "\xa", "" }, { "\xa", "" },
{ "&lt;", "<" }, { "&lt;", "<" },
{ "&amp;", "&" }, { "&amp;", "&" },
{ "&#42;", "*" } { "&#42;", "*" },
{ "&quot;", "\"" }
} }
}; };
struct Bad_filter : Exception { }; struct Bad_filter : Exception { };
@ -984,7 +985,11 @@ bool Log_event::handle_log_update(Expanding_string const &log_str)
{ {
while (true) { while (true) {
/* determine current pattern chunk */ /*
* Determine the log pattern chunk that covers the point defined
* by the current value of _pattern_offset. I.e., the first chunk of
* the pattern that could not be fully matched against the log yet.
*/
Plain_string const *pattern_chunk { nullptr }; Plain_string const *pattern_chunk { nullptr };
size_t pattern_chunk_offset { _pattern_offset }; size_t pattern_chunk_offset { _pattern_offset };
_plain_strings.for_each([&] (Plain_string const &chunk) { _plain_strings.for_each([&] (Plain_string const &chunk) {
@ -997,13 +1002,21 @@ bool Log_event::handle_log_update(Expanding_string const &log_str)
pattern_chunk = &chunk; pattern_chunk = &chunk;
} }
}); });
/*
* If there is nothing left to match, stop and return that the event
* was just triggered.
*/
if (!pattern_chunk) { if (!pattern_chunk) {
return true; return true;
} }
/* get the range of yet unmatched bytes inside the pattern chunk */
char const *pattern_chunk_curr { pattern_chunk->base() + pattern_chunk_offset }; char const *pattern_chunk_curr { pattern_chunk->base() + pattern_chunk_offset };
size_t const pattern_chunk_left { pattern_chunk->size() - pattern_chunk_offset }; size_t const pattern_chunk_left { pattern_chunk->size() - pattern_chunk_offset };
/* determine current log chunk */ /*
* Determine the buffered log chunk that covers the point
* defined by the current value of _log_offset.
*/
Expanding_string::Chunk const *log_chunk { nullptr }; Expanding_string::Chunk const *log_chunk { nullptr };
size_t log_chunk_offset { _log_offset }; size_t log_chunk_offset { _log_offset };
log_str.for_each_chunk([&] (Expanding_string::Chunk const &chunk) { log_str.for_each_chunk([&] (Expanding_string::Chunk const &chunk) {
@ -1016,19 +1029,53 @@ bool Log_event::handle_log_update(Expanding_string const &log_str)
log_chunk = &chunk; log_chunk = &chunk;
} }
}); });
/*
* If there is no log left to process, stop and return that the event
* was not yet triggered.
*/
if (!log_chunk) { if (!log_chunk) {
return false; return false;
} }
if (_log_prefix_valid) {
/*
* If the log chunk doesn't start with the log prefix configured
* for this event, completely ignore the chunk.
*/
if (memcmp(log_chunk->base(), _log_prefix.string(), _log_prefix.length() - 1)) {
_log_offset += log_chunk->size();
continue;
}
}
/* get the range of yet unprocessed bytes inside the log chunk */
char const *log_chunk_curr { log_chunk->base() + log_chunk_offset }; char const *log_chunk_curr { log_chunk->base() + log_chunk_offset };
size_t const log_chunk_left { log_chunk->size() - log_chunk_offset }; size_t const log_chunk_left { log_chunk->size() - log_chunk_offset };
/* compare log with pattern */ /*
* Compare the yet unmatched pattern bytes to the yet unprocessed log
* bytes advance .
*/
size_t const cmp_size { min(log_chunk_left, pattern_chunk_left) }; size_t const cmp_size { min(log_chunk_left, pattern_chunk_left) };
if (memcmp(pattern_chunk_curr, log_chunk_curr, cmp_size)) { if (memcmp(pattern_chunk_curr, log_chunk_curr, cmp_size)) {
/*
* If the offset into the pattern chunk is > 0, this means that
* the chunk could be matched partially against the less advanced
* log buffer during the last update. If the remaining bytes now
* fail to match against the just arrived subsequent log bytes,
* we must discard the partial match and try to match the whole
* chunk again. Note that it is correct to then increase the log
* offset in any case because continuing with the partial match
* would not have failed if (_log_offset - pattern_chunk_offset)
* would point to a match for the whole pattern chunk.
*/
_pattern_offset -= pattern_chunk_offset; _pattern_offset -= pattern_chunk_offset;
_log_offset -= pattern_chunk_offset; _log_offset -= pattern_chunk_offset;
_log_offset += 1; _log_offset += 1;
} else { } else {
_pattern_offset += cmp_size; _pattern_offset += cmp_size;
_log_offset += cmp_size; _log_offset += cmp_size;
} }
@ -1064,11 +1111,31 @@ Log_event::~Log_event()
} }
Log_prefix Log_event::_init_log_prefix(Xml_node const &xml)
{
if (!xml.has_attribute("log_prefix"))
return Log_prefix { };
char buf[Log_prefix::size()];
size_t buf_str_size { 0 };
xml.attribute("log_prefix").with_raw_value([&] (char const *src_ptr, size_t src_size) {
size_t const cpy_size = min(src_size, sizeof(buf) - 1);
memcpy(buf, src_ptr, cpy_size);
buf[cpy_size] = 0;
buf_str_size = sanitize_pattern(buf, cpy_size + 1);
});
return Cstring { buf, buf_str_size };
}
Log_event::Log_event(Allocator &alloc, Log_event::Log_event(Allocator &alloc,
Xml_node const &xml) Xml_node const &xml)
: :
Event { xml, Type::LOG }, Event { xml, Type::LOG },
_alloc { alloc } _alloc { alloc },
_log_prefix { _init_log_prefix(xml) },
_log_prefix_valid { _log_prefix != Log_prefix { } }
{ {
char const *const base { xml_content_base(xml) }; char const *const base { xml_content_base(xml) };
size_t const size { xml_content_size(xml) }; size_t const size { xml_content_size(xml) };

View File

@ -45,6 +45,8 @@ namespace Depot_deploy {
void print(Genode::Output &output) const; void print(Genode::Output &output) const;
}; };
using Log_prefix = String<256>;
class Child; class Child;
class Event; class Event;
class Timeout_event; class Timeout_event;
@ -169,17 +171,32 @@ class Depot_deploy::Log_event : public Event,
} }
}; };
Genode::Allocator &_alloc; Genode::Allocator &_alloc;
size_t _log_offset { 0 };
size_t _pattern_offset { 0 };
Genode::Fifo<Plain_string> _plain_strings { };
void _replace_wildcards_with_0(); /*
* Defines a point inside the concatenation of all chunks of the
* buffered log. Up to that point the buffered log has been processed
* by this log event already.
*/
size_t _log_offset { 0 };
/*
* Defines a point inside the concatenation of all chunks of the log
* pattern of this event. Up to that point the pattern could be
* successfully matched against the log so far.
*/
size_t _pattern_offset { 0 };
Genode::Fifo<Plain_string> _plain_strings { };
Log_prefix const _log_prefix;
bool const _log_prefix_valid;
Log_event(Log_event const &); Log_event(Log_event const &);
Log_event const & operator=(const Log_event&); Log_event const & operator=(const Log_event&);
Log_prefix _init_log_prefix(Xml_node const &xml);
public: public:
Log_event(Allocator &alloc, Log_event(Allocator &alloc,