diff options
Diffstat (limited to 'libbuild2/test/script/script.hxx')
-rw-r--r-- | libbuild2/test/script/script.hxx | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/libbuild2/test/script/script.hxx b/libbuild2/test/script/script.hxx new file mode 100644 index 0000000..e3f8251 --- /dev/null +++ b/libbuild2/test/script/script.hxx @@ -0,0 +1,559 @@ +// file : libbuild2/test/script/script.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_TEST_SCRIPT_SCRIPT_HXX +#define LIBBUILD2_TEST_SCRIPT_SCRIPT_HXX + +#include <set> + +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/variable.hxx> + +#include <libbuild2/test/target.hxx> + +#include <libbuild2/test/script/token.hxx> // replay_tokens + +namespace build2 +{ + class target; + + namespace test + { + namespace script + { + class parser; // Required by VC for 'friend class parser' declaration. + + // Pre-parse representation. + // + + enum class line_type + { + var, + cmd, + cmd_if, + cmd_ifn, + cmd_elif, + cmd_elifn, + cmd_else, + cmd_end + }; + + ostream& + operator<< (ostream&, line_type); + + struct line + { + line_type type; + replay_tokens tokens; + + union + { + const variable* var; // Pre-entered for line_type::var. + }; + }; + + // Most of the time we will have just one line (test command). + // + using lines = small_vector<line, 1>; + + // Parse object model. + // + + // redirect + // + enum class redirect_type + { + none, + pass, + null, + trace, + merge, + here_str_literal, + here_str_regex, + here_doc_literal, + here_doc_regex, + here_doc_ref, // Reference to here_doc literal or regex. + file, + }; + + // Pre-parsed (but not instantiated) regex lines. The idea here is that + // we should be able to re-create their (more or less) exact text + // representation for diagnostics but also instantiate without any + // re-parsing. + // + struct regex_line + { + // If regex is true, then value is the regex expression. Otherwise, it + // is a literal. Note that special characters can be present in both + // cases. For example, //+ is a regex, while /+ is a literal, both + // with '+' as a special character. Flags are only valid for regex. + // Literals falls apart into textual (has no special characters) and + // special (has just special characters instead) ones. For example + // foo is a textual literal, while /.+ is a special one. Note that + // literal must not have value and special both non-empty. + // + bool regex; + + string value; + string flags; + string special; + + uint64_t line; + uint64_t column; + + // Create regex with optional special characters. + // + regex_line (uint64_t l, uint64_t c, + string v, string f, string s = string ()) + : regex (true), + value (move (v)), + flags (move (f)), + special (move (s)), + line (l), + column (c) {} + + // Create a literal, either text or special. + // + regex_line (uint64_t l, uint64_t c, string v, bool s) + : regex (false), + value (s ? string () : move (v)), + special (s ? move (v) : string ()), + line (l), + column (c) {} + }; + + struct regex_lines + { + char intro; // Introducer character. + string flags; // Global flags (here-document). + + small_vector<regex_line, 8> lines; + }; + + // Output file redirect mode. + // + enum class redirect_fmode + { + compare, + overwrite, + append + }; + + struct redirect + { + redirect_type type; + + struct file_type + { + using path_type = build2::path; + path_type path; + redirect_fmode mode; // Meaningless for input redirect. + }; + + union + { + int fd; // Merge-to descriptor. + string str; // Note: with trailing newline, if requested. + regex_lines regex; // Note: with trailing blank, if requested. + file_type file; + reference_wrapper<const redirect> ref; // Note: no chains. + }; + + string modifiers; // Redirect modifiers. + string end; // Here-document end marker (no regex intro/flags). + uint64_t end_line; // Here-document end marker location. + uint64_t end_column; + + // Create redirect of a type other than reference. + // + explicit + redirect (redirect_type = redirect_type::none); + + // Create redirect of the reference type. + // + redirect (redirect_type t, const redirect& r) + : type (redirect_type::here_doc_ref), ref (r) + { + // There is no support (and need) for reference chains. + // + assert (t == redirect_type::here_doc_ref && + r.type != redirect_type::here_doc_ref); + } + + // Move constuctible/assignable-only type. + // + redirect (redirect&&); + redirect& operator= (redirect&&); + + ~redirect (); + + const redirect& + effective () const noexcept + { + return type == redirect_type::here_doc_ref ? ref.get () : *this; + } + }; + + // cleanup + // + enum class cleanup_type + { + always, // &foo - cleanup, fail if does not exist. + maybe, // &?foo - cleanup, ignore if does not exist. + never // &!foo - don’t cleanup, ignore if doesn’t exist. + }; + + // File or directory to be automatically cleaned up at the end of the + // scope. If the path ends with a trailing slash, then it is assumed to + // be a directory, otherwise -- a file. A directory that is about to be + // cleaned up must be empty. + // + // The last component in the path may contain a wildcard that have the + // following semantics: + // + // dir/* - remove all immediate files + // dir/*/ - remove all immediate sub-directories (must be empty) + // dir/** - remove all files recursively + // dir/**/ - remove all sub-directories recursively (must be empty) + // dir/*** - remove directory dir with all files and sub-directories + // recursively + // + struct cleanup + { + cleanup_type type; + build2::path path; + }; + using cleanups = vector<cleanup>; + + // command_exit + // + enum class exit_comparison {eq, ne}; + + struct command_exit + { + // C/C++ don't apply constraints on program exit code other than it + // being of type int. + // + // POSIX specifies that only the least significant 8 bits shall be + // available from wait() and waitpid(); the full value shall be + // available from waitid() (read more at _Exit, _exit Open Group + // spec). + // + // While the Linux man page for waitid() doesn't mention any + // deviations from the standard, the FreeBSD implementation (as of + // version 11.0) only returns 8 bits like the other wait*() calls. + // + // Windows supports 32-bit exit codes. + // + // Note that in shells some exit values can have special meaning so + // using them can be a source of confusion. For bash values in the + // [126, 255] range are such a special ones (see Appendix E, "Exit + // Codes With Special Meanings" in the Advanced Bash-Scripting Guide). + // + exit_comparison comparison; + uint8_t code; + }; + + // command + // + struct command + { + path program; + strings arguments; + + redirect in; + redirect out; + redirect err; + + script::cleanups cleanups; + + command_exit exit {exit_comparison::eq, 0}; + }; + + enum class command_to_stream: uint16_t + { + header = 0x01, + here_doc = 0x02, // Note: printed on a new line. + all = header | here_doc + }; + + void + to_stream (ostream&, const command&, command_to_stream); + + ostream& + operator<< (ostream&, const command&); + + // command_pipe + // + using command_pipe = vector<command>; + + void + to_stream (ostream&, const command_pipe&, command_to_stream); + + ostream& + operator<< (ostream&, const command_pipe&); + + // command_expr + // + enum class expr_operator {log_or, log_and}; + + struct expr_term + { + expr_operator op; // OR-ed to an implied false for the first term. + command_pipe pipe; + }; + + using command_expr = vector<expr_term>; + + void + to_stream (ostream&, const command_expr&, command_to_stream); + + ostream& + operator<< (ostream&, const command_expr&); + + // command_type + // + enum class command_type {test, setup, teardown}; + + // description + // + struct description + { + string id; + string summary; + string details; + + bool + empty () const + { + return id.empty () && summary.empty () && details.empty (); + } + }; + + // scope + // + class script; + + enum class scope_state {unknown, passed, failed}; + + class scope + { + public: + scope* const parent; // NULL for the root (script) scope. + script* const root; // Self for the root (script) scope. + + // The chain of if-else scope alternatives. See also if_cond_ below. + // + unique_ptr<scope> if_chain; + + // Note that if we pass the variable name as a string, then it will + // be looked up in the wrong pool. + // + variable_map vars; + + const path& id_path; // Id path ($@, relative in POSIX form). + const dir_path& wd_path; // Working dir ($~, absolute and normalized). + + optional<description> desc; + + scope_state state = scope_state::unknown; + test::script::cleanups cleanups; + paths special_cleanups; + + // Variables. + // + public: + // Lookup the variable starting from this scope, continuing with outer + // scopes, then the target being tested, then the testscript target, + // and then outer buildfile scopes (including testscript-type/pattern + // specific). + // + lookup + find (const variable&) const; + + // As above but only look for buildfile variables. If target_only is + // false then also look in scopes of the test target (this should only + // be done if the variable's visibility is target). + // + lookup + find_in_buildfile (const string&, bool target_only = true) const; + + // Return a value suitable for assignment. If the variable does not + // exist in this scope's map, then a new one with the NULL value is + // added and returned. Otherwise the existing value is returned. + // + value& + assign (const variable& var) {return vars.assign (var);} + + // Return a value suitable for append/prepend. If the variable does + // not exist in this scope's map, then outer scopes are searched for + // the same variable. If found then a new variable with the found + // value is added to this scope and returned. Otherwise this function + // proceeds as assign() above. + // + value& + append (const variable&); + + // Reset special $*, $N variables based on the test.* values. + // + void + reset_special (); + + // Cleanup. + // + public: + // Register a cleanup. If the cleanup is explicit, then override the + // cleanup type if this path is already registered. Ignore implicit + // registration of a path outside script working directory. + // + void + clean (cleanup, bool implicit); + + // Register cleanup of a special file. Such files are created to + // maintain testscript machinery and must be removed first, not to + // interfere with the user-defined wildcard cleanups. + // + void + clean_special (path p); + + public: + virtual + ~scope () = default; + + protected: + scope (const string& id, scope* parent, script* root); + + // Pre-parse data. + // + public: + virtual bool + empty () const = 0; + + protected: + friend class parser; + + location start_loc_; + location end_loc_; + + optional<line> if_cond_; + }; + + // group + // + class group: public scope + { + public: + vector<unique_ptr<scope>> scopes; + + public: + group (const string& id, group& p): scope (id, &p, p.root) {} + + protected: + group (const string& id, script* r): scope (id, nullptr, r) {} + + // Pre-parse data. + // + public: + virtual bool + empty () const override + { + return + !if_cond_ && // The condition expression can have side-effects. + setup_.empty () && + tdown_.empty () && + find_if (scopes.begin (), scopes.end (), + [] (const unique_ptr<scope>& s) + { + return !s->empty (); + }) == scopes.end (); + } + + private: + friend class parser; + + lines setup_; + lines tdown_; + }; + + // test + // + class test: public scope + { + public: + test (const string& id, group& p): scope (id, &p, p.root) {} + + // Pre-parse data. + // + public: + virtual bool + empty () const override + { + return tests_.empty (); + } + + private: + friend class parser; + + lines tests_; + }; + + // script + // + class script_base // Make sure certain things are initialized early. + { + protected: + script_base (); + + public: + variable_pool var_pool; + mutable shared_mutex var_pool_mutex; + + const variable& test_var; // test + const variable& options_var; // test.options + const variable& arguments_var; // test.arguments + const variable& redirects_var; // test.redirects + const variable& cleanups_var; // test.cleanups + + const variable& wd_var; // $~ + const variable& id_var; // $@ + const variable& cmd_var; // $* + const variable* cmdN_var[10]; // $N + }; + + class script: public script_base, public group + { + public: + script (const target& test_target, + const testscript& script_target, + const dir_path& root_wd); + + script (script&&) = delete; + script (const script&) = delete; + script& operator= (script&&) = delete; + script& operator= (const script&) = delete; + + public: + const target& test_target; // Target we are testing. + const build2::scope& target_scope; // Base scope of test target. + const testscript& script_target; // Target of the testscript file. + + // Pre-parse data. + // + private: + friend class parser; + + // Testscript file paths. Specifically, replay_token::file points to + // these paths. + // + std::set<path> paths_; + }; + } + } +} + +#include <libbuild2/test/script/script.ixx> + +#endif // LIBBUILD2_TEST_SCRIPT_SCRIPT_HXX |