1 // Copyright 2010 The Kyua Authors.
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 "model/test_program.hpp"
30
31 #include <map>
32 #include <sstream>
33
34 #include "model/exceptions.hpp"
35 #include "model/metadata.hpp"
36 #include "model/test_case.hpp"
37 #include "model/test_result.hpp"
38 #include "utils/format/containers.ipp"
39 #include "utils/format/macros.hpp"
40 #include "utils/fs/path.hpp"
41 #include "utils/noncopyable.hpp"
42 #include "utils/sanity.hpp"
43 #include "utils/text/operations.ipp"
44
45 namespace fs = utils::fs;
46 namespace text = utils::text;
47
48 using utils::none;
49
50
51 /// Internal implementation of a test_program.
52 struct model::test_program::impl : utils::noncopyable {
53 /// Name of the test program interface.
54 std::string interface_name;
55
56 /// Name of the test program binary relative to root.
57 fs::path binary;
58
59 /// Root of the test suite containing the test program.
60 fs::path root;
61
62 /// Name of the test suite this program belongs to.
63 std::string test_suite_name;
64
65 /// Metadata of the test program.
66 model::metadata md;
67
68 /// List of test cases in the test program.
69 ///
70 /// Must be queried via the test_program::test_cases() method.
71 model::test_cases_map test_cases;
72
73 /// Constructor.
74 ///
75 /// \param interface_name_ Name of the test program interface.
76 /// \param binary_ The name of the test program binary relative to root_.
77 /// \param root_ The root of the test suite containing the test program.
78 /// \param test_suite_name_ The name of the test suite this program
79 /// belongs to.
80 /// \param md_ Metadata of the test program.
81 /// \param test_cases_ The collection of test cases in the test program.
implmodel::test_program::impl82 impl(const std::string& interface_name_, const fs::path& binary_,
83 const fs::path& root_, const std::string& test_suite_name_,
84 const model::metadata& md_, const model::test_cases_map& test_cases_) :
85 interface_name(interface_name_),
86 binary(binary_),
87 root(root_),
88 test_suite_name(test_suite_name_),
89 md(md_)
90 {
91 PRE_MSG(!binary.is_absolute(),
92 F("The program '%s' must be relative to the root of the test "
93 "suite '%s'") % binary % root);
94
95 set_test_cases(test_cases_);
96 }
97
98 /// Sets the list of test cases of the test program.
99 ///
100 /// \param test_cases_ The new list of test cases.
101 void
set_test_casesmodel::test_program::impl102 set_test_cases(const model::test_cases_map& test_cases_)
103 {
104 for (model::test_cases_map::const_iterator iter = test_cases_.begin();
105 iter != test_cases_.end(); ++iter) {
106 const std::string& name = (*iter).first;
107 const model::test_case& test_case = (*iter).second;
108
109 PRE_MSG(name == test_case.name(),
110 F("The test case '%s' has been registered with the "
111 "non-matching name '%s'") % name % test_case.name());
112
113 test_cases.insert(model::test_cases_map::value_type(
114 name, test_case.apply_metadata_defaults(&md)));
115 }
116 INV(test_cases.size() == test_cases_.size());
117 }
118 };
119
120
121 /// Constructs a new test program.
122 ///
123 /// \param interface_name_ Name of the test program interface.
124 /// \param binary_ The name of the test program binary relative to root_.
125 /// \param root_ The root of the test suite containing the test program.
126 /// \param test_suite_name_ The name of the test suite this program belongs to.
127 /// \param md_ Metadata of the test program.
128 /// \param test_cases_ The collection of test cases in 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 model::metadata & md_,const model::test_cases_map & test_cases_)129 model::test_program::test_program(const std::string& interface_name_,
130 const fs::path& binary_,
131 const fs::path& root_,
132 const std::string& test_suite_name_,
133 const model::metadata& md_,
134 const model::test_cases_map& test_cases_) :
135 _pimpl(new impl(interface_name_, binary_, root_, test_suite_name_, md_,
136 test_cases_))
137 {
138 }
139
140
141 /// Destroys a test program.
~test_program(void)142 model::test_program::~test_program(void)
143 {
144 }
145
146
147 /// Gets the name of the test program interface.
148 ///
149 /// \return An interface name.
150 const std::string&
interface_name(void) const151 model::test_program::interface_name(void) const
152 {
153 return _pimpl->interface_name;
154 }
155
156
157 /// Gets the path to the test program relative to the root of the test suite.
158 ///
159 /// \return The relative path to the test program binary.
160 const fs::path&
relative_path(void) const161 model::test_program::relative_path(void) const
162 {
163 return _pimpl->binary;
164 }
165
166
167 /// Gets the absolute path to the test program.
168 ///
169 /// \return The absolute path to the test program binary.
170 const fs::path
absolute_path(void) const171 model::test_program::absolute_path(void) const
172 {
173 const fs::path full_path = _pimpl->root / _pimpl->binary;
174 return full_path.is_absolute() ? full_path : full_path.to_absolute();
175 }
176
177
178 /// Gets the root of the test suite containing this test program.
179 ///
180 /// \return The path to the root of the test suite.
181 const fs::path&
root(void) const182 model::test_program::root(void) const
183 {
184 return _pimpl->root;
185 }
186
187
188 /// Gets the name of the test suite containing this test program.
189 ///
190 /// \return The name of the test suite.
191 const std::string&
test_suite_name(void) const192 model::test_program::test_suite_name(void) const
193 {
194 return _pimpl->test_suite_name;
195 }
196
197
198 /// Gets the metadata of the test program.
199 ///
200 /// \return The metadata.
201 const model::metadata&
get_metadata(void) const202 model::test_program::get_metadata(void) const
203 {
204 return _pimpl->md;
205 }
206
207
208 /// Gets a test case by its name.
209 ///
210 /// \param name The name of the test case to locate.
211 ///
212 /// \return The requested test case.
213 ///
214 /// \throw not_found_error If the specified test case is not in the test
215 /// program.
216 const model::test_case&
find(const std::string & name) const217 model::test_program::find(const std::string& name) const
218 {
219 const test_cases_map& tcs = test_cases();
220
221 const test_cases_map::const_iterator iter = tcs.find(name);
222 if (iter == tcs.end())
223 throw not_found_error(F("Unknown test case %s in test program %s") %
224 name % relative_path());
225 return (*iter).second;
226 }
227
228
229 /// Gets the list of test cases from the test program.
230 ///
231 /// \return The list of test cases provided by the test program.
232 const model::test_cases_map&
test_cases(void) const233 model::test_program::test_cases(void) const
234 {
235 return _pimpl->test_cases;
236 }
237
238
239 /// Sets the list of test cases of the test program.
240 ///
241 /// This can only be called once and it may only be called from within
242 /// overridden test_cases() before that method ever returns a value for the
243 /// first time. Any other invocations will result in inconsistent program
244 /// state.
245 ///
246 /// \param test_cases_ The new list of test cases.
247 void
set_test_cases(const model::test_cases_map & test_cases_)248 model::test_program::set_test_cases(const model::test_cases_map& test_cases_)
249 {
250 PRE(_pimpl->test_cases.empty());
251 _pimpl->set_test_cases(test_cases_);
252 }
253
254
255 /// Equality comparator.
256 ///
257 /// \param other The other object to compare this one to.
258 ///
259 /// \return True if this object and other are equal; false otherwise.
260 bool
operator ==(const test_program & other) const261 model::test_program::operator==(const test_program& other) const
262 {
263 return _pimpl == other._pimpl || (
264 _pimpl->interface_name == other._pimpl->interface_name &&
265 _pimpl->binary == other._pimpl->binary &&
266 _pimpl->root == other._pimpl->root &&
267 _pimpl->test_suite_name == other._pimpl->test_suite_name &&
268 _pimpl->md == other._pimpl->md &&
269 test_cases() == other.test_cases());
270 }
271
272
273 /// Inequality comparator.
274 ///
275 /// \param other The other object to compare this one to.
276 ///
277 /// \return True if this object and other are different; false otherwise.
278 bool
operator !=(const test_program & other) const279 model::test_program::operator!=(const test_program& other) const
280 {
281 return !(*this == other);
282 }
283
284
285 /// Less-than comparator.
286 ///
287 /// A test program is considered to be less than another if and only if the
288 /// former's absolute path is less than the absolute path of the latter. In
289 /// other words, the absolute path is used here as the test program's
290 /// identifier.
291 ///
292 /// This simplistic less-than operator overload is provided so that test
293 /// programs can be held in sets and other containers.
294 ///
295 /// \param other The other object to compare this one to.
296 ///
297 /// \return True if this object sorts before the other object; false otherwise.
298 bool
operator <(const test_program & other) const299 model::test_program::operator<(const test_program& other) const
300 {
301 return absolute_path() < other.absolute_path();
302 }
303
304
305 /// Injects the object into a stream.
306 ///
307 /// \param output The stream into which to inject the object.
308 /// \param object The object to format.
309 ///
310 /// \return The output stream.
311 std::ostream&
operator <<(std::ostream & output,const test_program & object)312 model::operator<<(std::ostream& output, const test_program& object)
313 {
314 output << F("test_program{interface=%s, binary=%s, root=%s, test_suite=%s, "
315 "metadata=%s, test_cases=%s}")
316 % text::quote(object.interface_name(), '\'')
317 % text::quote(object.relative_path().str(), '\'')
318 % text::quote(object.root().str(), '\'')
319 % text::quote(object.test_suite_name(), '\'')
320 % object.get_metadata()
321 % object.test_cases();
322 return output;
323 }
324
325
326 /// Internal implementation of the test_program_builder class.
327 struct model::test_program_builder::impl : utils::noncopyable {
328 /// Partially-constructed program with only the required properties.
329 model::test_program prototype;
330
331 /// Optional metadata for the test program.
332 model::metadata metadata;
333
334 /// Collection of test cases.
335 model::test_cases_map test_cases;
336
337 /// Whether we have created a test_program object or not.
338 bool built;
339
340 /// Constructor.
341 ///
342 /// \param prototype_ The partially constructed program with only the
343 /// required properties.
implmodel::test_program_builder::impl344 impl(const model::test_program& prototype_) :
345 prototype(prototype_),
346 metadata(model::metadata_builder().build()),
347 built(false)
348 {
349 }
350 };
351
352
353 /// Constructs a new builder with non-optional values.
354 ///
355 /// \param interface_name_ Name of the test program interface.
356 /// \param binary_ The name of the test program binary relative to root_.
357 /// \param root_ The root of the test suite containing the test program.
358 /// \param test_suite_name_ The name of the test suite this program belongs to.
test_program_builder(const std::string & interface_name_,const fs::path & binary_,const fs::path & root_,const std::string & test_suite_name_)359 model::test_program_builder::test_program_builder(
360 const std::string& interface_name_, const fs::path& binary_,
361 const fs::path& root_, const std::string& test_suite_name_) :
362 _pimpl(new impl(model::test_program(interface_name_, binary_, root_,
363 test_suite_name_,
364 model::metadata_builder().build(),
365 model::test_cases_map())))
366 {
367 }
368
369
370 /// Destructor.
~test_program_builder(void)371 model::test_program_builder::~test_program_builder(void)
372 {
373 }
374
375
376 /// Accumulates an additional test case with default metadata.
377 ///
378 /// \param test_case_name The name of the test case.
379 ///
380 /// \return A reference to this builder.
381 model::test_program_builder&
add_test_case(const std::string & test_case_name)382 model::test_program_builder::add_test_case(const std::string& test_case_name)
383 {
384 return add_test_case(test_case_name, model::metadata_builder().build());
385 }
386
387
388 /// Accumulates an additional test case.
389 ///
390 /// \param test_case_name The name of the test case.
391 /// \param metadata The test case metadata.
392 ///
393 /// \return A reference to this builder.
394 model::test_program_builder&
add_test_case(const std::string & test_case_name,const model::metadata & metadata)395 model::test_program_builder::add_test_case(const std::string& test_case_name,
396 const model::metadata& metadata)
397 {
398 const model::test_case test_case(test_case_name, metadata);
399 PRE_MSG(_pimpl->test_cases.find(test_case_name) == _pimpl->test_cases.end(),
400 F("Attempted to re-register test case '%s'") % test_case_name);
401 _pimpl->test_cases.insert(model::test_cases_map::value_type(
402 test_case_name, test_case));
403 return *this;
404 }
405
406
407 /// Sets the test program metadata.
408 ///
409 /// \return metadata The metadata for the test program.
410 ///
411 /// \return A reference to this builder.
412 model::test_program_builder&
set_metadata(const model::metadata & metadata)413 model::test_program_builder::set_metadata(const model::metadata& metadata)
414 {
415 _pimpl->metadata = metadata;
416 return *this;
417 }
418
419
420 /// Creates a new test_program object.
421 ///
422 /// \pre This has not yet been called. We only support calling this function
423 /// once.
424 ///
425 /// \return The constructed test_program object.
426 model::test_program
build(void) const427 model::test_program_builder::build(void) const
428 {
429 PRE(!_pimpl->built);
430 _pimpl->built = true;
431
432 return test_program(_pimpl->prototype.interface_name(),
433 _pimpl->prototype.relative_path(),
434 _pimpl->prototype.root(),
435 _pimpl->prototype.test_suite_name(),
436 _pimpl->metadata,
437 _pimpl->test_cases);
438 }
439
440
441 /// Creates a new dynamically-allocated test_program object.
442 ///
443 /// \pre This has not yet been called. We only support calling this function
444 /// once.
445 ///
446 /// \return The constructed test_program object.
447 model::test_program_ptr
build_ptr(void) const448 model::test_program_builder::build_ptr(void) const
449 {
450 const test_program result = build();
451 return test_program_ptr(new test_program(result));
452 }
453