xref: /freebsd/contrib/kyua/engine/kyuafile.cpp (revision b0d29bc4)
1*b0d29bc4SBrooks Davis // Copyright 2010 The Kyua Authors.
2*b0d29bc4SBrooks Davis // All rights reserved.
3*b0d29bc4SBrooks Davis //
4*b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5*b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6*b0d29bc4SBrooks Davis // met:
7*b0d29bc4SBrooks Davis //
8*b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer.
10*b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
12*b0d29bc4SBrooks Davis //   documentation and/or other materials provided with the distribution.
13*b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14*b0d29bc4SBrooks Davis //   may be used to endorse or promote products derived from this software
15*b0d29bc4SBrooks Davis //   without specific prior written permission.
16*b0d29bc4SBrooks Davis //
17*b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*b0d29bc4SBrooks Davis 
29*b0d29bc4SBrooks Davis #include "engine/kyuafile.hpp"
30*b0d29bc4SBrooks Davis 
31*b0d29bc4SBrooks Davis #include <algorithm>
32*b0d29bc4SBrooks Davis #include <iterator>
33*b0d29bc4SBrooks Davis #include <stdexcept>
34*b0d29bc4SBrooks Davis 
35*b0d29bc4SBrooks Davis #include <lutok/exceptions.hpp>
36*b0d29bc4SBrooks Davis #include <lutok/operations.hpp>
37*b0d29bc4SBrooks Davis #include <lutok/stack_cleaner.hpp>
38*b0d29bc4SBrooks Davis #include <lutok/state.ipp>
39*b0d29bc4SBrooks Davis 
40*b0d29bc4SBrooks Davis #include "engine/exceptions.hpp"
41*b0d29bc4SBrooks Davis #include "engine/scheduler.hpp"
42*b0d29bc4SBrooks Davis #include "model/metadata.hpp"
43*b0d29bc4SBrooks Davis #include "model/test_program.hpp"
44*b0d29bc4SBrooks Davis #include "utils/config/exceptions.hpp"
45*b0d29bc4SBrooks Davis #include "utils/config/tree.ipp"
46*b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
47*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
48*b0d29bc4SBrooks Davis #include "utils/fs/lua_module.hpp"
49*b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp"
50*b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
51*b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
52*b0d29bc4SBrooks Davis #include "utils/optional.ipp"
53*b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
54*b0d29bc4SBrooks Davis 
55*b0d29bc4SBrooks Davis namespace config = utils::config;
56*b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
57*b0d29bc4SBrooks Davis namespace fs = utils::fs;
58*b0d29bc4SBrooks Davis namespace scheduler = engine::scheduler;
59*b0d29bc4SBrooks Davis 
60*b0d29bc4SBrooks Davis using utils::none;
61*b0d29bc4SBrooks Davis using utils::optional;
62*b0d29bc4SBrooks Davis 
63*b0d29bc4SBrooks Davis 
64*b0d29bc4SBrooks Davis // History of Kyuafile file versions:
65*b0d29bc4SBrooks Davis //
66*b0d29bc4SBrooks Davis // 3 - DOES NOT YET EXIST.  Pending changes for when this is introduced:
67*b0d29bc4SBrooks Davis //
68*b0d29bc4SBrooks Davis //     * Revisit what to do about the test_suite definition.  Support for
69*b0d29bc4SBrooks Davis //       per-test program overrides is deprecated and should be removed.
70*b0d29bc4SBrooks Davis //       But, maybe, the whole test_suite definition idea is wrong and we
71*b0d29bc4SBrooks Davis //       should instead be explicitly telling which configuration variables
72*b0d29bc4SBrooks Davis //       to "inject" into each test program.
73*b0d29bc4SBrooks Davis //
74*b0d29bc4SBrooks Davis // 2 - Changed the syntax() call to take only a version number, instead of the
75*b0d29bc4SBrooks Davis //     word 'config' as the first argument and the version as the second one.
76*b0d29bc4SBrooks Davis //     Files now start with syntax(2) instead of syntax('kyuafile', 1).
77*b0d29bc4SBrooks Davis //
78*b0d29bc4SBrooks Davis // 1 - Initial version.
79*b0d29bc4SBrooks Davis 
80*b0d29bc4SBrooks Davis 
81*b0d29bc4SBrooks Davis namespace {
82*b0d29bc4SBrooks Davis 
83*b0d29bc4SBrooks Davis 
84*b0d29bc4SBrooks Davis static int lua_current_kyuafile(lutok::state&);
85*b0d29bc4SBrooks Davis static int lua_generic_test_program(lutok::state&);
86*b0d29bc4SBrooks Davis static int lua_include(lutok::state&);
87*b0d29bc4SBrooks Davis static int lua_syntax(lutok::state&);
88*b0d29bc4SBrooks Davis static int lua_test_suite(lutok::state&);
89*b0d29bc4SBrooks Davis 
90*b0d29bc4SBrooks Davis 
91*b0d29bc4SBrooks Davis /// Concatenates two paths while avoiding paths to start with './'.
92*b0d29bc4SBrooks Davis ///
93*b0d29bc4SBrooks Davis /// \param root Path to the directory containing the file.
94*b0d29bc4SBrooks Davis /// \param file Path to concatenate to root.  Cannot be absolute.
95*b0d29bc4SBrooks Davis ///
96*b0d29bc4SBrooks Davis /// \return The concatenated path.
97*b0d29bc4SBrooks Davis static fs::path
relativize(const fs::path & root,const fs::path & file)98*b0d29bc4SBrooks Davis relativize(const fs::path& root, const fs::path& file)
99*b0d29bc4SBrooks Davis {
100*b0d29bc4SBrooks Davis     PRE(!file.is_absolute());
101*b0d29bc4SBrooks Davis 
102*b0d29bc4SBrooks Davis     if (root == fs::path("."))
103*b0d29bc4SBrooks Davis         return file;
104*b0d29bc4SBrooks Davis     else
105*b0d29bc4SBrooks Davis         return root / file;
106*b0d29bc4SBrooks Davis }
107*b0d29bc4SBrooks Davis 
108*b0d29bc4SBrooks Davis 
109*b0d29bc4SBrooks Davis /// Implementation of a parser for Kyuafiles.
110*b0d29bc4SBrooks Davis ///
111*b0d29bc4SBrooks Davis /// The main purpose of having this as a class is to keep track of global state
112*b0d29bc4SBrooks Davis /// within the Lua files and allowing the Lua callbacks to easily access such
113*b0d29bc4SBrooks Davis /// data.
114*b0d29bc4SBrooks Davis class parser : utils::noncopyable {
115*b0d29bc4SBrooks Davis     /// Lua state to parse a single Kyuafile file.
116*b0d29bc4SBrooks Davis     lutok::state _state;
117*b0d29bc4SBrooks Davis 
118*b0d29bc4SBrooks Davis     /// Root directory of the test suite represented by the Kyuafile.
119*b0d29bc4SBrooks Davis     const fs::path _source_root;
120*b0d29bc4SBrooks Davis 
121*b0d29bc4SBrooks Davis     /// Root directory of the test programs.
122*b0d29bc4SBrooks Davis     const fs::path _build_root;
123*b0d29bc4SBrooks Davis 
124*b0d29bc4SBrooks Davis     /// Name of the Kyuafile to load relative to _source_root.
125*b0d29bc4SBrooks Davis     const fs::path _relative_filename;
126*b0d29bc4SBrooks Davis 
127*b0d29bc4SBrooks Davis     /// Version of the Kyuafile file format requested by the parsed file.
128*b0d29bc4SBrooks Davis     ///
129*b0d29bc4SBrooks Davis     /// This is set once the Kyuafile invokes the syntax() call.
130*b0d29bc4SBrooks Davis     optional< int > _version;
131*b0d29bc4SBrooks Davis 
132*b0d29bc4SBrooks Davis     /// Name of the test suite defined by the Kyuafile.
133*b0d29bc4SBrooks Davis     ///
134*b0d29bc4SBrooks Davis     /// This is set once the Kyuafile invokes the test_suite() call.
135*b0d29bc4SBrooks Davis     optional< std::string > _test_suite;
136*b0d29bc4SBrooks Davis 
137*b0d29bc4SBrooks Davis     /// Collection of test programs defined by the Kyuafile.
138*b0d29bc4SBrooks Davis     ///
139*b0d29bc4SBrooks Davis     /// This acts as an accumulator for all the *_test_program() calls within
140*b0d29bc4SBrooks Davis     /// the Kyuafile.
141*b0d29bc4SBrooks Davis     model::test_programs_vector _test_programs;
142*b0d29bc4SBrooks Davis 
143*b0d29bc4SBrooks Davis     /// Safely gets _test_suite and respects any test program overrides.
144*b0d29bc4SBrooks Davis     ///
145*b0d29bc4SBrooks Davis     /// \param program_override The test program-specific test suite name.  May
146*b0d29bc4SBrooks Davis     ///     be empty to indicate no override.
147*b0d29bc4SBrooks Davis     ///
148*b0d29bc4SBrooks Davis     /// \return The name of the test suite.
149*b0d29bc4SBrooks Davis     ///
150*b0d29bc4SBrooks Davis     /// \throw std::runtime_error If program_override is empty and the Kyuafile
151*b0d29bc4SBrooks Davis     /// did not yet define the global name of the test suite.
152*b0d29bc4SBrooks Davis     std::string
get_test_suite(const std::string & program_override)153*b0d29bc4SBrooks Davis     get_test_suite(const std::string& program_override)
154*b0d29bc4SBrooks Davis     {
155*b0d29bc4SBrooks Davis         std::string test_suite;
156*b0d29bc4SBrooks Davis 
157*b0d29bc4SBrooks Davis         if (program_override.empty()) {
158*b0d29bc4SBrooks Davis             if (!_test_suite) {
159*b0d29bc4SBrooks Davis                 throw std::runtime_error("No test suite defined in the "
160*b0d29bc4SBrooks Davis                                          "Kyuafile and no override provided in "
161*b0d29bc4SBrooks Davis                                          "the test_program definition");
162*b0d29bc4SBrooks Davis             }
163*b0d29bc4SBrooks Davis             test_suite = _test_suite.get();
164*b0d29bc4SBrooks Davis         } else {
165*b0d29bc4SBrooks Davis             test_suite = program_override;
166*b0d29bc4SBrooks Davis         }
167*b0d29bc4SBrooks Davis 
168*b0d29bc4SBrooks Davis         return test_suite;
169*b0d29bc4SBrooks Davis     }
170*b0d29bc4SBrooks Davis 
171*b0d29bc4SBrooks Davis public:
172*b0d29bc4SBrooks Davis     /// Initializes the parser and the Lua state.
173*b0d29bc4SBrooks Davis     ///
174*b0d29bc4SBrooks Davis     /// \param source_root_ The root directory of the test suite represented by
175*b0d29bc4SBrooks Davis     ///     the Kyuafile.
176*b0d29bc4SBrooks Davis     /// \param build_root_ The root directory of the test programs.
177*b0d29bc4SBrooks Davis     /// \param relative_filename_ Name of the Kyuafile to load relative to
178*b0d29bc4SBrooks Davis     ///     source_root_.
179*b0d29bc4SBrooks Davis     /// \param user_config User configuration holding any test suite properties
180*b0d29bc4SBrooks Davis     ///     to be passed to the list operation.
181*b0d29bc4SBrooks Davis     /// \param scheduler_handle The scheduler context to use for loading the
182*b0d29bc4SBrooks Davis     ///     test case lists.
parser(const fs::path & source_root_,const fs::path & build_root_,const fs::path & relative_filename_,const config::tree & user_config,scheduler::scheduler_handle & scheduler_handle)183*b0d29bc4SBrooks Davis     parser(const fs::path& source_root_, const fs::path& build_root_,
184*b0d29bc4SBrooks Davis            const fs::path& relative_filename_,
185*b0d29bc4SBrooks Davis            const config::tree& user_config,
186*b0d29bc4SBrooks Davis            scheduler::scheduler_handle& scheduler_handle) :
187*b0d29bc4SBrooks Davis         _source_root(source_root_), _build_root(build_root_),
188*b0d29bc4SBrooks Davis         _relative_filename(relative_filename_)
189*b0d29bc4SBrooks Davis     {
190*b0d29bc4SBrooks Davis         lutok::stack_cleaner cleaner(_state);
191*b0d29bc4SBrooks Davis 
192*b0d29bc4SBrooks Davis         _state.push_cxx_function(lua_syntax);
193*b0d29bc4SBrooks Davis         _state.set_global("syntax");
194*b0d29bc4SBrooks Davis 
195*b0d29bc4SBrooks Davis         *_state.new_userdata< parser* >() = this;
196*b0d29bc4SBrooks Davis         _state.set_global("_parser");
197*b0d29bc4SBrooks Davis 
198*b0d29bc4SBrooks Davis         _state.push_cxx_function(lua_current_kyuafile);
199*b0d29bc4SBrooks Davis         _state.set_global("current_kyuafile");
200*b0d29bc4SBrooks Davis 
201*b0d29bc4SBrooks Davis         *_state.new_userdata< const config::tree* >() = &user_config;
202*b0d29bc4SBrooks Davis         *_state.new_userdata< scheduler::scheduler_handle* >() =
203*b0d29bc4SBrooks Davis             &scheduler_handle;
204*b0d29bc4SBrooks Davis         _state.push_cxx_closure(lua_include, 2);
205*b0d29bc4SBrooks Davis         _state.set_global("include");
206*b0d29bc4SBrooks Davis 
207*b0d29bc4SBrooks Davis         _state.push_cxx_function(lua_test_suite);
208*b0d29bc4SBrooks Davis         _state.set_global("test_suite");
209*b0d29bc4SBrooks Davis 
210*b0d29bc4SBrooks Davis         const std::set< std::string > interfaces =
211*b0d29bc4SBrooks Davis             scheduler::registered_interface_names();
212*b0d29bc4SBrooks Davis         for (std::set< std::string >::const_iterator iter = interfaces.begin();
213*b0d29bc4SBrooks Davis              iter != interfaces.end(); ++iter) {
214*b0d29bc4SBrooks Davis             const std::string& interface = *iter;
215*b0d29bc4SBrooks Davis 
216*b0d29bc4SBrooks Davis             _state.push_string(interface);
217*b0d29bc4SBrooks Davis             *_state.new_userdata< const config::tree* >() = &user_config;
218*b0d29bc4SBrooks Davis             *_state.new_userdata< scheduler::scheduler_handle* >() =
219*b0d29bc4SBrooks Davis                 &scheduler_handle;
220*b0d29bc4SBrooks Davis             _state.push_cxx_closure(lua_generic_test_program, 3);
221*b0d29bc4SBrooks Davis             _state.set_global(interface + "_test_program");
222*b0d29bc4SBrooks Davis         }
223*b0d29bc4SBrooks Davis 
224*b0d29bc4SBrooks Davis         _state.open_base();
225*b0d29bc4SBrooks Davis         _state.open_string();
226*b0d29bc4SBrooks Davis         _state.open_table();
227*b0d29bc4SBrooks Davis         fs::open_fs(_state, callback_current_kyuafile().branch_path());
228*b0d29bc4SBrooks Davis     }
229*b0d29bc4SBrooks Davis 
230*b0d29bc4SBrooks Davis     /// Destructor.
~parser(void)231*b0d29bc4SBrooks Davis     ~parser(void)
232*b0d29bc4SBrooks Davis     {
233*b0d29bc4SBrooks Davis     }
234*b0d29bc4SBrooks Davis 
235*b0d29bc4SBrooks Davis     /// Gets the parser object associated to a Lua state.
236*b0d29bc4SBrooks Davis     ///
237*b0d29bc4SBrooks Davis     /// \param state The Lua state from which to obtain the parser object.
238*b0d29bc4SBrooks Davis     ///
239*b0d29bc4SBrooks Davis     /// \return A pointer to the parser.
240*b0d29bc4SBrooks Davis     static parser*
get_from_state(lutok::state & state)241*b0d29bc4SBrooks Davis     get_from_state(lutok::state& state)
242*b0d29bc4SBrooks Davis     {
243*b0d29bc4SBrooks Davis         lutok::stack_cleaner cleaner(state);
244*b0d29bc4SBrooks Davis         state.get_global("_parser");
245*b0d29bc4SBrooks Davis         return *state.to_userdata< parser* >(-1);
246*b0d29bc4SBrooks Davis     }
247*b0d29bc4SBrooks Davis 
248*b0d29bc4SBrooks Davis     /// Callback for the Kyuafile current_kyuafile() function.
249*b0d29bc4SBrooks Davis     ///
250*b0d29bc4SBrooks Davis     /// \return Returns the absolute path to the current Kyuafile.
251*b0d29bc4SBrooks Davis     fs::path
callback_current_kyuafile(void) const252*b0d29bc4SBrooks Davis     callback_current_kyuafile(void) const
253*b0d29bc4SBrooks Davis     {
254*b0d29bc4SBrooks Davis         const fs::path file = relativize(_source_root, _relative_filename);
255*b0d29bc4SBrooks Davis         if (file.is_absolute())
256*b0d29bc4SBrooks Davis             return file;
257*b0d29bc4SBrooks Davis         else
258*b0d29bc4SBrooks Davis             return file.to_absolute();
259*b0d29bc4SBrooks Davis     }
260*b0d29bc4SBrooks Davis 
261*b0d29bc4SBrooks Davis     /// Callback for the Kyuafile include() function.
262*b0d29bc4SBrooks Davis     ///
263*b0d29bc4SBrooks Davis     /// \post _test_programs is extended with the the test programs defined by
264*b0d29bc4SBrooks Davis     /// the included file.
265*b0d29bc4SBrooks Davis     ///
266*b0d29bc4SBrooks Davis     /// \param raw_file Path to the file to include.
267*b0d29bc4SBrooks Davis     /// \param user_config User configuration holding any test suite properties
268*b0d29bc4SBrooks Davis     ///     to be passed to the list operation.
269*b0d29bc4SBrooks Davis     /// \param scheduler_handle Scheduler context to run test programs in.
270*b0d29bc4SBrooks Davis     void
callback_include(const fs::path & raw_file,const config::tree & user_config,scheduler::scheduler_handle & scheduler_handle)271*b0d29bc4SBrooks Davis     callback_include(const fs::path& raw_file,
272*b0d29bc4SBrooks Davis                      const config::tree& user_config,
273*b0d29bc4SBrooks Davis                      scheduler::scheduler_handle& scheduler_handle)
274*b0d29bc4SBrooks Davis     {
275*b0d29bc4SBrooks Davis         const fs::path file = relativize(_relative_filename.branch_path(),
276*b0d29bc4SBrooks Davis                                          raw_file);
277*b0d29bc4SBrooks Davis         const model::test_programs_vector subtps =
278*b0d29bc4SBrooks Davis             parser(_source_root, _build_root, file, user_config,
279*b0d29bc4SBrooks Davis                    scheduler_handle).parse();
280*b0d29bc4SBrooks Davis 
281*b0d29bc4SBrooks Davis         std::copy(subtps.begin(), subtps.end(),
282*b0d29bc4SBrooks Davis                   std::back_inserter(_test_programs));
283*b0d29bc4SBrooks Davis     }
284*b0d29bc4SBrooks Davis 
285*b0d29bc4SBrooks Davis     /// Callback for the Kyuafile syntax() function.
286*b0d29bc4SBrooks Davis     ///
287*b0d29bc4SBrooks Davis     /// \post _version is set to the requested version.
288*b0d29bc4SBrooks Davis     ///
289*b0d29bc4SBrooks Davis     /// \param version Version of the Kyuafile syntax requested by the file.
290*b0d29bc4SBrooks Davis     ///
291*b0d29bc4SBrooks Davis     /// \throw std::runtime_error If the format or the version are invalid, or
292*b0d29bc4SBrooks Davis     /// if syntax() has already been called.
293*b0d29bc4SBrooks Davis     void
callback_syntax(const int version)294*b0d29bc4SBrooks Davis     callback_syntax(const int version)
295*b0d29bc4SBrooks Davis     {
296*b0d29bc4SBrooks Davis         if (_version)
297*b0d29bc4SBrooks Davis             throw std::runtime_error("Can only call syntax() once");
298*b0d29bc4SBrooks Davis 
299*b0d29bc4SBrooks Davis         if (version < 1 || version > 2)
300*b0d29bc4SBrooks Davis             throw std::runtime_error(F("Unsupported file version %s") %
301*b0d29bc4SBrooks Davis                                      version);
302*b0d29bc4SBrooks Davis 
303*b0d29bc4SBrooks Davis         _version = utils::make_optional(version);
304*b0d29bc4SBrooks Davis     }
305*b0d29bc4SBrooks Davis 
306*b0d29bc4SBrooks Davis     /// Callback for the various Kyuafile *_test_program() functions.
307*b0d29bc4SBrooks Davis     ///
308*b0d29bc4SBrooks Davis     /// \post _test_programs is extended to include the newly defined test
309*b0d29bc4SBrooks Davis     /// program.
310*b0d29bc4SBrooks Davis     ///
311*b0d29bc4SBrooks Davis     /// \param interface Name of the test program interface.
312*b0d29bc4SBrooks Davis     /// \param raw_path Path to the test program, relative to the Kyuafile.
313*b0d29bc4SBrooks Davis     ///     This has to be adjusted according to the relative location of this
314*b0d29bc4SBrooks Davis     ///     Kyuafile to _source_root.
315*b0d29bc4SBrooks Davis     /// \param test_suite_override Name of the test suite this test program
316*b0d29bc4SBrooks Davis     ///     belongs to, if explicitly defined at the test program level.
317*b0d29bc4SBrooks Davis     /// \param metadata Metadata variables passed to the test program.
318*b0d29bc4SBrooks Davis     /// \param user_config User configuration holding any test suite properties
319*b0d29bc4SBrooks Davis     ///     to be passed to the list operation.
320*b0d29bc4SBrooks Davis     /// \param scheduler_handle Scheduler context to run test programs in.
321*b0d29bc4SBrooks Davis     ///
322*b0d29bc4SBrooks Davis     /// \throw std::runtime_error If the test program definition is invalid or
323*b0d29bc4SBrooks Davis     ///     if the test program does not exist.
324*b0d29bc4SBrooks Davis     void
callback_test_program(const std::string & interface,const fs::path & raw_path,const std::string & test_suite_override,const model::metadata & metadata,const config::tree & user_config,scheduler::scheduler_handle & scheduler_handle)325*b0d29bc4SBrooks Davis     callback_test_program(const std::string& interface,
326*b0d29bc4SBrooks Davis                           const fs::path& raw_path,
327*b0d29bc4SBrooks Davis                           const std::string& test_suite_override,
328*b0d29bc4SBrooks Davis                           const model::metadata& metadata,
329*b0d29bc4SBrooks Davis                           const config::tree& user_config,
330*b0d29bc4SBrooks Davis                           scheduler::scheduler_handle& scheduler_handle)
331*b0d29bc4SBrooks Davis     {
332*b0d29bc4SBrooks Davis         if (raw_path.is_absolute())
333*b0d29bc4SBrooks Davis             throw std::runtime_error(F("Got unexpected absolute path for test "
334*b0d29bc4SBrooks Davis                                        "program '%s'") % raw_path);
335*b0d29bc4SBrooks Davis         else if (raw_path.str() != raw_path.leaf_name())
336*b0d29bc4SBrooks Davis             throw std::runtime_error(F("Test program '%s' cannot contain path "
337*b0d29bc4SBrooks Davis                                        "components") % raw_path);
338*b0d29bc4SBrooks Davis 
339*b0d29bc4SBrooks Davis         const fs::path path = relativize(_relative_filename.branch_path(),
340*b0d29bc4SBrooks Davis                                          raw_path);
341*b0d29bc4SBrooks Davis 
342*b0d29bc4SBrooks Davis         if (!fs::exists(_build_root / path))
343*b0d29bc4SBrooks Davis             throw std::runtime_error(F("Non-existent test program '%s'") %
344*b0d29bc4SBrooks Davis                                      path);
345*b0d29bc4SBrooks Davis 
346*b0d29bc4SBrooks Davis         const std::string test_suite = get_test_suite(test_suite_override);
347*b0d29bc4SBrooks Davis 
348*b0d29bc4SBrooks Davis         _test_programs.push_back(model::test_program_ptr(
349*b0d29bc4SBrooks Davis             new scheduler::lazy_test_program(interface, path, _build_root,
350*b0d29bc4SBrooks Davis                                              test_suite, metadata, user_config,
351*b0d29bc4SBrooks Davis                                              scheduler_handle)));
352*b0d29bc4SBrooks Davis     }
353*b0d29bc4SBrooks Davis 
354*b0d29bc4SBrooks Davis     /// Callback for the Kyuafile test_suite() function.
355*b0d29bc4SBrooks Davis     ///
356*b0d29bc4SBrooks Davis     /// \post _version is set to the requested version.
357*b0d29bc4SBrooks Davis     ///
358*b0d29bc4SBrooks Davis     /// \param name Name of the test suite.
359*b0d29bc4SBrooks Davis     ///
360*b0d29bc4SBrooks Davis     /// \throw std::runtime_error If test_suite() has already been called.
361*b0d29bc4SBrooks Davis     void
callback_test_suite(const std::string & name)362*b0d29bc4SBrooks Davis     callback_test_suite(const std::string& name)
363*b0d29bc4SBrooks Davis     {
364*b0d29bc4SBrooks Davis         if (_test_suite)
365*b0d29bc4SBrooks Davis             throw std::runtime_error("Can only call test_suite() once");
366*b0d29bc4SBrooks Davis         _test_suite = utils::make_optional(name);
367*b0d29bc4SBrooks Davis     }
368*b0d29bc4SBrooks Davis 
369*b0d29bc4SBrooks Davis     /// Parses the Kyuafile.
370*b0d29bc4SBrooks Davis     ///
371*b0d29bc4SBrooks Davis     /// \pre Can only be invoked once.
372*b0d29bc4SBrooks Davis     ///
373*b0d29bc4SBrooks Davis     /// \return The collection of test programs defined by the Kyuafile.
374*b0d29bc4SBrooks Davis     ///
375*b0d29bc4SBrooks Davis     /// \throw load_error If there is any problem parsing the file.
376*b0d29bc4SBrooks Davis     const model::test_programs_vector&
parse(void)377*b0d29bc4SBrooks Davis     parse(void)
378*b0d29bc4SBrooks Davis     {
379*b0d29bc4SBrooks Davis         PRE(_test_programs.empty());
380*b0d29bc4SBrooks Davis 
381*b0d29bc4SBrooks Davis         const fs::path load_path = relativize(_source_root, _relative_filename);
382*b0d29bc4SBrooks Davis         try {
383*b0d29bc4SBrooks Davis             lutok::do_file(_state, load_path.str(), 0, 0, 0);
384*b0d29bc4SBrooks Davis         } catch (const std::runtime_error& e) {
385*b0d29bc4SBrooks Davis             // It is tempting to think that all of our various auxiliary
386*b0d29bc4SBrooks Davis             // functions above could raise load_error by themselves thus making
387*b0d29bc4SBrooks Davis             // this exception rewriting here unnecessary.  Howver, that would
388*b0d29bc4SBrooks Davis             // not work because the helper functions above are executed within a
389*b0d29bc4SBrooks Davis             // Lua context, and we lose their type when they are propagated out
390*b0d29bc4SBrooks Davis             // of it.
391*b0d29bc4SBrooks Davis             throw engine::load_error(load_path, e.what());
392*b0d29bc4SBrooks Davis         }
393*b0d29bc4SBrooks Davis 
394*b0d29bc4SBrooks Davis         if (!_version)
395*b0d29bc4SBrooks Davis             throw engine::load_error(load_path, "syntax() never called");
396*b0d29bc4SBrooks Davis 
397*b0d29bc4SBrooks Davis         return _test_programs;
398*b0d29bc4SBrooks Davis     }
399*b0d29bc4SBrooks Davis };
400*b0d29bc4SBrooks Davis 
401*b0d29bc4SBrooks Davis 
402*b0d29bc4SBrooks Davis /// Glue to invoke parser::callback_test_program() from Lua.
403*b0d29bc4SBrooks Davis ///
404*b0d29bc4SBrooks Davis /// This is a helper function for the various *_test_program() calls, as they
405*b0d29bc4SBrooks Davis /// only differ in the interface of the defined test program.
406*b0d29bc4SBrooks Davis ///
407*b0d29bc4SBrooks Davis /// \pre state(-1) A table with the arguments that define the test program.  The
408*b0d29bc4SBrooks Davis /// special argument 'test_suite' provides an override to the global test suite
409*b0d29bc4SBrooks Davis /// name.  The rest of the arguments are part of the test program metadata.
410*b0d29bc4SBrooks Davis /// \pre state(upvalue 1) String with the name of the interface.
411*b0d29bc4SBrooks Davis /// \pre state(upvalue 2) User configuration with the per-test suite settings.
412*b0d29bc4SBrooks Davis /// \pre state(upvalue 3) Scheduler context to run test programs in.
413*b0d29bc4SBrooks Davis ///
414*b0d29bc4SBrooks Davis /// \param state The Lua state that executed the function.
415*b0d29bc4SBrooks Davis ///
416*b0d29bc4SBrooks Davis /// \return Number of return values left on the Lua stack.
417*b0d29bc4SBrooks Davis ///
418*b0d29bc4SBrooks Davis /// \throw std::runtime_error If the arguments to the function are invalid.
419*b0d29bc4SBrooks Davis static int
lua_generic_test_program(lutok::state & state)420*b0d29bc4SBrooks Davis lua_generic_test_program(lutok::state& state)
421*b0d29bc4SBrooks Davis {
422*b0d29bc4SBrooks Davis     if (!state.is_string(state.upvalue_index(1)))
423*b0d29bc4SBrooks Davis         throw std::runtime_error("Found corrupt state for test_program "
424*b0d29bc4SBrooks Davis                                  "function");
425*b0d29bc4SBrooks Davis     const std::string interface = state.to_string(state.upvalue_index(1));
426*b0d29bc4SBrooks Davis 
427*b0d29bc4SBrooks Davis     if (!state.is_userdata(state.upvalue_index(2)))
428*b0d29bc4SBrooks Davis         throw std::runtime_error("Found corrupt state for test_program "
429*b0d29bc4SBrooks Davis                                  "function");
430*b0d29bc4SBrooks Davis     const config::tree* user_config = *state.to_userdata< const config::tree* >(
431*b0d29bc4SBrooks Davis         state.upvalue_index(2));
432*b0d29bc4SBrooks Davis 
433*b0d29bc4SBrooks Davis     if (!state.is_userdata(state.upvalue_index(3)))
434*b0d29bc4SBrooks Davis         throw std::runtime_error("Found corrupt state for test_program "
435*b0d29bc4SBrooks Davis                                  "function");
436*b0d29bc4SBrooks Davis     scheduler::scheduler_handle* scheduler_handle =
437*b0d29bc4SBrooks Davis         *state.to_userdata< scheduler::scheduler_handle* >(
438*b0d29bc4SBrooks Davis             state.upvalue_index(3));
439*b0d29bc4SBrooks Davis 
440*b0d29bc4SBrooks Davis     if (!state.is_table(-1))
441*b0d29bc4SBrooks Davis         throw std::runtime_error(
442*b0d29bc4SBrooks Davis             F("%s_test_program expects a table of properties as its single "
443*b0d29bc4SBrooks Davis               "argument") % interface);
444*b0d29bc4SBrooks Davis 
445*b0d29bc4SBrooks Davis     scheduler::ensure_valid_interface(interface);
446*b0d29bc4SBrooks Davis 
447*b0d29bc4SBrooks Davis     lutok::stack_cleaner cleaner(state);
448*b0d29bc4SBrooks Davis 
449*b0d29bc4SBrooks Davis     state.push_string("name");
450*b0d29bc4SBrooks Davis     state.get_table(-2);
451*b0d29bc4SBrooks Davis     if (!state.is_string(-1))
452*b0d29bc4SBrooks Davis         throw std::runtime_error("Test program name not defined or not a "
453*b0d29bc4SBrooks Davis                                  "string");
454*b0d29bc4SBrooks Davis     const fs::path path(state.to_string(-1));
455*b0d29bc4SBrooks Davis     state.pop(1);
456*b0d29bc4SBrooks Davis 
457*b0d29bc4SBrooks Davis     state.push_string("test_suite");
458*b0d29bc4SBrooks Davis     state.get_table(-2);
459*b0d29bc4SBrooks Davis     std::string test_suite;
460*b0d29bc4SBrooks Davis     if (state.is_nil(-1)) {
461*b0d29bc4SBrooks Davis         // Leave empty to use the global test-suite value.
462*b0d29bc4SBrooks Davis     } else if (state.is_string(-1)) {
463*b0d29bc4SBrooks Davis         test_suite = state.to_string(-1);
464*b0d29bc4SBrooks Davis     } else {
465*b0d29bc4SBrooks Davis         throw std::runtime_error(F("Found non-string value in the test_suite "
466*b0d29bc4SBrooks Davis                                    "property of test program '%s'") % path);
467*b0d29bc4SBrooks Davis     }
468*b0d29bc4SBrooks Davis     state.pop(1);
469*b0d29bc4SBrooks Davis 
470*b0d29bc4SBrooks Davis     model::metadata_builder mdbuilder;
471*b0d29bc4SBrooks Davis     state.push_nil();
472*b0d29bc4SBrooks Davis     while (state.next(-2)) {
473*b0d29bc4SBrooks Davis         if (!state.is_string(-2))
474*b0d29bc4SBrooks Davis             throw std::runtime_error(F("Found non-string metadata property "
475*b0d29bc4SBrooks Davis                                        "name in test program '%s'") %
476*b0d29bc4SBrooks Davis                                      path);
477*b0d29bc4SBrooks Davis         const std::string property = state.to_string(-2);
478*b0d29bc4SBrooks Davis 
479*b0d29bc4SBrooks Davis         if (property != "name" && property != "test_suite") {
480*b0d29bc4SBrooks Davis             std::string value;
481*b0d29bc4SBrooks Davis             if (state.is_boolean(-1)) {
482*b0d29bc4SBrooks Davis                 value = F("%s") % state.to_boolean(-1);
483*b0d29bc4SBrooks Davis             } else if (state.is_number(-1)) {
484*b0d29bc4SBrooks Davis                 value = F("%s") % state.to_integer(-1);
485*b0d29bc4SBrooks Davis             } else if (state.is_string(-1)) {
486*b0d29bc4SBrooks Davis                 value = state.to_string(-1);
487*b0d29bc4SBrooks Davis             } else {
488*b0d29bc4SBrooks Davis                 throw std::runtime_error(
489*b0d29bc4SBrooks Davis                     F("Metadata property '%s' in test program '%s' cannot be "
490*b0d29bc4SBrooks Davis                       "converted to a string") % property % path);
491*b0d29bc4SBrooks Davis             }
492*b0d29bc4SBrooks Davis 
493*b0d29bc4SBrooks Davis             mdbuilder.set_string(property, value);
494*b0d29bc4SBrooks Davis         }
495*b0d29bc4SBrooks Davis 
496*b0d29bc4SBrooks Davis         state.pop(1);
497*b0d29bc4SBrooks Davis     }
498*b0d29bc4SBrooks Davis 
499*b0d29bc4SBrooks Davis     parser::get_from_state(state)->callback_test_program(
500*b0d29bc4SBrooks Davis         interface, path, test_suite, mdbuilder.build(), *user_config,
501*b0d29bc4SBrooks Davis         *scheduler_handle);
502*b0d29bc4SBrooks Davis     return 0;
503*b0d29bc4SBrooks Davis }
504*b0d29bc4SBrooks Davis 
505*b0d29bc4SBrooks Davis 
506*b0d29bc4SBrooks Davis /// Glue to invoke parser::callback_current_kyuafile() from Lua.
507*b0d29bc4SBrooks Davis ///
508*b0d29bc4SBrooks Davis /// \param state The Lua state that executed the function.
509*b0d29bc4SBrooks Davis ///
510*b0d29bc4SBrooks Davis /// \return Number of return values left on the Lua stack.
511*b0d29bc4SBrooks Davis static int
lua_current_kyuafile(lutok::state & state)512*b0d29bc4SBrooks Davis lua_current_kyuafile(lutok::state& state)
513*b0d29bc4SBrooks Davis {
514*b0d29bc4SBrooks Davis     state.push_string(parser::get_from_state(state)->
515*b0d29bc4SBrooks Davis                       callback_current_kyuafile().str());
516*b0d29bc4SBrooks Davis     return 1;
517*b0d29bc4SBrooks Davis }
518*b0d29bc4SBrooks Davis 
519*b0d29bc4SBrooks Davis 
520*b0d29bc4SBrooks Davis /// Glue to invoke parser::callback_include() from Lua.
521*b0d29bc4SBrooks Davis ///
522*b0d29bc4SBrooks Davis /// \param state The Lua state that executed the function.
523*b0d29bc4SBrooks Davis ///
524*b0d29bc4SBrooks Davis /// \pre state(upvalue 1) User configuration with the per-test suite settings.
525*b0d29bc4SBrooks Davis /// \pre state(upvalue 2) Scheduler context to run test programs in.
526*b0d29bc4SBrooks Davis ///
527*b0d29bc4SBrooks Davis /// \return Number of return values left on the Lua stack.
528*b0d29bc4SBrooks Davis static int
lua_include(lutok::state & state)529*b0d29bc4SBrooks Davis lua_include(lutok::state& state)
530*b0d29bc4SBrooks Davis {
531*b0d29bc4SBrooks Davis     if (!state.is_userdata(state.upvalue_index(1)))
532*b0d29bc4SBrooks Davis         throw std::runtime_error("Found corrupt state for test_program "
533*b0d29bc4SBrooks Davis                                  "function");
534*b0d29bc4SBrooks Davis     const config::tree* user_config = *state.to_userdata< const config::tree* >(
535*b0d29bc4SBrooks Davis         state.upvalue_index(1));
536*b0d29bc4SBrooks Davis 
537*b0d29bc4SBrooks Davis     if (!state.is_userdata(state.upvalue_index(2)))
538*b0d29bc4SBrooks Davis         throw std::runtime_error("Found corrupt state for test_program "
539*b0d29bc4SBrooks Davis                                  "function");
540*b0d29bc4SBrooks Davis     scheduler::scheduler_handle* scheduler_handle =
541*b0d29bc4SBrooks Davis         *state.to_userdata< scheduler::scheduler_handle* >(
542*b0d29bc4SBrooks Davis             state.upvalue_index(2));
543*b0d29bc4SBrooks Davis 
544*b0d29bc4SBrooks Davis     parser::get_from_state(state)->callback_include(
545*b0d29bc4SBrooks Davis         fs::path(state.to_string(-1)), *user_config, *scheduler_handle);
546*b0d29bc4SBrooks Davis     return 0;
547*b0d29bc4SBrooks Davis }
548*b0d29bc4SBrooks Davis 
549*b0d29bc4SBrooks Davis 
550*b0d29bc4SBrooks Davis /// Glue to invoke parser::callback_syntax() from Lua.
551*b0d29bc4SBrooks Davis ///
552*b0d29bc4SBrooks Davis /// \pre state(-2) The syntax format name, if a v1 file.
553*b0d29bc4SBrooks Davis /// \pre state(-1) The syntax format version.
554*b0d29bc4SBrooks Davis ///
555*b0d29bc4SBrooks Davis /// \param state The Lua state that executed the function.
556*b0d29bc4SBrooks Davis ///
557*b0d29bc4SBrooks Davis /// \return Number of return values left on the Lua stack.
558*b0d29bc4SBrooks Davis static int
lua_syntax(lutok::state & state)559*b0d29bc4SBrooks Davis lua_syntax(lutok::state& state)
560*b0d29bc4SBrooks Davis {
561*b0d29bc4SBrooks Davis     if (!state.is_number(-1))
562*b0d29bc4SBrooks Davis         throw std::runtime_error("Last argument to syntax must be a number");
563*b0d29bc4SBrooks Davis     const int syntax_version = state.to_integer(-1);
564*b0d29bc4SBrooks Davis 
565*b0d29bc4SBrooks Davis     if (syntax_version == 1) {
566*b0d29bc4SBrooks Davis         if (state.get_top() != 2)
567*b0d29bc4SBrooks Davis             throw std::runtime_error("Version 1 files need two arguments to "
568*b0d29bc4SBrooks Davis                                      "syntax()");
569*b0d29bc4SBrooks Davis         if (!state.is_string(-2) || state.to_string(-2) != "kyuafile")
570*b0d29bc4SBrooks Davis             throw std::runtime_error("First argument to syntax must be "
571*b0d29bc4SBrooks Davis                                      "'kyuafile' for version 1 files");
572*b0d29bc4SBrooks Davis     } else {
573*b0d29bc4SBrooks Davis         if (state.get_top() != 1)
574*b0d29bc4SBrooks Davis             throw std::runtime_error("syntax() only takes one argument");
575*b0d29bc4SBrooks Davis     }
576*b0d29bc4SBrooks Davis 
577*b0d29bc4SBrooks Davis     parser::get_from_state(state)->callback_syntax(syntax_version);
578*b0d29bc4SBrooks Davis     return 0;
579*b0d29bc4SBrooks Davis }
580*b0d29bc4SBrooks Davis 
581*b0d29bc4SBrooks Davis 
582*b0d29bc4SBrooks Davis /// Glue to invoke parser::callback_test_suite() from Lua.
583*b0d29bc4SBrooks Davis ///
584*b0d29bc4SBrooks Davis /// \param state The Lua state that executed the function.
585*b0d29bc4SBrooks Davis ///
586*b0d29bc4SBrooks Davis /// \return Number of return values left on the Lua stack.
587*b0d29bc4SBrooks Davis static int
lua_test_suite(lutok::state & state)588*b0d29bc4SBrooks Davis lua_test_suite(lutok::state& state)
589*b0d29bc4SBrooks Davis {
590*b0d29bc4SBrooks Davis     parser::get_from_state(state)->callback_test_suite(state.to_string(-1));
591*b0d29bc4SBrooks Davis     return 0;
592*b0d29bc4SBrooks Davis }
593*b0d29bc4SBrooks Davis 
594*b0d29bc4SBrooks Davis 
595*b0d29bc4SBrooks Davis }  // anonymous namespace
596*b0d29bc4SBrooks Davis 
597*b0d29bc4SBrooks Davis 
598*b0d29bc4SBrooks Davis /// Constructs a kyuafile form initialized data.
599*b0d29bc4SBrooks Davis ///
600*b0d29bc4SBrooks Davis /// Use load() to parse a test suite configuration file and construct a
601*b0d29bc4SBrooks Davis /// kyuafile object.
602*b0d29bc4SBrooks Davis ///
603*b0d29bc4SBrooks Davis /// \param source_root_ The root directory for the test suite represented by the
604*b0d29bc4SBrooks Davis ///     Kyuafile.  In other words, the directory containing the first Kyuafile
605*b0d29bc4SBrooks Davis ///     processed.
606*b0d29bc4SBrooks Davis /// \param build_root_ The root directory for the test programs themselves.  In
607*b0d29bc4SBrooks Davis ///     general, this will be the same as source_root_.  If different, the
608*b0d29bc4SBrooks Davis ///     specified directory must follow the exact same layout of source_root_.
609*b0d29bc4SBrooks Davis /// \param tps_ Collection of test programs that belong to this test suite.
kyuafile(const fs::path & source_root_,const fs::path & build_root_,const model::test_programs_vector & tps_)610*b0d29bc4SBrooks Davis engine::kyuafile::kyuafile(const fs::path& source_root_,
611*b0d29bc4SBrooks Davis                            const fs::path& build_root_,
612*b0d29bc4SBrooks Davis                            const model::test_programs_vector& tps_) :
613*b0d29bc4SBrooks Davis     _source_root(source_root_),
614*b0d29bc4SBrooks Davis     _build_root(build_root_),
615*b0d29bc4SBrooks Davis     _test_programs(tps_)
616*b0d29bc4SBrooks Davis {
617*b0d29bc4SBrooks Davis }
618*b0d29bc4SBrooks Davis 
619*b0d29bc4SBrooks Davis 
620*b0d29bc4SBrooks Davis /// Destructor.
~kyuafile(void)621*b0d29bc4SBrooks Davis engine::kyuafile::~kyuafile(void)
622*b0d29bc4SBrooks Davis {
623*b0d29bc4SBrooks Davis }
624*b0d29bc4SBrooks Davis 
625*b0d29bc4SBrooks Davis 
626*b0d29bc4SBrooks Davis /// Parses a test suite configuration file.
627*b0d29bc4SBrooks Davis ///
628*b0d29bc4SBrooks Davis /// \param file The file to parse.
629*b0d29bc4SBrooks Davis /// \param user_build_root If not none, specifies a path to a directory
630*b0d29bc4SBrooks Davis ///     containing the test programs themselves.  The layout of the build root
631*b0d29bc4SBrooks Davis ///     must match the layout of the source root (which is just the directory
632*b0d29bc4SBrooks Davis ///     from which the Kyuafile is being read).
633*b0d29bc4SBrooks Davis /// \param user_config User configuration holding any test suite properties
634*b0d29bc4SBrooks Davis ///     to be passed to the list operation.
635*b0d29bc4SBrooks Davis /// \param scheduler_handle The scheduler context to use for loading the test
636*b0d29bc4SBrooks Davis ///     case lists.
637*b0d29bc4SBrooks Davis ///
638*b0d29bc4SBrooks Davis /// \return High-level representation of the configuration file.
639*b0d29bc4SBrooks Davis ///
640*b0d29bc4SBrooks Davis /// \throw load_error If there is any problem loading the file.  This includes
641*b0d29bc4SBrooks Davis ///     file access errors and syntax errors.
642*b0d29bc4SBrooks Davis engine::kyuafile
load(const fs::path & file,const optional<fs::path> user_build_root,const config::tree & user_config,scheduler::scheduler_handle & scheduler_handle)643*b0d29bc4SBrooks Davis engine::kyuafile::load(const fs::path& file,
644*b0d29bc4SBrooks Davis                        const optional< fs::path > user_build_root,
645*b0d29bc4SBrooks Davis                        const config::tree& user_config,
646*b0d29bc4SBrooks Davis                        scheduler::scheduler_handle& scheduler_handle)
647*b0d29bc4SBrooks Davis {
648*b0d29bc4SBrooks Davis     const fs::path source_root_ = file.branch_path();
649*b0d29bc4SBrooks Davis     const fs::path build_root_ = user_build_root ?
650*b0d29bc4SBrooks Davis         user_build_root.get() : source_root_;
651*b0d29bc4SBrooks Davis 
652*b0d29bc4SBrooks Davis     // test_program.absolute_path() uses the current work directory and that
653*b0d29bc4SBrooks Davis     // fails to resolve the correct path once we have used chdir to enter the
654*b0d29bc4SBrooks Davis     // test work directory.  To prevent this causing issues down the road,
655*b0d29bc4SBrooks Davis     // force the build root to be absolute so that absolute_path() does not
656*b0d29bc4SBrooks Davis     // need to rely on the current work directory.
657*b0d29bc4SBrooks Davis     const fs::path abs_build_root = build_root_.is_absolute() ?
658*b0d29bc4SBrooks Davis         build_root_ : build_root_.to_absolute();
659*b0d29bc4SBrooks Davis 
660*b0d29bc4SBrooks Davis     return kyuafile(source_root_, build_root_,
661*b0d29bc4SBrooks Davis                     parser(source_root_, abs_build_root,
662*b0d29bc4SBrooks Davis                            fs::path(file.leaf_name()), user_config,
663*b0d29bc4SBrooks Davis                            scheduler_handle).parse());
664*b0d29bc4SBrooks Davis }
665*b0d29bc4SBrooks Davis 
666*b0d29bc4SBrooks Davis 
667*b0d29bc4SBrooks Davis /// Gets the root directory of the test suite.
668*b0d29bc4SBrooks Davis ///
669*b0d29bc4SBrooks Davis /// \return A path.
670*b0d29bc4SBrooks Davis const fs::path&
source_root(void) const671*b0d29bc4SBrooks Davis engine::kyuafile::source_root(void) const
672*b0d29bc4SBrooks Davis {
673*b0d29bc4SBrooks Davis     return _source_root;
674*b0d29bc4SBrooks Davis }
675*b0d29bc4SBrooks Davis 
676*b0d29bc4SBrooks Davis 
677*b0d29bc4SBrooks Davis /// Gets the root directory of the test programs.
678*b0d29bc4SBrooks Davis ///
679*b0d29bc4SBrooks Davis /// \return A path.
680*b0d29bc4SBrooks Davis const fs::path&
build_root(void) const681*b0d29bc4SBrooks Davis engine::kyuafile::build_root(void) const
682*b0d29bc4SBrooks Davis {
683*b0d29bc4SBrooks Davis     return _build_root;
684*b0d29bc4SBrooks Davis }
685*b0d29bc4SBrooks Davis 
686*b0d29bc4SBrooks Davis 
687*b0d29bc4SBrooks Davis /// Gets the collection of test programs that belong to this test suite.
688*b0d29bc4SBrooks Davis ///
689*b0d29bc4SBrooks Davis /// \return Collection of test program executable names.
690*b0d29bc4SBrooks Davis const model::test_programs_vector&
test_programs(void) const691*b0d29bc4SBrooks Davis engine::kyuafile::test_programs(void) const
692*b0d29bc4SBrooks Davis {
693*b0d29bc4SBrooks Davis     return _test_programs;
694*b0d29bc4SBrooks Davis }
695