aboutsummaryrefslogtreecommitdiff
path: root/build2/test/script/script
blob: f1a3f50ad43814524f932bd3263b9ae8e9d355f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// 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 <build2/types>
#include <build2/utility>

#include <build2/variable>

#include <build2/test/target>

#include <build2/test/script/token> // 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, test};

      struct line
      {
        line_type type;
        replay_tokens tokens;
      };

      using lines = vector<line>;

      // Parse object model.
      //
      enum class redirect_type
      {
        none,
        null,
        here_string,  // Value is the string.
        here_document // Value is the document.
      };

      struct redirect
      {
        redirect_type type = redirect_type::none;
        string value;    // Note: includes trailing newline, if required.
        string here_end; // Only for here-documents.
      };

      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;

        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&);

      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).

        // 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&);

      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<unique_ptr<scope>> 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;

        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 <build2/test/script/script.ixx>

#endif // BUILD2_TEST_SCRIPT_SCRIPT