xref: /netbsd-src/external/bsd/kyua-cli/dist/engine/test_program.cpp (revision 6b3a42af15b5e090c339512c790dd68f3d11a9d8)
1 // Copyright 2010 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "engine/test_program.hpp"
30 
31 #include <algorithm>
32 #include <map>
33 #include <sstream>
34 #include <stdexcept>
35 
36 #include <lutok/operations.hpp>
37 #include <lutok/state.ipp>
38 
39 #include "engine/exceptions.hpp"
40 #include "engine/test_result.hpp"
41 #include "engine/testers.hpp"
42 #include "utils/format/macros.hpp"
43 #include "utils/logging/macros.hpp"
44 #include "utils/logging/operations.hpp"
45 #include "utils/optional.ipp"
46 #include "utils/sanity.hpp"
47 #include "utils/text/operations.ipp"
48 
49 namespace fs = utils::fs;
50 namespace logging = utils::logging;
51 namespace text = utils::text;
52 
53 using utils::none;
54 using utils::optional;
55 
56 
57 namespace {
58 
59 
60 /// Lua hook for the test_case function.
61 ///
62 /// \pre state(-1) contains the arguments to the function.
63 ///
64 /// \param state The Lua state in which we are running.
65 ///
66 /// \return The number of return values, which is always 0.
67 static int
lua_test_case(lutok::state & state)68 lua_test_case(lutok::state& state)
69 {
70     if (!state.is_table())
71         throw std::runtime_error("Oh noes"); // XXX
72 
73     state.get_global("_test_cases");
74     engine::test_cases_vector* test_cases =
75         *state.to_userdata< engine::test_cases_vector* >();
76     state.pop(1);
77 
78     state.get_global("_test_program");
79     const engine::test_program* test_program =
80         *state.to_userdata< engine::test_program* >();
81     state.pop(1);
82 
83     state.push_string("name");
84     state.get_table(-2);
85     const std::string name = state.to_string();
86     state.pop(1);
87 
88     engine::metadata_builder mdbuilder(test_program->get_metadata());
89 
90     state.push_nil();
91     while (state.next(-2)) {
92         if (!state.is_string(-2))
93             throw std::runtime_error("Oh oh");  // XXX
94         const std::string property = state.to_string(-2);
95 
96         if (!state.is_string(-1))
97             throw std::runtime_error("Oh oh");  // XXX
98         const std::string value = state.to_string(-1);
99 
100         if (property != "name")
101             mdbuilder.set_string(property, value);
102 
103         state.pop(1);
104     }
105     state.pop(1);
106 
107     engine::test_case_ptr test_case(
108         new engine::test_case(test_program->interface_name(), *test_program,
109                               name, mdbuilder.build()));
110     test_cases->push_back(test_case);
111 
112     return 0;
113 }
114 
115 
116 /// Sets up the Lua state to process the output of a test case list.
117 ///
118 /// \param [in,out] state The Lua state to configure.
119 /// \param test_program Pointer to the test program being loaded.
120 /// \param [out] test_cases Vector that will contain the list of test cases.
121 static void
setup_lua_state(lutok::state & state,const engine::test_program * test_program,engine::test_cases_vector * test_cases)122 setup_lua_state(lutok::state& state, const engine::test_program* test_program,
123                 engine::test_cases_vector* test_cases)
124 {
125     *state.new_userdata< engine::test_cases_vector* >() = test_cases;
126     state.set_global("_test_cases");
127 
128     *state.new_userdata< const engine::test_program* >() = test_program;
129     state.set_global("_test_program");
130 
131     state.push_cxx_function(lua_test_case);
132     state.set_global("test_case");
133 }
134 
135 
136 /// Loads the list of test cases from a test program.
137 ///
138 /// \param test_program Representation of the test program to load.
139 ///
140 /// \return A list of test cases.
141 static engine::test_cases_vector
load_test_cases(const engine::test_program & test_program)142 load_test_cases(const engine::test_program& test_program)
143 {
144     const engine::tester tester(test_program.interface_name(), none, none);
145     const std::string output = tester.list(test_program.absolute_path());
146 
147     engine::test_cases_vector test_cases;
148     lutok::state state;
149     setup_lua_state(state, &test_program, &test_cases);
150     lutok::do_string(state, output, 0);
151     return test_cases;
152 }
153 
154 
155 /// Predicate to compare two test cases via pointers to them.
156 ///
157 /// \param tc1 Entry in a map of test case names to test case pointers.
158 /// \param tc2 Entry in a map of test case names to test case pointers.
159 ///
160 /// \return True if the test case in tc1 is the same as in tc2.  Note that the
161 /// container test programs are NOT compared.
162 static bool
compare_test_case(const std::pair<std::string,engine::test_case_ptr> & tc1,const std::pair<std::string,engine::test_case_ptr> & tc2)163 compare_test_case(const std::pair< std::string, engine::test_case_ptr >& tc1,
164                   const std::pair< std::string, engine::test_case_ptr >& tc2)
165 {
166     return tc1.first == tc2.first && *tc1.second == *tc2.second;
167 }
168 
169 
170 /// Compares if two sets of test cases hold the same values.
171 ///
172 /// \param tests1 First collection of test cases.
173 /// \param tests2 Second collection of test cases.
174 ///
175 /// \return True if both collections hold the same test cases (value-wise, not
176 /// pointer-wise); false otherwise.
177 static bool
compare_test_cases(const optional<engine::test_cases_vector> & tests1,const optional<engine::test_cases_vector> & tests2)178 compare_test_cases(const optional< engine::test_cases_vector >& tests1,
179                    const optional< engine::test_cases_vector >& tests2)
180 {
181     if (!tests1 && !tests2)
182         return true;
183     else if ((tests1 && !tests2) || (!tests1 && tests2))
184         return false;
185     INV(tests1 && tests2);
186 
187     // This is very inefficient, but because it should only be used in our own
188     // tests, it doesn't matter.
189     std::map< std::string, engine::test_case_ptr > map1, map2;
190     for (engine::test_cases_vector::const_iterator iter = tests1.get().begin();
191          iter != tests1.get().end(); ++iter)
192         map1.insert(make_pair((*iter)->name(), *iter));
193     for (engine::test_cases_vector::const_iterator iter = tests2.get().begin();
194          iter != tests2.get().end(); ++iter)
195         map2.insert(make_pair((*iter)->name(), *iter));
196     return std::equal(map1.begin(), map1.end(), map2.begin(),
197                       compare_test_case);
198 }
199 
200 
201 }  // anonymous namespace
202 
203 
204 /// Internal implementation of a test_program.
205 struct engine::test_program::impl {
206     /// Name of the test program interface.
207     std::string interface_name;
208 
209     /// Name of the test program binary relative to root.
210     fs::path binary;
211 
212     /// Root of the test suite containing the test program.
213     fs::path root;
214 
215     /// Name of the test suite this program belongs to.
216     std::string test_suite_name;
217 
218     /// Metadata of the test program.
219     metadata md;
220 
221     /// List of test cases in the test program; lazily initialized.
222     optional< test_cases_vector > test_cases;
223 
224     /// Constructor.
225     ///
226     /// \param interface_name_ Name of the test program interface.
227     /// \param binary_ The name of the test program binary relative to root_.
228     /// \param root_ The root of the test suite containing the test program.
229     /// \param test_suite_name_ The name of the test suite this program
230     ///     belongs to.
231     /// \param md_ Metadata of the test program.
implengine::test_program::impl232     impl(const std::string& interface_name_, const fs::path& binary_,
233          const fs::path& root_, const std::string& test_suite_name_,
234          const metadata& md_) :
235         interface_name(interface_name_),
236         binary(binary_),
237         root(root_),
238         test_suite_name(test_suite_name_),
239         md(md_)
240     {
241         PRE_MSG(!binary.is_absolute(),
242                 F("The program '%s' must be relative to the root of the test "
243                   "suite '%s'") % binary % root);
244     }
245 
246     /// Equality comparator.
247     ///
248     /// \param other The other object to compare this one to.
249     ///
250     /// \return True if this object and other are equal; false otherwise.
251     bool
operator ==engine::test_program::impl252     operator==(const impl& other) const
253     {
254         return (interface_name == other.interface_name &&
255                 binary == other.binary &&
256                 root == other.root &&
257                 test_suite_name == other.test_suite_name &&
258                 md == other.md &&
259                 compare_test_cases(test_cases, other.test_cases));
260     }
261 };
262 
263 
264 /// Constructs a new test program.
265 ///
266 /// \param interface_name_ Name of the test program interface.
267 /// \param binary_ The name of the test program binary relative to root_.
268 /// \param root_ The root of the test suite containing the test program.
269 /// \param test_suite_name_ The name of the test suite this program belongs to.
270 /// \param md_ Metadata of the test program.
test_program(const std::string & interface_name_,const fs::path & binary_,const fs::path & root_,const std::string & test_suite_name_,const metadata & md_)271 engine::test_program::test_program(const std::string& interface_name_,
272                                    const fs::path& binary_,
273                                    const fs::path& root_,
274                                    const std::string& test_suite_name_,
275                                    const metadata& md_) :
276     _pimpl(new impl(interface_name_, binary_, root_, test_suite_name_, md_))
277 {
278 }
279 
280 
281 /// Destroys a test program.
~test_program(void)282 engine::test_program::~test_program(void)
283 {
284 }
285 
286 
287 /// Gets the name of the test program interface.
288 ///
289 /// \return An interface name.
290 const std::string&
interface_name(void) const291 engine::test_program::interface_name(void) const
292 {
293     return _pimpl->interface_name;
294 }
295 
296 
297 /// Gets the path to the test program relative to the root of the test suite.
298 ///
299 /// \return The relative path to the test program binary.
300 const fs::path&
relative_path(void) const301 engine::test_program::relative_path(void) const
302 {
303     return _pimpl->binary;
304 }
305 
306 
307 /// Gets the absolute path to the test program.
308 ///
309 /// \return The absolute path to the test program binary.
310 const fs::path
absolute_path(void) const311 engine::test_program::absolute_path(void) const
312 {
313     const fs::path full_path = _pimpl->root / _pimpl->binary;
314     return full_path.is_absolute() ? full_path : full_path.to_absolute();
315 }
316 
317 
318 /// Gets the root of the test suite containing this test program.
319 ///
320 /// \return The path to the root of the test suite.
321 const fs::path&
root(void) const322 engine::test_program::root(void) const
323 {
324     return _pimpl->root;
325 }
326 
327 
328 /// Gets the name of the test suite containing this test program.
329 ///
330 /// \return The name of the test suite.
331 const std::string&
test_suite_name(void) const332 engine::test_program::test_suite_name(void) const
333 {
334     return _pimpl->test_suite_name;
335 }
336 
337 
338 /// Gets the metadata of the test program.
339 ///
340 /// \return The metadata.
341 const engine::metadata&
get_metadata(void) const342 engine::test_program::get_metadata(void) const
343 {
344     return _pimpl->md;
345 }
346 
347 
348 /// Gets a test case by its name.
349 ///
350 /// \param name The name of the test case to locate.
351 ///
352 /// \return The requested test case.
353 ///
354 /// \throw not_found_error If the specified test case is not in the test
355 ///     program.
356 const engine::test_case_ptr&
find(const std::string & name) const357 engine::test_program::find(const std::string& name) const
358 {
359     // TODO(jmmv): Should use a test_cases_map instead of a vector to optimize
360     // lookups.
361     const test_cases_vector& tcs = test_cases();
362     for (test_cases_vector::const_iterator iter = tcs.begin();
363          iter != tcs.end(); iter++) {
364         if ((*iter)->name() == name)
365             return *iter;
366     }
367     throw not_found_error(F("Unknown test case %s in test program %s") % name %
368                           relative_path());
369 }
370 
371 
372 /// Gets the list of test cases from the test program.
373 ///
374 /// Note that this operation may be expensive because it may lazily load the
375 /// test cases list from the test program.  Errors during the processing of the
376 /// test case list are represented as a single test case describing the failure.
377 ///
378 /// \return The list of test cases provided by the test program.
379 const engine::test_cases_vector&
test_cases(void) const380 engine::test_program::test_cases(void) const
381 {
382     if (!_pimpl->test_cases) {
383         try {
384             _pimpl->test_cases = load_test_cases(*this);
385         } catch (const std::runtime_error& e) {
386             // TODO(jmmv): This is a very ugly workaround for the fact that we
387             // cannot report failures at the test-program level.  We should
388             // either address this, or move this reporting to the testers
389             // themselves.
390             LW(F("Failed to load test cases list: %s") % e.what());
391             engine::test_cases_vector fake_test_cases;
392             fake_test_cases.push_back(test_case_ptr(new test_case(
393                 _pimpl->interface_name, *this, "__test_cases_list__",
394                 "Represents the correct processing of the test cases list",
395                 test_result(engine::test_result::broken, e.what()))));
396             _pimpl->test_cases = fake_test_cases;
397         }
398     }
399     return _pimpl->test_cases.get();
400 }
401 
402 
403 /// Sets the collection of test cases included in this test program.
404 ///
405 /// This function is provided so that when we load test programs from the
406 /// database we can populate them with the test cases they include.  We don't
407 /// want such test programs to be executed to gather this information.
408 ///
409 /// We cannot provide this collection of tests in the constructor of the test
410 /// program because the test cases have to point to their test programs.
411 ///
412 /// \pre The test program must not have attempted to load its test cases yet.
413 ///     I.e. test_cases() has not been called.
414 ///
415 /// \param test_cases_ The test cases to add to this test program.
416 void
set_test_cases(const test_cases_vector & test_cases_)417 engine::test_program::set_test_cases(const test_cases_vector& test_cases_)
418 {
419     PRE(!_pimpl->test_cases);
420     _pimpl->test_cases = test_cases_;
421 }
422 
423 
424 /// Equality comparator.
425 ///
426 /// \param other The other object to compare this one to.
427 ///
428 /// \return True if this object and other are equal; false otherwise.
429 bool
operator ==(const test_program & other) const430 engine::test_program::operator==(const test_program& other) const
431 {
432     return _pimpl == other._pimpl || *_pimpl == *other._pimpl;
433 }
434 
435 
436 /// Inequality comparator.
437 ///
438 /// \param other The other object to compare this one to.
439 ///
440 /// \return True if this object and other are different; false otherwise.
441 bool
operator !=(const test_program & other) const442 engine::test_program::operator!=(const test_program& other) const
443 {
444     return !(*this == other);
445 }
446 
447 
448 /// Injects the object into a stream.
449 ///
450 /// \param output The stream into which to inject the object.
451 /// \param object The object to format.
452 ///
453 /// \return The output stream.
454 std::ostream&
operator <<(std::ostream & output,const test_cases_vector & object)455 engine::operator<<(std::ostream& output, const test_cases_vector& object)
456 {
457     output << "[";
458     for (test_cases_vector::size_type i = 0; i < object.size(); ++i) {
459         if (i != 0)
460             output << ", ";
461         output << *object[i];
462     }
463     output << "]";
464     return output;
465 }
466 
467 
468 /// Injects the object into a stream.
469 ///
470 /// \param output The stream into which to inject the object.
471 /// \param object The object to format.
472 ///
473 /// \return The output stream.
474 std::ostream&
operator <<(std::ostream & output,const test_program & object)475 engine::operator<<(std::ostream& output, const test_program& object)
476 {
477     output << F("test_program{interface=%s, binary=%s, root=%s, test_suite=%s, "
478                 "metadata=%s, test_cases=%s}")
479         % text::quote(object.interface_name(), '\'')
480         % text::quote(object.relative_path().str(), '\'')
481         % text::quote(object.root().str(), '\'')
482         % text::quote(object.test_suite_name(), '\'')
483         % object.get_metadata()
484         % object.test_cases();
485     return output;
486 }
487