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
45namespace fs = utils::fs;
46namespace text = utils::text;
47
48using utils::none;
49
50
51/// Internal implementation of a test_program.
52struct 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.
82    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
102    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.
129model::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.
142model::test_program::~test_program(void)
143{
144}
145
146
147/// Gets the name of the test program interface.
148///
149/// \return An interface name.
150const std::string&
151model::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.
160const fs::path&
161model::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.
170const fs::path
171model::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.
181const fs::path&
182model::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.
191const std::string&
192model::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.
201const model::metadata&
202model::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.
216const model::test_case&
217model::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.
232const model::test_cases_map&
233model::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.
247void
248model::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.
260bool
261model::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.
278bool
279model::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.
298bool
299model::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.
311std::ostream&
312model::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.
327struct 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.
344    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.
359model::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.
371model::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.
381model::test_program_builder&
382model::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.
394model::test_program_builder&
395model::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.
412model::test_program_builder&
413model::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.
426model::test_program
427model::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.
447model::test_program_ptr
448model::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