1 // Copyright 2010 Google Inc.
2 // All rights reserved.
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
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"
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
;
54 using utils::optional
;
60 /// Lua hook for the test_case function.
62 /// \pre state(-1) contains the arguments to the function.
64 /// \param state The Lua state in which we are running.
66 /// \return The number of return values, which is always 0.
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
* >();
78 state
.get_global("_test_program");
79 const engine::test_program
* test_program
=
80 *state
.to_userdata
< engine::test_program
* >();
83 state
.push_string("name");
85 const std::string name
= state
.to_string();
88 engine::metadata_builder
mdbuilder(test_program
->get_metadata());
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
);
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
);
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.
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
;
149 setup_lua_state(state
, &test_program
, &test_cases
);
150 lutok::do_string(state
, output
, 0);
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.
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.
178 compare_test_cases(const optional
< engine::test_cases_vector
>& tests1
,
179 const optional
< engine::test_cases_vector
>& tests2
)
181 if (!tests1
&& !tests2
)
183 else if ((tests1
&& !tests2
) || (!tests1
&& tests2
))
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(),
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.
212 /// Root of the test suite containing the test program.
215 /// Name of the test suite this program belongs to.
216 std::string test_suite_name
;
218 /// Metadata of the test program.
221 /// List of test cases in the test program; lazily initialized.
222 optional
< test_cases_vector
> test_cases
;
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
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_
),
238 test_suite_name(test_suite_name_
),
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.
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
&&
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.
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.
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.
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.
322 engine::test_program::root(void) const
328 /// Gets the name of the test suite containing this test program.
330 /// \return The name of the test suite.
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
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
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
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
)
367 throw not_found_error(F("Unknown test case %s in test program %s") % name
%
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
) {
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
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.
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.
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.
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.
455 engine::operator<<(std::ostream
& output
, const test_cases_vector
& object
)
458 for (test_cases_vector::size_type i
= 0; i
< object
.size(); ++i
) {
461 output
<< *object
[i
];
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.
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();