aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/test/script/script.hxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-07-04 19:12:15 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-07-05 14:24:43 +0300
commit57b10c06925d0bdf6ffb38488ee908f085109e95 (patch)
treef2103684d319650c3302aef9d7a70dd64ff2a347 /libbuild2/test/script/script.hxx
parent30b4eda196e090aa820d312e6a9435a4ae84c303 (diff)
Move config, dist, test, and install modules into library
Diffstat (limited to 'libbuild2/test/script/script.hxx')
-rw-r--r--libbuild2/test/script/script.hxx559
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