Remove building with NOCRYPTO option
[minix3.git] / external / bsd / kyua-cli / dist / engine / test_program.cpp
blob9cec29e9df2687b664a1fd350a32337ae1ed0c98
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.
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.
29 #include "engine/test_program.hpp"
31 #include <algorithm>
32 #include <map>
33 #include <sstream>
34 #include <stdexcept>
36 #include <lutok/operations.hpp>
37 #include <lutok/state.ipp>
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"
49 namespace fs = utils::fs;
50 namespace logging = utils::logging;
51 namespace text = utils::text;
53 using utils::none;
54 using utils::optional;
57 namespace {
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
68 lua_test_case(lutok::state& state)
70 if (!state.is_table())
71 throw std::runtime_error("Oh noes"); // XXX
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);
78 state.get_global("_test_program");
79 const engine::test_program* test_program =
80 *state.to_userdata< engine::test_program* >();
81 state.pop(1);
83 state.push_string("name");
84 state.get_table(-2);
85 const std::string name = state.to_string();
86 state.pop(1);
88 engine::metadata_builder mdbuilder(test_program->get_metadata());
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);
96 if (!state.is_string(-1))
97 throw std::runtime_error("Oh oh"); // XXX
98 const std::string value = state.to_string(-1);
100 if (property != "name")
101 mdbuilder.set_string(property, value);
103 state.pop(1);
105 state.pop(1);
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);
112 return 0;
116 /// Sets up the Lua state to process the output of a test case list.
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
122 setup_lua_state(lutok::state& state, const engine::test_program* test_program,
123 engine::test_cases_vector* test_cases)
125 *state.new_userdata< engine::test_cases_vector* >() = test_cases;
126 state.set_global("_test_cases");
128 *state.new_userdata< const engine::test_program* >() = test_program;
129 state.set_global("_test_program");
131 state.push_cxx_function(lua_test_case);
132 state.set_global("test_case");
136 /// Loads the list of test cases from a test program.
138 /// \param test_program Representation of the test program to load.
140 /// \return A list of test cases.
141 static engine::test_cases_vector
142 load_test_cases(const engine::test_program& test_program)
144 const engine::tester tester(test_program.interface_name(), none, none);
145 const std::string output = tester.list(test_program.absolute_path());
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;
155 /// Predicate to compare two test cases via pointers to them.
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.
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
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)
166 return tc1.first == tc2.first && *tc1.second == *tc2.second;
170 /// Compares if two sets of test cases hold the same values.
172 /// \param tests1 First collection of test cases.
173 /// \param tests2 Second collection of test cases.
175 /// \return True if both collections hold the same test cases (value-wise, not
176 /// pointer-wise); false otherwise.
177 static bool
178 compare_test_cases(const optional< engine::test_cases_vector >& tests1,
179 const optional< engine::test_cases_vector >& tests2)
181 if (!tests1 && !tests2)
182 return true;
183 else if ((tests1 && !tests2) || (!tests1 && tests2))
184 return false;
185 INV(tests1 && tests2);
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);
201 } // anonymous namespace
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;
209 /// Name of the test program binary relative to root.
210 fs::path binary;
212 /// Root of the test suite containing the test program.
213 fs::path root;
215 /// Name of the test suite this program belongs to.
216 std::string test_suite_name;
218 /// Metadata of the test program.
219 metadata md;
221 /// List of test cases in the test program; lazily initialized.
222 optional< test_cases_vector > test_cases;
224 /// Constructor.
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.
232 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_)
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);
246 /// Equality comparator.
248 /// \param other The other object to compare this one to.
250 /// \return True if this object and other are equal; false otherwise.
251 bool
252 operator==(const impl& other) const
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));
264 /// Constructs a new test program.
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.
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_))
281 /// Destroys a test program.
282 engine::test_program::~test_program(void)
287 /// Gets the name of the test program interface.
289 /// \return An interface name.
290 const std::string&
291 engine::test_program::interface_name(void) const
293 return _pimpl->interface_name;
297 /// Gets the path to the test program relative to the root of the test suite.
299 /// \return The relative path to the test program binary.
300 const fs::path&
301 engine::test_program::relative_path(void) const
303 return _pimpl->binary;
307 /// Gets the absolute path to the test program.
309 /// \return The absolute path to the test program binary.
310 const fs::path
311 engine::test_program::absolute_path(void) const
313 const fs::path full_path = _pimpl->root / _pimpl->binary;
314 return full_path.is_absolute() ? full_path : full_path.to_absolute();
318 /// Gets the root of the test suite containing this test program.
320 /// \return The path to the root of the test suite.
321 const fs::path&
322 engine::test_program::root(void) const
324 return _pimpl->root;
328 /// Gets the name of the test suite containing this test program.
330 /// \return The name of the test suite.
331 const std::string&
332 engine::test_program::test_suite_name(void) const
334 return _pimpl->test_suite_name;
338 /// Gets the metadata of the test program.
340 /// \return The metadata.
341 const engine::metadata&
342 engine::test_program::get_metadata(void) const
344 return _pimpl->md;
348 /// Gets a test case by its name.
350 /// \param name The name of the test case to locate.
352 /// \return The requested test case.
354 /// \throw not_found_error If the specified test case is not in the test
355 /// program.
356 const engine::test_case_ptr&
357 engine::test_program::find(const std::string& name) const
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;
367 throw not_found_error(F("Unknown test case %s in test program %s") % name %
368 relative_path());
372 /// Gets the list of test cases from the test program.
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.
378 /// \return The list of test cases provided by the test program.
379 const engine::test_cases_vector&
380 engine::test_program::test_cases(void) const
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;
399 return _pimpl->test_cases.get();
403 /// Sets the collection of test cases included in this test program.
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.
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.
412 /// \pre The test program must not have attempted to load its test cases yet.
413 /// I.e. test_cases() has not been called.
415 /// \param test_cases_ The test cases to add to this test program.
416 void
417 engine::test_program::set_test_cases(const test_cases_vector& test_cases_)
419 PRE(!_pimpl->test_cases);
420 _pimpl->test_cases = test_cases_;
424 /// Equality comparator.
426 /// \param other The other object to compare this one to.
428 /// \return True if this object and other are equal; false otherwise.
429 bool
430 engine::test_program::operator==(const test_program& other) const
432 return _pimpl == other._pimpl || *_pimpl == *other._pimpl;
436 /// Inequality comparator.
438 /// \param other The other object to compare this one to.
440 /// \return True if this object and other are different; false otherwise.
441 bool
442 engine::test_program::operator!=(const test_program& other) const
444 return !(*this == other);
448 /// Injects the object into a stream.
450 /// \param output The stream into which to inject the object.
451 /// \param object The object to format.
453 /// \return The output stream.
454 std::ostream&
455 engine::operator<<(std::ostream& output, const test_cases_vector& object)
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];
463 output << "]";
464 return output;
468 /// Injects the object into a stream.
470 /// \param output The stream into which to inject the object.
471 /// \param object The object to format.
473 /// \return The output stream.
474 std::ostream&
475 engine::operator<<(std::ostream& output, const test_program& object)
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;