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