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