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