xref: /freebsd/contrib/kyua/model/metadata.cpp (revision 83a1ee578c9d1ab7013e997289c7cd470c0e6902)
1 // Copyright 2012 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/metadata.hpp"
30 
31 #include <memory>
32 
33 #include "engine/execenv/execenv.hpp"
34 #include "model/exceptions.hpp"
35 #include "model/types.hpp"
36 #include "utils/config/exceptions.hpp"
37 #include "utils/config/nodes.ipp"
38 #include "utils/config/tree.ipp"
39 #include "utils/datetime.hpp"
40 #include "utils/defs.hpp"
41 #include "utils/format/macros.hpp"
42 #include "utils/fs/exceptions.hpp"
43 #include "utils/fs/path.hpp"
44 #include "utils/noncopyable.hpp"
45 #include "utils/optional.ipp"
46 #include "utils/sanity.hpp"
47 #include "utils/text/exceptions.hpp"
48 #include "utils/text/operations.hpp"
49 #include "utils/units.hpp"
50 
51 namespace config = utils::config;
52 namespace datetime = utils::datetime;
53 namespace fs = utils::fs;
54 namespace text = utils::text;
55 namespace units = utils::units;
56 
57 using utils::optional;
58 
59 
60 namespace {
61 
62 
63 /// Global instance of defaults.
64 ///
65 /// This exists so that the getters in metadata can return references instead
66 /// of object copies.  Use get_defaults() to query.
67 static optional< config::tree > defaults;
68 
69 
70 /// A leaf node that holds a bytes quantity.
71 class bytes_node : public config::native_leaf_node< units::bytes > {
72 public:
73     /// Copies the node.
74     ///
75     /// \return A dynamically-allocated node.
76     virtual base_node*
deep_copy(void) const77     deep_copy(void) const
78     {
79         std::unique_ptr< bytes_node > new_node(new bytes_node());
80         new_node->_value = _value;
81         return new_node.release();
82     }
83 
84     /// Pushes the node's value onto the Lua stack.
85     void
push_lua(lutok::state &) const86     push_lua(lutok::state& /* state */) const
87     {
88         UNREACHABLE;
89     }
90 
91     /// Sets the value of the node from an entry in the Lua stack.
92     void
set_lua(lutok::state &,const int)93     set_lua(lutok::state& /* state */, const int /* index */)
94     {
95         UNREACHABLE;
96     }
97 };
98 
99 
100 /// A leaf node that holds a time delta.
101 class delta_node : public config::typed_leaf_node< datetime::delta > {
102 public:
103     /// Copies the node.
104     ///
105     /// \return A dynamically-allocated node.
106     virtual base_node*
deep_copy(void) const107     deep_copy(void) const
108     {
109         std::unique_ptr< delta_node > new_node(new delta_node());
110         new_node->_value = _value;
111         return new_node.release();
112     }
113 
114     /// Sets the value of the node from a raw string representation.
115     ///
116     /// \param raw_value The value to set the node to.
117     ///
118     /// \throw value_error If the value is invalid.
119     void
set_string(const std::string & raw_value)120     set_string(const std::string& raw_value)
121     {
122         unsigned int seconds;
123         try {
124             seconds = text::to_type< unsigned int >(raw_value);
125         } catch (const text::error& e) {
126             throw config::value_error(F("Invalid time delta %s") % raw_value);
127         }
128         set(datetime::delta(seconds, 0));
129     }
130 
131     /// Converts the contents of the node to a string.
132     ///
133     /// \pre The node must have a value.
134     ///
135     /// \return A string representation of the value held by the node.
136     std::string
to_string(void) const137     to_string(void) const
138     {
139         return F("%s") % value().seconds;
140     }
141 
142     /// Pushes the node's value onto the Lua stack.
143     void
push_lua(lutok::state &) const144     push_lua(lutok::state& /* state */) const
145     {
146         UNREACHABLE;
147     }
148 
149     /// Sets the value of the node from an entry in the Lua stack.
150     void
set_lua(lutok::state &,const int)151     set_lua(lutok::state& /* state */, const int /* index */)
152     {
153         UNREACHABLE;
154     }
155 };
156 
157 
158 /// A leaf node that holds a "required user" property.
159 ///
160 /// This node is just a string, but it provides validation of the only allowed
161 /// values.
162 class user_node : public config::string_node {
163     /// Copies the node.
164     ///
165     /// \return A dynamically-allocated node.
166     virtual base_node*
deep_copy(void) const167     deep_copy(void) const
168     {
169         std::unique_ptr< user_node > new_node(new user_node());
170         new_node->_value = _value;
171         return new_node.release();
172     }
173 
174     /// Checks a given user textual representation for validity.
175     ///
176     /// \param user The value to validate.
177     ///
178     /// \throw config::value_error If the value is not valid.
179     void
validate(const value_type & user) const180     validate(const value_type& user) const
181     {
182         if (!user.empty() && user != "root" && user != "unprivileged")
183             throw config::value_error("Invalid required user value");
184     }
185 };
186 
187 
188 /// A leaf node that holds a set of paths.
189 ///
190 /// This node type is used to represent the value of the required files and
191 /// required programs, for example, and these do not allow relative paths.  We
192 /// check this here.
193 class paths_set_node : public config::base_set_node< fs::path > {
194     /// Copies the node.
195     ///
196     /// \return A dynamically-allocated node.
197     virtual base_node*
deep_copy(void) const198     deep_copy(void) const
199     {
200         std::unique_ptr< paths_set_node > new_node(new paths_set_node());
201         new_node->_value = _value;
202         return new_node.release();
203     }
204 
205     /// Converts a single path to the native type.
206     ///
207     /// \param raw_value The value to parse.
208     ///
209     /// \return The parsed value.
210     ///
211     /// \throw config::value_error If the value is invalid.
212     fs::path
parse_one(const std::string & raw_value) const213     parse_one(const std::string& raw_value) const
214     {
215         try {
216             return fs::path(raw_value);
217         } catch (const fs::error& e) {
218             throw config::value_error(e.what());
219         }
220     }
221 
222     /// Checks a collection of paths for validity.
223     ///
224     /// \param paths The value to validate.
225     ///
226     /// \throw config::value_error If the value is not valid.
227     void
validate(const value_type & paths) const228     validate(const value_type& paths) const
229     {
230         for (value_type::const_iterator iter = paths.begin();
231              iter != paths.end(); ++iter) {
232             const fs::path& path = *iter;
233             if (!path.is_absolute() && path.ncomponents() > 1)
234                 throw config::value_error(F("Relative path '%s' not allowed") %
235                                           *iter);
236         }
237     }
238 };
239 
240 
241 /// Initializes a tree to hold test case requirements.
242 ///
243 /// \param [in,out] tree The tree to initialize.
244 static void
init_tree(config::tree & tree)245 init_tree(config::tree& tree)
246 {
247     tree.define< config::strings_set_node >("allowed_architectures");
248     tree.define< config::strings_set_node >("allowed_platforms");
249     tree.define_dynamic("custom");
250     tree.define< config::string_node >("description");
251     tree.define< config::string_node >("execenv");
252     tree.define< config::string_node >("execenv_jail_params");
253     tree.define< config::bool_node >("has_cleanup");
254     tree.define< config::bool_node >("is_exclusive");
255     tree.define< config::strings_set_node >("required_configs");
256     tree.define< bytes_node >("required_disk_space");
257     tree.define< paths_set_node >("required_files");
258     tree.define< bytes_node >("required_memory");
259 #ifdef __FreeBSD__
260     tree.define< config::strings_set_node >("required_kmods");
261 #endif
262     tree.define< paths_set_node >("required_programs");
263     tree.define< user_node >("required_user");
264     tree.define< delta_node >("timeout");
265 }
266 
267 
268 /// Sets default values on a tree object.
269 ///
270 /// \param [in,out] tree The tree to configure.
271 static void
set_defaults(config::tree & tree)272 set_defaults(config::tree& tree)
273 {
274     tree.set< config::strings_set_node >("allowed_architectures",
275                                          model::strings_set());
276     tree.set< config::strings_set_node >("allowed_platforms",
277                                          model::strings_set());
278     tree.set< config::string_node >("description", "");
279     tree.set< config::string_node >("execenv", "");
280     tree.set< config::string_node >("execenv_jail_params", "");
281     tree.set< config::bool_node >("has_cleanup", false);
282     tree.set< config::bool_node >("is_exclusive", false);
283     tree.set< config::strings_set_node >("required_configs",
284                                          model::strings_set());
285     tree.set< bytes_node >("required_disk_space", units::bytes(0));
286     tree.set< paths_set_node >("required_files", model::paths_set());
287     tree.set< bytes_node >("required_memory", units::bytes(0));
288 #ifdef __FreeBSD__
289     tree.set< config::strings_set_node >("required_kmods", model::strings_set());
290 #endif
291     tree.set< paths_set_node >("required_programs", model::paths_set());
292     tree.set< user_node >("required_user", "");
293     // TODO(jmmv): We shouldn't be setting a default timeout like this.  See
294     // Issue 5 for details.
295     tree.set< delta_node >("timeout", datetime::delta(300, 0));
296 }
297 
298 
299 /// Queries the global defaults tree object with lazy initialization.
300 ///
301 /// \return A metadata tree.  This object is statically allocated so it is
302 /// acceptable to obtain references to it and its members.
303 const config::tree&
get_defaults(void)304 get_defaults(void)
305 {
306     if (!defaults) {
307         config::tree props;
308         init_tree(props);
309         set_defaults(props);
310         defaults = props;
311     }
312     return defaults.get();
313 }
314 
315 
316 /// Looks up a value in a tree with error rewriting.
317 ///
318 /// \tparam NodeType The type of the node.
319 /// \param tree The tree in which to insert the value.
320 /// \param key The key to set.
321 ///
322 /// \return A read-write reference to the value in the node.
323 ///
324 /// \throw model::error If the key is not known or if the value is not valid.
325 template< class NodeType >
326 typename NodeType::value_type&
lookup_rw(config::tree & tree,const std::string & key)327 lookup_rw(config::tree& tree, const std::string& key)
328 {
329     try {
330         return tree.lookup_rw< NodeType >(key);
331     } catch (const config::unknown_key_error& e) {
332         throw model::error(F("Unknown metadata property %s") % key);
333     } catch (const config::value_error& e) {
334         throw model::error(F("Invalid value for metadata property %s: %s") %
335                             key % e.what());
336     }
337 }
338 
339 
340 /// Sets a value in a tree with error rewriting.
341 ///
342 /// \tparam NodeType The type of the node.
343 /// \param tree The tree in which to insert the value.
344 /// \param key The key to set.
345 /// \param value The value to set the node to.
346 ///
347 /// \throw model::error If the key is not known or if the value is not valid.
348 template< class NodeType >
349 void
set(config::tree & tree,const std::string & key,const typename NodeType::value_type & value)350 set(config::tree& tree, const std::string& key,
351     const typename NodeType::value_type& value)
352 {
353     try {
354         tree.set< NodeType >(key, value);
355     } catch (const config::unknown_key_error& e) {
356         throw model::error(F("Unknown metadata property %s") % key);
357     } catch (const config::value_error& e) {
358         throw model::error(F("Invalid value for metadata property %s: %s") %
359                             key % e.what());
360     }
361 }
362 
363 
364 }  // anonymous namespace
365 
366 
367 /// Internal implementation of the metadata class.
368 struct model::metadata::impl : utils::noncopyable {
369     /// Metadata properties.
370     config::tree props;
371 
372     /// Constructor.
373     ///
374     /// \param props_ Metadata properties of the test.
implmodel::metadata::impl375     impl(const utils::config::tree& props_) :
376         props(props_)
377     {
378     }
379 
380     /// Equality comparator.
381     ///
382     /// \param other The other object to compare this one to.
383     ///
384     /// \return True if this object and other are equal; false otherwise.
385     bool
operator ==model::metadata::impl386     operator==(const impl& other) const
387     {
388         return (get_defaults().combine(props) ==
389                 get_defaults().combine(other.props));
390     }
391 };
392 
393 
394 /// Constructor.
395 ///
396 /// \param props Metadata properties of the test.
metadata(const utils::config::tree & props)397 model::metadata::metadata(const utils::config::tree& props) :
398     _pimpl(new impl(props))
399 {
400 }
401 
402 
403 /// Destructor.
~metadata(void)404 model::metadata::~metadata(void)
405 {
406 }
407 
408 
409 /// Applies a set of overrides to this metadata object.
410 ///
411 /// \param overrides The overrides to apply.  Any values explicitly set in this
412 ///     other object will override any possible values set in this object.
413 ///
414 /// \return A new metadata object with the combination.
415 model::metadata
apply_overrides(const metadata & overrides) const416 model::metadata::apply_overrides(const metadata& overrides) const
417 {
418     return metadata(_pimpl->props.combine(overrides._pimpl->props));
419 }
420 
421 
422 /// Returns the architectures allowed by the test.
423 ///
424 /// \return Set of architectures, or empty if this does not apply.
425 const model::strings_set&
allowed_architectures(void) const426 model::metadata::allowed_architectures(void) const
427 {
428     if (_pimpl->props.is_set("allowed_architectures")) {
429         return _pimpl->props.lookup< config::strings_set_node >(
430             "allowed_architectures");
431     } else {
432         return get_defaults().lookup< config::strings_set_node >(
433             "allowed_architectures");
434     }
435 }
436 
437 
438 /// Returns the platforms allowed by the test.
439 ///
440 /// \return Set of platforms, or empty if this does not apply.
441 const model::strings_set&
allowed_platforms(void) const442 model::metadata::allowed_platforms(void) const
443 {
444     if (_pimpl->props.is_set("allowed_platforms")) {
445         return _pimpl->props.lookup< config::strings_set_node >(
446             "allowed_platforms");
447     } else {
448         return get_defaults().lookup< config::strings_set_node >(
449             "allowed_platforms");
450     }
451 }
452 
453 
454 /// Returns all the user-defined metadata properties.
455 ///
456 /// \return A key/value map of properties.
457 model::properties_map
custom(void) const458 model::metadata::custom(void) const
459 {
460     return _pimpl->props.all_properties("custom", true);
461 }
462 
463 
464 /// Returns the description of the test.
465 ///
466 /// \return Textual description; may be empty.
467 const std::string&
description(void) const468 model::metadata::description(void) const
469 {
470     if (_pimpl->props.is_set("description")) {
471         return _pimpl->props.lookup< config::string_node >("description");
472     } else {
473         return get_defaults().lookup< config::string_node >("description");
474     }
475 }
476 
477 
478 /// Returns execution environment name.
479 ///
480 /// \return Name of configured execution environment.
481 const std::string&
execenv(void) const482 model::metadata::execenv(void) const
483 {
484     if (_pimpl->props.is_set("execenv")) {
485         return _pimpl->props.lookup< config::string_node >("execenv");
486     } else {
487         return get_defaults().lookup< config::string_node >("execenv");
488     }
489 }
490 
491 
492 /// Returns execenv jail(8) parameters string to run a test with.
493 ///
494 /// \return String of jail parameters.
495 const std::string&
execenv_jail_params(void) const496 model::metadata::execenv_jail_params(void) const
497 {
498     if (_pimpl->props.is_set("execenv_jail_params")) {
499         return _pimpl->props.lookup< config::string_node >(
500             "execenv_jail_params");
501     } else {
502         return get_defaults().lookup< config::string_node >(
503             "execenv_jail_params");
504     }
505 }
506 
507 
508 /// Returns whether the test has a cleanup part or not.
509 ///
510 /// \return True if there is a cleanup part; false otherwise.
511 bool
has_cleanup(void) const512 model::metadata::has_cleanup(void) const
513 {
514     if (_pimpl->props.is_set("has_cleanup")) {
515         return _pimpl->props.lookup< config::bool_node >("has_cleanup");
516     } else {
517         return get_defaults().lookup< config::bool_node >("has_cleanup");
518     }
519 }
520 
521 
522 /// Returns whether the test has a specific execenv apart from default one.
523 ///
524 /// \return True if there is a non-host execenv configured; false otherwise.
525 bool
has_execenv(void) const526 model::metadata::has_execenv(void) const
527 {
528     const std::string& name = execenv();
529     return !name.empty() && name != engine::execenv::default_execenv_name;
530 }
531 
532 
533 /// Returns whether the test is exclusive or not.
534 ///
535 /// \return True if the test has to be run on its own, not concurrently with any
536 /// other tests; false otherwise.
537 bool
is_exclusive(void) const538 model::metadata::is_exclusive(void) const
539 {
540     if (_pimpl->props.is_set("is_exclusive")) {
541         return _pimpl->props.lookup< config::bool_node >("is_exclusive");
542     } else {
543         return get_defaults().lookup< config::bool_node >("is_exclusive");
544     }
545 }
546 
547 
548 /// Returns the list of configuration variables needed by the test.
549 ///
550 /// \return Set of configuration variables.
551 const model::strings_set&
required_configs(void) const552 model::metadata::required_configs(void) const
553 {
554     if (_pimpl->props.is_set("required_configs")) {
555         return _pimpl->props.lookup< config::strings_set_node >(
556             "required_configs");
557     } else {
558         return get_defaults().lookup< config::strings_set_node >(
559             "required_configs");
560     }
561 }
562 
563 
564 /// Returns the amount of free disk space required by the test.
565 ///
566 /// \return Number of bytes, or 0 if this does not apply.
567 const units::bytes&
required_disk_space(void) const568 model::metadata::required_disk_space(void) const
569 {
570     if (_pimpl->props.is_set("required_disk_space")) {
571         return _pimpl->props.lookup< bytes_node >("required_disk_space");
572     } else {
573         return get_defaults().lookup< bytes_node >("required_disk_space");
574     }
575 }
576 
577 
578 /// Returns the list of files needed by the test.
579 ///
580 /// \return Set of paths.
581 const model::paths_set&
required_files(void) const582 model::metadata::required_files(void) const
583 {
584     if (_pimpl->props.is_set("required_files")) {
585         return _pimpl->props.lookup< paths_set_node >("required_files");
586     } else {
587         return get_defaults().lookup< paths_set_node >("required_files");
588     }
589 }
590 
591 
592 /// Returns the amount of memory required by the test.
593 ///
594 /// \return Number of bytes, or 0 if this does not apply.
595 const units::bytes&
required_memory(void) const596 model::metadata::required_memory(void) const
597 {
598     if (_pimpl->props.is_set("required_memory")) {
599         return _pimpl->props.lookup< bytes_node >("required_memory");
600     } else {
601         return get_defaults().lookup< bytes_node >("required_memory");
602     }
603 }
604 
605 
606 #ifdef __FreeBSD__
607 /// Returns the list of kmods needed by the test.
608 ///
609 /// \return Set of strings.
610 const model::strings_set&
required_kmods(void) const611 model::metadata::required_kmods(void) const
612 {
613     if (_pimpl->props.is_set("required_kmods")) {
614         return _pimpl->props.lookup< config::strings_set_node >("required_kmods");
615     } else {
616 	return get_defaults().lookup< config::strings_set_node >("required_kmods");
617     }
618 }
619 #endif
620 
621 
622 /// Returns the list of programs needed by the test.
623 ///
624 /// \return Set of paths.
625 const model::paths_set&
required_programs(void) const626 model::metadata::required_programs(void) const
627 {
628     if (_pimpl->props.is_set("required_programs")) {
629         return _pimpl->props.lookup< paths_set_node >("required_programs");
630     } else {
631         return get_defaults().lookup< paths_set_node >("required_programs");
632     }
633 }
634 
635 
636 /// Returns the user required by the test.
637 ///
638 /// \return One of unprivileged, root or empty.
639 const std::string&
required_user(void) const640 model::metadata::required_user(void) const
641 {
642     if (_pimpl->props.is_set("required_user")) {
643         return _pimpl->props.lookup< user_node >("required_user");
644     } else {
645         return get_defaults().lookup< user_node >("required_user");
646     }
647 }
648 
649 
650 /// Returns the timeout of the test.
651 ///
652 /// \return A time delta; should be compared to default_timeout to see if it has
653 /// been overriden.
654 const datetime::delta&
timeout(void) const655 model::metadata::timeout(void) const
656 {
657     if (_pimpl->props.is_set("timeout")) {
658         return _pimpl->props.lookup< delta_node >("timeout");
659     } else {
660         return get_defaults().lookup< delta_node >("timeout");
661     }
662 }
663 
664 
665 /// Externalizes the metadata to a set of key/value textual pairs.
666 ///
667 /// \return A key/value representation of the metadata.
668 model::properties_map
to_properties(void) const669 model::metadata::to_properties(void) const
670 {
671     const config::tree fully_specified = get_defaults().combine(_pimpl->props);
672     return fully_specified.all_properties();
673 }
674 
675 
676 /// Equality comparator.
677 ///
678 /// \param other The other object to compare this one to.
679 ///
680 /// \return True if this object and other are equal; false otherwise.
681 bool
operator ==(const metadata & other) const682 model::metadata::operator==(const metadata& other) const
683 {
684     return _pimpl == other._pimpl || *_pimpl == *other._pimpl;
685 }
686 
687 
688 /// Inequality comparator.
689 ///
690 /// \param other The other object to compare this one to.
691 ///
692 /// \return True if this object and other are different; false otherwise.
693 bool
operator !=(const metadata & other) const694 model::metadata::operator!=(const metadata& other) const
695 {
696     return !(*this == other);
697 }
698 
699 
700 /// Injects the object into a stream.
701 ///
702 /// \param output The stream into which to inject the object.
703 /// \param object The object to format.
704 ///
705 /// \return The output stream.
706 std::ostream&
operator <<(std::ostream & output,const metadata & object)707 model::operator<<(std::ostream& output, const metadata& object)
708 {
709     output << "metadata{";
710 
711     bool first = true;
712     const model::properties_map props = object.to_properties();
713     for (model::properties_map::const_iterator iter = props.begin();
714          iter != props.end(); ++iter) {
715         if (!first)
716             output << ", ";
717         output << F("%s=%s") % (*iter).first %
718             text::quote((*iter).second, '\'');
719         first = false;
720     }
721 
722     output << "}";
723     return output;
724 }
725 
726 
727 /// Internal implementation of the metadata_builder class.
728 struct model::metadata_builder::impl : utils::noncopyable {
729     /// Collection of requirements.
730     config::tree props;
731 
732     /// Whether we have created a metadata object or not.
733     bool built;
734 
735     /// Constructor.
implmodel::metadata_builder::impl736     impl(void) :
737         built(false)
738     {
739         init_tree(props);
740     }
741 
742     /// Constructor.
743     ///
744     /// \param base The base model to construct a copy from.
implmodel::metadata_builder::impl745     impl(const model::metadata& base) :
746         props(base._pimpl->props.deep_copy()),
747         built(false)
748     {
749     }
750 };
751 
752 
753 /// Constructor.
metadata_builder(void)754 model::metadata_builder::metadata_builder(void) :
755     _pimpl(new impl())
756 {
757 }
758 
759 
760 /// Constructor.
metadata_builder(const model::metadata & base)761 model::metadata_builder::metadata_builder(const model::metadata& base) :
762     _pimpl(new impl(base))
763 {
764 }
765 
766 
767 /// Destructor.
~metadata_builder(void)768 model::metadata_builder::~metadata_builder(void)
769 {
770 }
771 
772 
773 /// Accumulates an additional allowed architecture.
774 ///
775 /// \param arch The architecture.
776 ///
777 /// \return A reference to this builder.
778 ///
779 /// \throw model::error If the value is invalid.
780 model::metadata_builder&
add_allowed_architecture(const std::string & arch)781 model::metadata_builder::add_allowed_architecture(const std::string& arch)
782 {
783     if (!_pimpl->props.is_set("allowed_architectures")) {
784         _pimpl->props.set< config::strings_set_node >(
785             "allowed_architectures",
786             get_defaults().lookup< config::strings_set_node >(
787                 "allowed_architectures"));
788     }
789     lookup_rw< config::strings_set_node >(
790         _pimpl->props, "allowed_architectures").insert(arch);
791     return *this;
792 }
793 
794 
795 /// Accumulates an additional allowed platform.
796 ///
797 /// \param platform The platform.
798 ///
799 /// \return A reference to this builder.
800 ///
801 /// \throw model::error If the value is invalid.
802 model::metadata_builder&
add_allowed_platform(const std::string & platform)803 model::metadata_builder::add_allowed_platform(const std::string& platform)
804 {
805     if (!_pimpl->props.is_set("allowed_platforms")) {
806         _pimpl->props.set< config::strings_set_node >(
807             "allowed_platforms",
808             get_defaults().lookup< config::strings_set_node >(
809                 "allowed_platforms"));
810     }
811     lookup_rw< config::strings_set_node >(
812         _pimpl->props, "allowed_platforms").insert(platform);
813     return *this;
814 }
815 
816 
817 /// Accumulates a single user-defined property.
818 ///
819 /// \param key Name of the property to define.
820 /// \param value Value of the property.
821 ///
822 /// \return A reference to this builder.
823 ///
824 /// \throw model::error If the value is invalid.
825 model::metadata_builder&
add_custom(const std::string & key,const std::string & value)826 model::metadata_builder::add_custom(const std::string& key,
827                                      const std::string& value)
828 {
829     _pimpl->props.set_string(F("custom.%s") % key, value);
830     return *this;
831 }
832 
833 
834 /// Accumulates an additional required configuration variable.
835 ///
836 /// \param var The name of the configuration variable.
837 ///
838 /// \return A reference to this builder.
839 ///
840 /// \throw model::error If the value is invalid.
841 model::metadata_builder&
add_required_config(const std::string & var)842 model::metadata_builder::add_required_config(const std::string& var)
843 {
844     if (!_pimpl->props.is_set("required_configs")) {
845         _pimpl->props.set< config::strings_set_node >(
846             "required_configs",
847             get_defaults().lookup< config::strings_set_node >(
848                 "required_configs"));
849     }
850     lookup_rw< config::strings_set_node >(
851         _pimpl->props, "required_configs").insert(var);
852     return *this;
853 }
854 
855 
856 /// Accumulates an additional required file.
857 ///
858 /// \param path The path to the file.
859 ///
860 /// \return A reference to this builder.
861 ///
862 /// \throw model::error If the value is invalid.
863 model::metadata_builder&
add_required_file(const fs::path & path)864 model::metadata_builder::add_required_file(const fs::path& path)
865 {
866     if (!_pimpl->props.is_set("required_files")) {
867         _pimpl->props.set< paths_set_node >(
868             "required_files",
869             get_defaults().lookup< paths_set_node >("required_files"));
870     }
871     lookup_rw< paths_set_node >(_pimpl->props, "required_files").insert(path);
872     return *this;
873 }
874 
875 
876 /// Accumulates an additional required program.
877 ///
878 /// \param path The path to the program.
879 ///
880 /// \return A reference to this builder.
881 ///
882 /// \throw model::error If the value is invalid.
883 model::metadata_builder&
add_required_program(const fs::path & path)884 model::metadata_builder::add_required_program(const fs::path& path)
885 {
886     if (!_pimpl->props.is_set("required_programs")) {
887         _pimpl->props.set< paths_set_node >(
888             "required_programs",
889             get_defaults().lookup< paths_set_node >("required_programs"));
890     }
891     lookup_rw< paths_set_node >(_pimpl->props,
892                                 "required_programs").insert(path);
893     return *this;
894 }
895 
896 
897 /// Sets the architectures allowed by the test.
898 ///
899 /// \param as Set of architectures.
900 ///
901 /// \return A reference to this builder.
902 ///
903 /// \throw model::error If the value is invalid.
904 model::metadata_builder&
set_allowed_architectures(const model::strings_set & as)905 model::metadata_builder::set_allowed_architectures(
906     const model::strings_set& as)
907 {
908     set< config::strings_set_node >(_pimpl->props, "allowed_architectures", as);
909     return *this;
910 }
911 
912 
913 /// Sets the platforms allowed by the test.
914 ///
915 /// \return ps Set of platforms.
916 ///
917 /// \return A reference to this builder.
918 ///
919 /// \throw model::error If the value is invalid.
920 model::metadata_builder&
set_allowed_platforms(const model::strings_set & ps)921 model::metadata_builder::set_allowed_platforms(const model::strings_set& ps)
922 {
923     set< config::strings_set_node >(_pimpl->props, "allowed_platforms", ps);
924     return *this;
925 }
926 
927 
928 /// Sets the user-defined properties.
929 ///
930 /// \param props The custom properties to set.
931 ///
932 /// \return A reference to this builder.
933 ///
934 /// \throw model::error If the value is invalid.
935 model::metadata_builder&
set_custom(const model::properties_map & props)936 model::metadata_builder::set_custom(const model::properties_map& props)
937 {
938     for (model::properties_map::const_iterator iter = props.begin();
939          iter != props.end(); ++iter)
940         _pimpl->props.set_string(F("custom.%s") % (*iter).first,
941                                  (*iter).second);
942     return *this;
943 }
944 
945 
946 /// Sets the description of the test.
947 ///
948 /// \param description Textual description of the test.
949 ///
950 /// \return A reference to this builder.
951 ///
952 /// \throw model::error If the value is invalid.
953 model::metadata_builder&
set_description(const std::string & description)954 model::metadata_builder::set_description(const std::string& description)
955 {
956     set< config::string_node >(_pimpl->props, "description", description);
957     return *this;
958 }
959 
960 
961 /// Sets execution environment name.
962 ///
963 /// \param name Execution environment name.
964 ///
965 /// \return A reference to this builder.
966 ///
967 /// \throw model::error If the value is invalid.
968 model::metadata_builder&
set_execenv(const std::string & name)969 model::metadata_builder::set_execenv(const std::string& name)
970 {
971     set< config::string_node >(_pimpl->props, "execenv", name);
972     return *this;
973 }
974 
975 
976 /// Sets execenv jail(8) parameters string to run the test with.
977 ///
978 /// \param params String of jail parameters.
979 ///
980 /// \return A reference to this builder.
981 ///
982 /// \throw model::error If the value is invalid.
983 model::metadata_builder&
set_execenv_jail_params(const std::string & params)984 model::metadata_builder::set_execenv_jail_params(const std::string& params)
985 {
986     set< config::string_node >(_pimpl->props, "execenv_jail_params", params);
987     return *this;
988 }
989 
990 
991 /// Sets whether the test has a cleanup part or not.
992 ///
993 /// \param cleanup True if the test has a cleanup part; false otherwise.
994 ///
995 /// \return A reference to this builder.
996 ///
997 /// \throw model::error If the value is invalid.
998 model::metadata_builder&
set_has_cleanup(const bool cleanup)999 model::metadata_builder::set_has_cleanup(const bool cleanup)
1000 {
1001     set< config::bool_node >(_pimpl->props, "has_cleanup", cleanup);
1002     return *this;
1003 }
1004 
1005 
1006 /// Sets whether the test is exclusive or not.
1007 ///
1008 /// \param exclusive True if the test is exclusive; false otherwise.
1009 ///
1010 /// \return A reference to this builder.
1011 ///
1012 /// \throw model::error If the value is invalid.
1013 model::metadata_builder&
set_is_exclusive(const bool exclusive)1014 model::metadata_builder::set_is_exclusive(const bool exclusive)
1015 {
1016     set< config::bool_node >(_pimpl->props, "is_exclusive", exclusive);
1017     return *this;
1018 }
1019 
1020 
1021 /// Sets the list of configuration variables needed by the test.
1022 ///
1023 /// \param vars Set of configuration variables.
1024 ///
1025 /// \return A reference to this builder.
1026 ///
1027 /// \throw model::error If the value is invalid.
1028 model::metadata_builder&
set_required_configs(const model::strings_set & vars)1029 model::metadata_builder::set_required_configs(const model::strings_set& vars)
1030 {
1031     set< config::strings_set_node >(_pimpl->props, "required_configs", vars);
1032     return *this;
1033 }
1034 
1035 
1036 /// Sets the amount of free disk space required by the test.
1037 ///
1038 /// \param bytes Number of bytes.
1039 ///
1040 /// \return A reference to this builder.
1041 ///
1042 /// \throw model::error If the value is invalid.
1043 model::metadata_builder&
set_required_disk_space(const units::bytes & bytes)1044 model::metadata_builder::set_required_disk_space(const units::bytes& bytes)
1045 {
1046     set< bytes_node >(_pimpl->props, "required_disk_space", bytes);
1047     return *this;
1048 }
1049 
1050 
1051 /// Sets the list of files needed by the test.
1052 ///
1053 /// \param files Set of paths.
1054 ///
1055 /// \return A reference to this builder.
1056 ///
1057 /// \throw model::error If the value is invalid.
1058 model::metadata_builder&
set_required_files(const model::paths_set & files)1059 model::metadata_builder::set_required_files(const model::paths_set& files)
1060 {
1061     set< paths_set_node >(_pimpl->props, "required_files", files);
1062     return *this;
1063 }
1064 
1065 
1066 /// Sets the amount of memory required by the test.
1067 ///
1068 /// \param bytes Number of bytes.
1069 ///
1070 /// \return A reference to this builder.
1071 ///
1072 /// \throw model::error If the value is invalid.
1073 model::metadata_builder&
set_required_memory(const units::bytes & bytes)1074 model::metadata_builder::set_required_memory(const units::bytes& bytes)
1075 {
1076     set< bytes_node >(_pimpl->props, "required_memory", bytes);
1077     return *this;
1078 }
1079 
1080 
1081 /// Sets the list of programs needed by the test.
1082 ///
1083 /// \param progs Set of paths.
1084 ///
1085 /// \return A reference to this builder.
1086 ///
1087 /// \throw model::error If the value is invalid.
1088 model::metadata_builder&
set_required_programs(const model::paths_set & progs)1089 model::metadata_builder::set_required_programs(const model::paths_set& progs)
1090 {
1091     set< paths_set_node >(_pimpl->props, "required_programs", progs);
1092     return *this;
1093 }
1094 
1095 
1096 /// Sets the user required by the test.
1097 ///
1098 /// \param user One of unprivileged, root or empty.
1099 ///
1100 /// \return A reference to this builder.
1101 ///
1102 /// \throw model::error If the value is invalid.
1103 model::metadata_builder&
set_required_user(const std::string & user)1104 model::metadata_builder::set_required_user(const std::string& user)
1105 {
1106     set< user_node >(_pimpl->props, "required_user", user);
1107     return *this;
1108 }
1109 
1110 
1111 /// Sets a metadata property by name from its textual representation.
1112 ///
1113 /// \param key The property to set.
1114 /// \param value The value to set the property to.
1115 ///
1116 /// \return A reference to this builder.
1117 ///
1118 /// \throw model::error If the value is invalid or the key does not exist.
1119 model::metadata_builder&
set_string(const std::string & key,const std::string & value)1120 model::metadata_builder::set_string(const std::string& key,
1121                                     const std::string& value)
1122 {
1123     try {
1124         _pimpl->props.set_string(key, value);
1125     } catch (const config::unknown_key_error& e) {
1126         throw model::format_error(F("Unknown metadata property %s") % key);
1127     } catch (const config::value_error& e) {
1128         throw model::format_error(
1129             F("Invalid value for metadata property %s: %s") % key % e.what());
1130     }
1131     return *this;
1132 }
1133 
1134 
1135 /// Sets the timeout of the test.
1136 ///
1137 /// \param timeout The timeout to set.
1138 ///
1139 /// \return A reference to this builder.
1140 ///
1141 /// \throw model::error If the value is invalid.
1142 model::metadata_builder&
set_timeout(const datetime::delta & timeout)1143 model::metadata_builder::set_timeout(const datetime::delta& timeout)
1144 {
1145     set< delta_node >(_pimpl->props, "timeout", timeout);
1146     return *this;
1147 }
1148 
1149 
1150 /// Creates a new metadata object.
1151 ///
1152 /// \pre This has not yet been called.  We only support calling this function
1153 /// once due to the way the internal tree works: we pass around references, not
1154 /// deep copies, so if we allowed a second build, we'd encourage reusing the
1155 /// same builder to construct different metadata objects, and this could have
1156 /// unintended consequences.
1157 ///
1158 /// \return The constructed metadata object.
1159 model::metadata
build(void) const1160 model::metadata_builder::build(void) const
1161 {
1162     PRE(!_pimpl->built);
1163     _pimpl->built = true;
1164 
1165     return metadata(_pimpl->props);
1166 }
1167