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