// file : build2/test/script/script -*- C++ -*- // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUILD2_TEST_SCRIPT_SCRIPT #define BUILD2_TEST_SCRIPT_SCRIPT #include #include #include #include #include // 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 {variable, setup, tdown, test}; struct line { line_type type; replay_tokens tokens; }; using lines = vector; // Parse object model. // enum class redirect_type { none, pass, null, merge, here_string, here_document, file }; struct redirect { redirect_type type; struct doc_type { string doc; // Note: includes trailing newline, if required. string end; }; struct file_type { using path_type = build2::path; path_type path; bool append = false; }; union { int fd; // Merge-to descriptor. string str; // Note: includes trailing newline, if required. doc_type doc; file_type file; }; explicit redirect (redirect_type = redirect_type::none); redirect (redirect&&); redirect (const redirect&); redirect& operator= (redirect&&); redirect& operator= (const redirect&); ~redirect (); }; 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 status; }; struct command { path program; strings arguments; redirect in; redirect out; redirect err; paths 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&); struct description { string id; string summary; string details; bool empty () const { return id.empty () && summary.empty () && details.empty (); } }; class script; class scope { public: scope* const parent; // NULL for the root (script) scope. script* const root; // Self for the root (script) scope. // 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 desc; // Files and directories that must be automatically cleaned up when // the scope is left. If the path contains '*' it is a wildcard. If the // path is not a wildcard and ends with a trailing slash, then it is // assumed to be to a directory, otherwise -- to a file. A directory // must be empty by the time of removal, // // The supported wildcards: // // dir/*** - remove directory 'dir' with all files and sub-directories // recursively. Removing non-existing 'dir' is not an error. // // The not yet supported wildcards: // // &dir/* - remove all immediate files of directory 'dir'; // &dir/*/ - remove all immediate sub-directories (must be empty); // &dir/** - remove all files recursively; // &dir/**/ - remove all sub-directories recursively (must be empty by // the time of removal). // paths 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; // 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&); // Register path for cleanup. Suppress duplicates. // void clean (path p); public: virtual ~scope () = default; protected: scope (const string& id, scope* parent); // Pre-parse data. // private: friend class parser; location start_loc_; location end_loc_; }; class group: public scope { public: vector> scopes; public: group (const string& id, group& p): scope (id, &p) {} protected: group (const string& id): scope (id, nullptr) {} // For root. // Pre-parse data. // private: friend class parser; bool empty () const { return scopes.empty () && setup_.empty () && tdown_.empty (); } lines setup_; lines tdown_; }; class test: public scope { public: test (const string& id, group& p): scope (id, &p) {} // Pre-parse data. // private: friend class parser; lines tests_; }; class script_base // Make sure certain things are initialized early. { protected: script_base (); public: variable_pool var_pool; const variable& test_var; // test const variable& opts_var; // test.options const variable& args_var; // test.arguments const variable& cmd_var; // $* const variable& wd_var; // $~ const variable& id_var; // $@ }; class script: public script_base, public group { public: script (target& test_target, testscript& script_target, const dir_path& root_wd); public: target& test_target; // Target we are testing. testscript& script_target; // Target of the testscript file. }; } } } #include #endif // BUILD2_TEST_SCRIPT_SCRIPT