From 5c186f901ea7d1b09ab551a3fd2a6c1fd2426d59 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 20 Jun 2017 20:33:52 +0300 Subject: Add support for build configuration warning-detecting regexes --- libbbot/build-config.cxx | 24 ++++++-- libbbot/build-config.hxx | 6 +- libbbot/manifest.cxx | 150 ++++++++++++++++++++++++++++++++--------------- libbbot/manifest.hxx | 21 ++++++- tests/manifest/task.test | 32 +++++++++- 5 files changed, 176 insertions(+), 57 deletions(-) diff --git a/libbbot/build-config.cxx b/libbbot/build-config.cxx index 071ad7f..f18c48a 100644 --- a/libbbot/build-config.cxx +++ b/libbbot/build-config.cxx @@ -75,14 +75,15 @@ namespace bbot continue; } - // If the third field doesn't contain '=' character, then we will treat - // it as a target. + // If the third field doesn't start with '~' character and doesn't + // contain '=' character, then we will treat it as a target. // - if (tl[i].value.find ('=') == string::npos) + const string& v (tl[i].value); + if (v[0] != '~' && v.find ('=') == string::npos) { try { - config.target = target_triplet (tl[i].value); + config.target = target_triplet (v); } catch (const invalid_argument& e) { @@ -96,8 +97,19 @@ namespace bbot { for (; i < n; ++i) { - task_manifest::check_config (tl[i].value); - config.vars.emplace_back (move (tl[i].value)); + string& v (tl[i].value); + + if (v[0] == '~') // Regular expression. + { + string re (v, 1); + task_manifest::check_regex (re); + config.warning_regexes.emplace_back (move (re)); + } + else // Configuration variable. + { + task_manifest::check_config (v); + config.vars.emplace_back (move (v)); + } } } catch (const invalid_argument& e) diff --git a/libbbot/build-config.hxx b/libbbot/build-config.hxx index 2e96e03..363b889 100644 --- a/libbbot/build-config.hxx +++ b/libbbot/build-config.hxx @@ -30,6 +30,10 @@ namespace bbot butl::optional target; std::vector vars; + + // Warning-detecting regular expressions. + // + std::vector warning_regexes; }; using build_configs = std::vector; @@ -39,7 +43,7 @@ namespace bbot // // buildtab consists of lines in the following format: // - // [] [] + // [] [] [] // using butl::tab_parsing; diff --git a/libbbot/manifest.cxx b/libbbot/manifest.cxx index 56c5f1d..dae6e3e 100644 --- a/libbbot/manifest.cxx +++ b/libbbot/manifest.cxx @@ -4,6 +4,7 @@ #include +#include #include #include #include // isxdigit() @@ -14,6 +15,7 @@ #include // uint64_t #include // invalid_argument +#include #include #include // digit() #include @@ -33,8 +35,6 @@ namespace bbot using serialization = manifest_serialization; using name_value = manifest_name_value; - using strings = vector; - // result_status // string @@ -390,6 +390,57 @@ namespace bbot if (nv.value != "1") bad_value ("unsupported format version"); + // Parse value represented as a whitespace-separated list of quoted + // strings (quoting is validated and preserved) and validate each string + // with the function specified. + // + auto parse_tab = [&bad_value] ( + const string& value, + const function& check, + const string& what) -> strings + { + strings r; + + // Note that when reporting errors we combine the manifest value + // position with the respective field and error positions. + // + try + { + istringstream is (value); + tab_parser parser (is, ""); + + // Here we naturally support multiline value manifest. + // + tab_fields tl; + while (!(tl = parser.next ()).empty ()) + { + for (auto& tf: tl) + { + try + { + check (tf.value); + } + catch (const invalid_argument& e) + { + bad_value (string ("invalid task ") + what + ": " + e.what (), + tf.column - 1, + tl.line - 1); + } + + r.emplace_back (move (tf.value)); + } + } + } + catch (const tab_parsing& e) + { + bad_value ("invalid task " + what + ": " + e.description, + e.column - 1, + e.line - 1); + } + + return r; + }; + // Parse the task manifest. // for (nv = p.next (); !nv.empty (); nv = p.next ()) @@ -483,46 +534,21 @@ namespace bbot if (!config.empty ()) bad_name ("task configuration redefinition"); - // Note that when reporting errors we combine the manifest value - // position with the respective field and error positions. - // - try - { - istringstream is (v); - tab_parser parser (is, ""); - - // Here we naturally support multiline config manifest. - // - tab_fields tl; - while (!(tl = parser.next ()).empty ()) - { - for (auto& tf: tl) - { - try - { - check_config (tf.value); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task configuration: ") + e.what (), - tf.column - 1, - tl.line - 1); - } - - config.emplace_back (move (tf.value)); - } - } - } - catch (const tab_parsing& e) - { - bad_value ("invalid task configuration: " + e.description, - e.column - 1, - e.line - 1); - } + config = parse_tab (v, check_config, "configuration"); if (config.empty ()) bad_value ("empty task configuration"); } + else if (n == "warning-regex") + { + if (!warning_regex.empty ()) + bad_name ("task warning regex redefinition"); + + warning_regex = parse_tab (v, check_regex, "warning regex"); + + if (warning_regex.empty ()) + bad_value ("empty task warning regex"); + } else if (!iu) bad_name ("unknown name '" + n + "' in task manifest"); } @@ -561,21 +587,28 @@ namespace bbot if (target) s.next ("target", target->string ()); - // Recompose config string as a space-separated variable list, + // Serialize an optional value of the strings type as a space-separated + // string list. // - if (!config.empty ()) + auto serialize_list = [&s] (const char* name, const strings& value) { - string v; - for (auto b (config.cbegin ()), i (b), e (config.cend ()); i != e; ++i) + if (!value.empty ()) { - if (i != b) - v += ' '; + string v; + for (auto b (value.cbegin ()), i (b), e (value.cend ()); i != e; ++i) + { + if (i != b) + v += ' '; - v += *i; + v += *i; + } + + s.next (name, v); } + }; - s.next ("config", v); - } + serialize_list ("config", config); + serialize_list ("warning-regex", warning_regex); s.next ("", ""); // End of manifest. } @@ -586,6 +619,12 @@ namespace bbot return string_parser::unquote (config); } + strings task_manifest:: + unquoted_warning_regex () const + { + return string_parser::unquote (warning_regex); + } + void task_manifest:: check_config (const string& s) { @@ -609,6 +648,23 @@ namespace bbot throw invalid_argument ("no variable value"); } + void task_manifest:: + check_regex (const string& s) + { + try + { + regex re (string_parser::unquote (s)); + } + catch (const regex_error& e) + { + // Print regex_error description if meaningful (no space). + // + ostringstream os; + os << "invalid regex" << e; + throw invalid_argument (os.str ()); + } + } + // task_response_manifest // task_response_manifest:: diff --git a/libbbot/manifest.hxx b/libbbot/manifest.hxx index b981ab4..36f2f5b 100644 --- a/libbbot/manifest.hxx +++ b/libbbot/manifest.hxx @@ -131,23 +131,34 @@ namespace bbot // strings config; + // Regular expressions for detecting warnings in the operation result logs + // (in addition to the default list of expressions). + // Note: could be quoted. + // + strings warning_regex; + strings unquoted_config () const; + strings + unquoted_warning_regex () const; + task_manifest (std::string nm, bpkg::version vr, bpkg::repository_location rl, strings tr, std::string mn, butl::optional tg, - strings cf) + strings cf, + strings wr) : name (std::move (nm)), version (std::move (vr)), repository (std::move (rl)), trust (tr), machine (std::move (mn)), target (std::move (tg)), - config (std::move (cf)) {} + config (std::move (cf)), + warning_regex (std::move (wr)){} public: task_manifest () = default; // VC export. @@ -165,6 +176,12 @@ namespace bbot // static void check_config (const std::string&); + + // Check that a string is a valid (ECMAScript) regular expression. Throw + // invalid_argument if that's not the case. + // + static void + check_regex (const std::string&); }; class LIBBBOT_EXPORT task_response_manifest diff --git a/tests/manifest/task.test b/tests/manifest/task.test index f2afd9c..95bb749 100644 --- a/tests/manifest/task.test +++ b/tests/manifest/task.test @@ -21,6 +21,7 @@ test.options += -t machine: windows_10-msvc_14 target: x86_64-microsoft-win32-msvc14.0 config: config.cc.coptions=/Z7 config.cc.loptions=/DEBUG + warning-regex: '^warning: ' '^.+: warning: ' EOF : no-target @@ -32,9 +33,10 @@ test.options += -t repository: http://pkg.example.org/1/math machine: windows_10-msvc_14 config: config.cc.coptions=/Z7 config.cc.loptions=/DEBUG + warning-regex: '^warning: ' '^.+: warning: ' EOF - : no-config + : no-config-no-regex : $* <>EOF : 1 @@ -135,6 +137,14 @@ test.options += -t config: config.cc.coptions=/Z7 config: config.cc.loptions=/DEBUG EOI + + : warning-regex + : + $* <'stdin:3:1: error: task warning regex redefinition' == 1 + : 1 + warning-regex: '^warning: ' + warning-regex: '^.+: warning: ' + EOI } : invalid @@ -237,6 +247,26 @@ test.options += -t } } + : warning-regex + : + { + : empty + : + $* <'stdin:2:15: error: empty task warning regex' == 1 + : 1 + warning-regex: + EOI + + : regex-error + : + $* <>~/EOE/ == 1 + : 1 + warning-regex: '^[warning: ' + EOI + /stdin:2:16: error: invalid task warning regex: invalid regex.*/ + EOE + } + : trust : $* <'stdin:2:8: error: invalid repository certificate fingerprint' == 1 -- cgit v1.1