aboutsummaryrefslogtreecommitdiff
path: root/build2/test/script/script
blob: 9ce352d0e88c824f2b95e866ac9efa4053abfc15 (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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// 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, setup, tdown, test};

      struct line
      {
        line_type type;
        replay_tokens tokens;
      };

      using lines = vector<line>;

      // 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<description> desc;

        // Files and directories 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 (removing non-existent directory is not an
        //           error)
        //
        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<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;

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

#endif // BUILD2_TEST_SCRIPT_SCRIPT