1// Copyright 2014 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 "engine/scheduler.hpp"
30
31extern "C" {
32#include <sys/types.h>
33
34#include <signal.h>
35#include <unistd.h>
36}
37
38#include <cstdlib>
39#include <fstream>
40#include <iostream>
41#include <string>
42
43#include <atf-c++.hpp>
44
45#include "engine/config.hpp"
46#include "engine/exceptions.hpp"
47#include "model/context.hpp"
48#include "model/metadata.hpp"
49#include "model/test_case.hpp"
50#include "model/test_program.hpp"
51#include "model/test_result.hpp"
52#include "utils/config/tree.ipp"
53#include "utils/datetime.hpp"
54#include "utils/defs.hpp"
55#include "utils/env.hpp"
56#include "utils/format/containers.ipp"
57#include "utils/format/macros.hpp"
58#include "utils/fs/operations.hpp"
59#include "utils/fs/path.hpp"
60#include "utils/optional.ipp"
61#include "utils/passwd.hpp"
62#include "utils/process/status.hpp"
63#include "utils/sanity.hpp"
64#include "utils/stacktrace.hpp"
65#include "utils/stream.hpp"
66#include "utils/test_utils.ipp"
67#include "utils/text/exceptions.hpp"
68#include "utils/text/operations.ipp"
69
70namespace config = utils::config;
71namespace datetime = utils::datetime;
72namespace fs = utils::fs;
73namespace passwd = utils::passwd;
74namespace process = utils::process;
75namespace scheduler = engine::scheduler;
76namespace text = utils::text;
77
78using utils::none;
79using utils::optional;
80
81
82namespace {
83
84
85/// Checks if a string starts with a prefix.
86///
87/// \param str The string to be tested.
88/// \param prefix The prefix to look for.
89///
90/// \return True if the string is prefixed as specified.
91static bool
92starts_with(const std::string& str, const std::string& prefix)
93{
94    return (str.length() >= prefix.length() &&
95            str.substr(0, prefix.length()) == prefix);
96}
97
98
99/// Strips a prefix from a string and converts the rest to an integer.
100///
101/// \param str The string to be tested.
102/// \param prefix The prefix to strip from the string.
103///
104/// \return The part of the string after the prefix converted to an integer.
105static int
106suffix_to_int(const std::string& str, const std::string& prefix)
107{
108    PRE(starts_with(str, prefix));
109    try {
110        return text::to_type< int >(str.substr(prefix.length()));
111    } catch (const text::value_error& error) {
112        std::cerr << F("Failed: %s\n") % error.what();
113        std::abort();
114    }
115}
116
117
118/// Mock interface definition for testing.
119///
120/// This scheduler interface does not execute external binaries.  It is designed
121/// to simulate the scheduler of various programs with different exit statuses.
122class mock_interface : public scheduler::interface {
123    /// Executes the subprocess simulating an exec.
124    ///
125    /// This is just a simple wrapper over _exit(2) because we cannot use
126    /// std::exit on exit from this mock interface.  The reason is that we do
127    /// not want to invoke any destructors as otherwise we'd clear up the global
128    /// scheduler state by mistake.  This wouldn't be a major problem if it
129    /// wasn't because doing so deletes on-disk files and we want to leave them
130    /// in place so that the parent process can test for them!
131    ///
132    /// \param exit_code Exit code.
133    void
134    do_exit(const int exit_code) const UTILS_NORETURN
135    {
136        std::cout.flush();
137        std::cerr.flush();
138        ::_exit(exit_code);
139    }
140
141    /// Executes a test case that creates various files and then fails.
142    void
143    exec_create_files_and_fail(void) const UTILS_NORETURN
144    {
145        std::cerr << "This should not be clobbered\n";
146        atf::utils::create_file("first file", "");
147        atf::utils::create_file("second-file", "");
148        fs::mkdir_p(fs::path("dir1/dir2"), 0755);
149        ::kill(::getpid(), SIGTERM);
150        std::abort();
151    }
152
153    /// Executes a test case that deletes all files in the current directory.
154    ///
155    /// This is intended to validate that the test runs in an empty directory,
156    /// separate from any control files that the scheduler may have created.
157    void
158    exec_delete_all(void) const UTILS_NORETURN
159    {
160        const int exit_code = ::system("rm *") == -1
161            ? EXIT_FAILURE : EXIT_SUCCESS;
162
163        // Recreate our own cookie.
164        atf::utils::create_file("exec_test_was_called", "");
165
166        do_exit(exit_code);
167    }
168
169    /// Executes a test case that returns a specific exit code.
170    ///
171    /// \param exit_code Exit status to terminate the program with.
172    void
173    exec_exit(const int exit_code) const UTILS_NORETURN
174    {
175        do_exit(exit_code);
176    }
177
178    /// Executes a test case that just fails.
179    void
180    exec_fail(void) const UTILS_NORETURN
181    {
182        std::cerr << "This should not be clobbered\n";
183        ::kill(::getpid(), SIGTERM);
184        std::abort();
185    }
186
187    /// Executes a test case that prints all input parameters to the functor.
188    ///
189    /// \param test_program The test program to execute.
190    /// \param test_case_name Name of the test case to invoke, which must be a
191    ///     number.
192    /// \param vars User-provided variables to pass to the test program.
193    void
194    exec_print_params(const model::test_program& test_program,
195                      const std::string& test_case_name,
196                      const config::properties_map& vars) const
197        UTILS_NORETURN
198    {
199        std::cout << F("Test program: %s\n") % test_program.relative_path();
200        std::cout << F("Test case: %s\n") % test_case_name;
201        for (config::properties_map::const_iterator iter = vars.begin();
202             iter != vars.end(); ++iter) {
203            std::cout << F("%s=%s\n") % (*iter).first % (*iter).second;
204        }
205
206        std::cerr << F("stderr: %s\n") % test_case_name;
207
208        do_exit(EXIT_SUCCESS);
209    }
210
211public:
212    /// Executes a test program's list operation.
213    ///
214    /// This method is intended to be called within a subprocess and is expected
215    /// to terminate execution either by exec(2)ing the test program or by
216    /// exiting with a failure.
217    ///
218    /// \param test_program The test program to execute.
219    /// \param vars User-provided variables to pass to the test program.
220    void
221    exec_list(const model::test_program& test_program,
222              const config::properties_map& vars)
223        const UTILS_NORETURN
224    {
225        const std::string name = test_program.absolute_path().leaf_name();
226
227        std::cerr << name;
228        std::cerr.flush();
229        if (name == "check_i_exist") {
230            if (fs::exists(test_program.absolute_path())) {
231                std::cout << "found\n";
232                do_exit(EXIT_SUCCESS);
233            } else {
234                std::cout << "not_found\n";
235                do_exit(EXIT_FAILURE);
236            }
237        } else if (name == "empty") {
238            do_exit(EXIT_SUCCESS);
239        } else if (name == "misbehave") {
240            utils::abort_without_coredump();
241        } else if (name == "timeout") {
242            std::cout << "sleeping\n";
243            std::cout.flush();
244            ::sleep(100);
245            utils::abort_without_coredump();
246        } else if (name == "vars") {
247            for (config::properties_map::const_iterator iter = vars.begin();
248                 iter != vars.end(); ++iter) {
249                std::cout << F("%s_%s\n") % (*iter).first % (*iter).second;
250            }
251            do_exit(15);
252        } else {
253            std::abort();
254        }
255    }
256
257    /// Computes the test cases list of a test program.
258    ///
259    /// \param status The termination status of the subprocess used to execute
260    ///     the exec_test() method or none if the test timed out.
261    /// \param stdout_path Path to the file containing the stdout of the test.
262    /// \param stderr_path Path to the file containing the stderr of the test.
263    ///
264    /// \return A list of test cases.
265    model::test_cases_map
266    parse_list(const optional< process::status >& status,
267               const fs::path& stdout_path,
268               const fs::path& stderr_path) const
269    {
270        const std::string name = utils::read_file(stderr_path);
271        if (name == "check_i_exist") {
272            ATF_REQUIRE(status.get().exited());
273            ATF_REQUIRE_EQ(EXIT_SUCCESS, status.get().exitstatus());
274        } else if (name == "empty") {
275            ATF_REQUIRE(status.get().exited());
276            ATF_REQUIRE_EQ(EXIT_SUCCESS, status.get().exitstatus());
277        } else if (name == "misbehave") {
278            throw std::runtime_error("misbehaved in parse_list");
279        } else if (name == "timeout") {
280            ATF_REQUIRE(!status);
281        } else if (name == "vars") {
282            ATF_REQUIRE(status.get().exited());
283            ATF_REQUIRE_EQ(15, status.get().exitstatus());
284        } else {
285            ATF_FAIL("Invalid stderr contents; got " + name);
286        }
287
288        model::test_cases_map_builder test_cases_builder;
289
290        std::ifstream input(stdout_path.c_str());
291        ATF_REQUIRE(input);
292        std::string line;
293        while (std::getline(input, line).good()) {
294            test_cases_builder.add(line);
295        }
296
297        return test_cases_builder.build();
298    }
299
300    /// Executes a test case of the test program.
301    ///
302    /// This method is intended to be called within a subprocess and is expected
303    /// to terminate execution either by exec(2)ing the test program or by
304    /// exiting with a failure.
305    ///
306    /// \param test_program The test program to execute.
307    /// \param test_case_name Name of the test case to invoke.
308    /// \param vars User-provided variables to pass to the test program.
309    /// \param control_directory Directory where the interface may place control
310    ///     files.
311    void
312    exec_test(const model::test_program& test_program,
313              const std::string& test_case_name,
314              const config::properties_map& vars,
315              const fs::path& control_directory) const
316    {
317        const fs::path cookie = control_directory / "exec_test_was_called";
318        std::ofstream control_file(cookie.c_str());
319        if (!control_file) {
320            std::cerr << "Failed to create " << cookie << '\n';
321            std::abort();
322        }
323        control_file << test_case_name;
324        control_file.close();
325
326        if (test_case_name == "check_i_exist") {
327            do_exit(fs::exists(test_program.absolute_path()) ? 0 : 1);
328        } else if (starts_with(test_case_name, "cleanup_timeout")) {
329            exec_exit(EXIT_SUCCESS);
330        } else if (starts_with(test_case_name, "create_files_and_fail")) {
331            exec_create_files_and_fail();
332        } else if (test_case_name == "delete_all") {
333            exec_delete_all();
334        } else if (starts_with(test_case_name, "exit ")) {
335            exec_exit(suffix_to_int(test_case_name, "exit "));
336        } else if (starts_with(test_case_name, "fail")) {
337            exec_fail();
338        } else if (starts_with(test_case_name, "fail_body_fail_cleanup")) {
339            exec_fail();
340        } else if (starts_with(test_case_name, "fail_body_pass_cleanup")) {
341            exec_fail();
342        } else if (starts_with(test_case_name, "pass_body_fail_cleanup")) {
343            exec_exit(EXIT_SUCCESS);
344        } else if (starts_with(test_case_name, "print_params")) {
345            exec_print_params(test_program, test_case_name, vars);
346        } else if (starts_with(test_case_name, "skip_body_pass_cleanup")) {
347            exec_exit(EXIT_SUCCESS);
348        } else {
349            std::cerr << "Unknown test case " << test_case_name << '\n';
350            std::abort();
351        }
352    }
353
354    /// Executes a test cleanup routine of the test program.
355    ///
356    /// This method is intended to be called within a subprocess and is expected
357    /// to terminate execution either by exec(2)ing the test program or by
358    /// exiting with a failure.
359    ///
360    /// \param test_case_name Name of the test case to invoke.
361    void
362    exec_cleanup(const model::test_program& /* test_program */,
363                 const std::string& test_case_name,
364                 const config::properties_map& /* vars */,
365                 const fs::path& /* control_directory */) const
366    {
367        std::cout << "exec_cleanup was called\n";
368        std::cout.flush();
369
370        if (starts_with(test_case_name, "cleanup_timeout")) {
371            ::sleep(100);
372            std::abort();
373        } else if (starts_with(test_case_name, "fail_body_fail_cleanup")) {
374            exec_fail();
375        } else if (starts_with(test_case_name, "fail_body_pass_cleanup")) {
376            exec_exit(EXIT_SUCCESS);
377        } else if (starts_with(test_case_name, "pass_body_fail_cleanup")) {
378            exec_fail();
379        } else if (starts_with(test_case_name, "skip_body_pass_cleanup")) {
380            exec_exit(EXIT_SUCCESS);
381        } else {
382            std::cerr << "Should not have been called for a test without "
383                "a cleanup routine" << '\n';
384            std::abort();
385        }
386    }
387
388    /// Computes the result of a test case based on its termination status.
389    ///
390    /// \param status The termination status of the subprocess used to execute
391    ///     the exec_test() method or none if the test timed out.
392    /// \param control_directory Path to the directory where the interface may
393    ///     have placed control files.
394    /// \param stdout_path Path to the file containing the stdout of the test.
395    /// \param stderr_path Path to the file containing the stderr of the test.
396    ///
397    /// \return A test result.
398    model::test_result
399    compute_result(const optional< process::status >& status,
400                   const fs::path& control_directory,
401                   const fs::path& stdout_path,
402                   const fs::path& stderr_path) const
403    {
404        // Do not use any ATF_* macros here.  Some of the tests below invoke
405        // this code in a subprocess, and terminating such subprocess due to a
406        // failed ATF_* macro yields mysterious failures that are incredibly
407        // hard to debug.  (Case in point: the signal_handling test is racy by
408        // nature, and the test run by exec_test() above may not have created
409        // the cookie we expect below.  We don't want to "silently" exit if the
410        // file is not there.)
411
412        if (!status) {
413            return model::test_result(model::test_result_broken,
414                                      "Timed out");
415        }
416
417        if (status.get().exited()) {
418            // Only sanity-check the work directory-related parameters in case
419            // of a clean exit.  In all other cases, there is no guarantee that
420            // these were ever created.
421            const fs::path cookie = control_directory / "exec_test_was_called";
422            if (!atf::utils::file_exists(cookie.str())) {
423                return model::test_result(
424                    model::test_result_broken,
425                    "compute_result's control_directory does not seem to point "
426                    "to the right location");
427            }
428            const std::string test_case_name = utils::read_file(cookie);
429
430            if (!atf::utils::file_exists(stdout_path.str())) {
431                return model::test_result(
432                    model::test_result_broken,
433                    "compute_result's stdout_path does not exist");
434            }
435            if (!atf::utils::file_exists(stderr_path.str())) {
436                return model::test_result(
437                    model::test_result_broken,
438                    "compute_result's stderr_path does not exist");
439            }
440
441            if (test_case_name == "skip_body_pass_cleanup") {
442                return model::test_result(
443                    model::test_result_skipped,
444                    F("Exit %s") % status.get().exitstatus());
445            } else {
446                return model::test_result(
447                    model::test_result_passed,
448                    F("Exit %s") % status.get().exitstatus());
449            }
450        } else {
451            return model::test_result(
452                model::test_result_failed,
453                F("Signal %s") % status.get().termsig());
454        }
455    }
456};
457
458
459}  // anonymous namespace
460
461
462/// Runs list_tests on the scheduler and returns the results.
463///
464/// \param test_name The name of the test supported by our exec_list function.
465/// \param user_config Optional user settings for the test.
466///
467/// \return The loaded list of test cases.
468static model::test_cases_map
469check_integration_list(const char* test_name, const fs::path root,
470                       const config::tree& user_config = engine::empty_config())
471{
472    const model::test_program program = model::test_program_builder(
473        "mock", fs::path(test_name), root, "the-suite")
474        .build();
475
476    scheduler::scheduler_handle handle = scheduler::setup();
477    const model::test_cases_map test_cases = handle.list_tests(&program,
478                                                               user_config);
479    handle.cleanup();
480
481    return test_cases;
482}
483
484
485ATF_TEST_CASE_WITHOUT_HEAD(integration__list_some);
486ATF_TEST_CASE_BODY(integration__list_some)
487{
488    config::tree user_config = engine::empty_config();
489    user_config.set_string("test_suites.the-suite.first", "test");
490    user_config.set_string("test_suites.the-suite.second", "TEST");
491    user_config.set_string("test_suites.abc.unused", "unused");
492
493    const model::test_cases_map test_cases = check_integration_list(
494        "vars", fs::path("."), user_config);
495
496    const model::test_cases_map exp_test_cases = model::test_cases_map_builder()
497        .add("first_test").add("second_TEST").build();
498    ATF_REQUIRE_EQ(exp_test_cases, test_cases);
499}
500
501
502ATF_TEST_CASE_WITHOUT_HEAD(integration__list_check_paths);
503ATF_TEST_CASE_BODY(integration__list_check_paths)
504{
505    fs::mkdir_p(fs::path("dir1/dir2/dir3"), 0755);
506    atf::utils::create_file("dir1/dir2/dir3/check_i_exist", "");
507
508    const model::test_cases_map test_cases = check_integration_list(
509        "dir2/dir3/check_i_exist", fs::path("dir1"));
510
511    const model::test_cases_map exp_test_cases = model::test_cases_map_builder()
512        .add("found").build();
513    ATF_REQUIRE_EQ(exp_test_cases, test_cases);
514}
515
516
517ATF_TEST_CASE_WITHOUT_HEAD(integration__list_timeout);
518ATF_TEST_CASE_BODY(integration__list_timeout)
519{
520    scheduler::list_timeout = datetime::delta(1, 0);
521    const model::test_cases_map test_cases = check_integration_list(
522        "timeout", fs::path("."));
523
524    const model::test_cases_map exp_test_cases = model::test_cases_map_builder()
525        .add("sleeping").build();
526    ATF_REQUIRE_EQ(exp_test_cases, test_cases);
527}
528
529
530ATF_TEST_CASE_WITHOUT_HEAD(integration__list_fail);
531ATF_TEST_CASE_BODY(integration__list_fail)
532{
533    const model::test_cases_map test_cases = check_integration_list(
534        "misbehave", fs::path("."));
535
536    ATF_REQUIRE_EQ(1, test_cases.size());
537    const model::test_case& test_case = test_cases.begin()->second;
538    ATF_REQUIRE_EQ("__test_cases_list__", test_case.name());
539    ATF_REQUIRE(test_case.fake_result());
540    ATF_REQUIRE_EQ(model::test_result(model::test_result_broken,
541                                      "misbehaved in parse_list"),
542                   test_case.fake_result().get());
543}
544
545
546ATF_TEST_CASE_WITHOUT_HEAD(integration__list_empty);
547ATF_TEST_CASE_BODY(integration__list_empty)
548{
549    const model::test_cases_map test_cases = check_integration_list(
550        "empty", fs::path("."));
551
552    ATF_REQUIRE_EQ(1, test_cases.size());
553    const model::test_case& test_case = test_cases.begin()->second;
554    ATF_REQUIRE_EQ("__test_cases_list__", test_case.name());
555    ATF_REQUIRE(test_case.fake_result());
556    ATF_REQUIRE_EQ(model::test_result(model::test_result_broken,
557                                      "Empty test cases list"),
558                   test_case.fake_result().get());
559}
560
561
562ATF_TEST_CASE_WITHOUT_HEAD(integration__run_one);
563ATF_TEST_CASE_BODY(integration__run_one)
564{
565    const model::test_program_ptr program = model::test_program_builder(
566        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
567        .add_test_case("exit 41").build_ptr();
568
569    const config::tree user_config = engine::empty_config();
570
571    scheduler::scheduler_handle handle = scheduler::setup();
572
573    const scheduler::exec_handle exec_handle = handle.spawn_test(
574        program, "exit 41", user_config);
575
576    scheduler::result_handle_ptr result_handle = handle.wait_any();
577    const scheduler::test_result_handle* test_result_handle =
578        dynamic_cast< const scheduler::test_result_handle* >(
579            result_handle.get());
580    ATF_REQUIRE_EQ(exec_handle, result_handle->original_pid());
581    ATF_REQUIRE_EQ(model::test_result(model::test_result_passed, "Exit 41"),
582                   test_result_handle->test_result());
583    result_handle->cleanup();
584    result_handle.reset();
585
586    handle.cleanup();
587}
588
589
590ATF_TEST_CASE_WITHOUT_HEAD(integration__run_many);
591ATF_TEST_CASE_BODY(integration__run_many)
592{
593    static const std::size_t num_test_programs = 30;
594
595    const config::tree user_config = engine::empty_config();
596
597    scheduler::scheduler_handle handle = scheduler::setup();
598
599    // We mess around with the "current time" below, so make sure the tests do
600    // not spuriously exceed their deadline by bumping it to a large number.
601    const model::metadata infinite_timeout = model::metadata_builder()
602        .set_timeout(datetime::delta(1000000L, 0)).build();
603
604    std::size_t total_tests = 0;
605    std::map< scheduler::exec_handle, model::test_program_ptr >
606        exp_test_programs;
607    std::map< scheduler::exec_handle, std::string > exp_test_case_names;
608    std::map< scheduler::exec_handle, datetime::timestamp > exp_start_times;
609    std::map< scheduler::exec_handle, int > exp_exit_statuses;
610    for (std::size_t i = 0; i < num_test_programs; ++i) {
611        const std::string test_case_0 = F("exit %s") % (i * 3 + 0);
612        const std::string test_case_1 = F("exit %s") % (i * 3 + 1);
613        const std::string test_case_2 = F("exit %s") % (i * 3 + 2);
614
615        const model::test_program_ptr program = model::test_program_builder(
616            "mock", fs::path(F("program-%s") % i),
617            fs::current_path(), "the-suite")
618            .set_metadata(infinite_timeout)
619            .add_test_case(test_case_0)
620            .add_test_case(test_case_1)
621            .add_test_case(test_case_2)
622            .build_ptr();
623
624        const datetime::timestamp start_time = datetime::timestamp::from_values(
625            2014, 12, 8, 9, 40, 0, i);
626
627        scheduler::exec_handle exec_handle;
628
629        datetime::set_mock_now(start_time);
630        exec_handle = handle.spawn_test(program, test_case_0, user_config);
631        exp_test_programs.insert(std::make_pair(exec_handle, program));
632        exp_test_case_names.insert(std::make_pair(exec_handle, test_case_0));
633        exp_start_times.insert(std::make_pair(exec_handle, start_time));
634        exp_exit_statuses.insert(std::make_pair(exec_handle, i * 3));
635        ++total_tests;
636
637        datetime::set_mock_now(start_time);
638        exec_handle = handle.spawn_test(program, test_case_1, user_config);
639        exp_test_programs.insert(std::make_pair(exec_handle, program));
640        exp_test_case_names.insert(std::make_pair(exec_handle, test_case_1));
641        exp_start_times.insert(std::make_pair(exec_handle, start_time));
642        exp_exit_statuses.insert(std::make_pair(exec_handle, i * 3 + 1));
643        ++total_tests;
644
645        datetime::set_mock_now(start_time);
646        exec_handle = handle.spawn_test(program, test_case_2, user_config);
647        exp_test_programs.insert(std::make_pair(exec_handle, program));
648        exp_test_case_names.insert(std::make_pair(exec_handle, test_case_2));
649        exp_start_times.insert(std::make_pair(exec_handle, start_time));
650        exp_exit_statuses.insert(std::make_pair(exec_handle, i * 3 + 2));
651        ++total_tests;
652    }
653
654    for (std::size_t i = 0; i < total_tests; ++i) {
655        const datetime::timestamp end_time = datetime::timestamp::from_values(
656            2014, 12, 8, 9, 50, 10, i);
657        datetime::set_mock_now(end_time);
658        scheduler::result_handle_ptr result_handle = handle.wait_any();
659        const scheduler::test_result_handle* test_result_handle =
660            dynamic_cast< const scheduler::test_result_handle* >(
661                result_handle.get());
662
663        const scheduler::exec_handle exec_handle =
664            result_handle->original_pid();
665
666        const model::test_program_ptr test_program = exp_test_programs.find(
667            exec_handle)->second;
668        const std::string& test_case_name = exp_test_case_names.find(
669            exec_handle)->second;
670        const datetime::timestamp& start_time = exp_start_times.find(
671            exec_handle)->second;
672        const int exit_status = exp_exit_statuses.find(exec_handle)->second;
673
674        ATF_REQUIRE_EQ(model::test_result(model::test_result_passed,
675                                          F("Exit %s") % exit_status),
676                       test_result_handle->test_result());
677
678        ATF_REQUIRE_EQ(test_program, test_result_handle->test_program());
679        ATF_REQUIRE_EQ(test_case_name, test_result_handle->test_case_name());
680
681        ATF_REQUIRE_EQ(start_time, result_handle->start_time());
682        ATF_REQUIRE_EQ(end_time, result_handle->end_time());
683
684        result_handle->cleanup();
685
686        ATF_REQUIRE(!atf::utils::file_exists(
687                        result_handle->stdout_file().str()));
688        ATF_REQUIRE(!atf::utils::file_exists(
689                        result_handle->stderr_file().str()));
690        ATF_REQUIRE(!atf::utils::file_exists(
691                        result_handle->work_directory().str()));
692
693        result_handle.reset();
694    }
695
696    handle.cleanup();
697}
698
699
700ATF_TEST_CASE_WITHOUT_HEAD(integration__run_check_paths);
701ATF_TEST_CASE_BODY(integration__run_check_paths)
702{
703    fs::mkdir_p(fs::path("dir1/dir2/dir3"), 0755);
704    atf::utils::create_file("dir1/dir2/dir3/program", "");
705
706    const model::test_program_ptr program = model::test_program_builder(
707        "mock", fs::path("dir2/dir3/program"), fs::path("dir1"), "the-suite")
708        .add_test_case("check_i_exist").build_ptr();
709
710    scheduler::scheduler_handle handle = scheduler::setup();
711
712    (void)handle.spawn_test(program, "check_i_exist", engine::default_config());
713    scheduler::result_handle_ptr result_handle = handle.wait_any();
714    const scheduler::test_result_handle* test_result_handle =
715        dynamic_cast< const scheduler::test_result_handle* >(
716            result_handle.get());
717
718    ATF_REQUIRE_EQ(model::test_result(model::test_result_passed, "Exit 0"),
719                   test_result_handle->test_result());
720
721    result_handle->cleanup();
722    result_handle.reset();
723
724    handle.cleanup();
725}
726
727
728ATF_TEST_CASE_WITHOUT_HEAD(integration__parameters_and_output);
729ATF_TEST_CASE_BODY(integration__parameters_and_output)
730{
731    const model::test_program_ptr program = model::test_program_builder(
732        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
733        .add_test_case("print_params").build_ptr();
734
735    config::tree user_config = engine::empty_config();
736    user_config.set_string("test_suites.the-suite.one", "first variable");
737    user_config.set_string("test_suites.the-suite.two", "second variable");
738
739    scheduler::scheduler_handle handle = scheduler::setup();
740
741    const scheduler::exec_handle exec_handle = handle.spawn_test(
742        program, "print_params", user_config);
743
744    scheduler::result_handle_ptr result_handle = handle.wait_any();
745    const scheduler::test_result_handle* test_result_handle =
746        dynamic_cast< const scheduler::test_result_handle* >(
747            result_handle.get());
748
749    ATF_REQUIRE_EQ(exec_handle, result_handle->original_pid());
750    ATF_REQUIRE_EQ(program, test_result_handle->test_program());
751    ATF_REQUIRE_EQ("print_params", test_result_handle->test_case_name());
752    ATF_REQUIRE_EQ(model::test_result(model::test_result_passed, "Exit 0"),
753                   test_result_handle->test_result());
754
755    const fs::path stdout_file = result_handle->stdout_file();
756    ATF_REQUIRE(atf::utils::compare_file(
757        stdout_file.str(),
758        "Test program: the-program\n"
759        "Test case: print_params\n"
760        "one=first variable\n"
761        "two=second variable\n"));
762    const fs::path stderr_file = result_handle->stderr_file();
763    ATF_REQUIRE(atf::utils::compare_file(
764        stderr_file.str(), "stderr: print_params\n"));
765
766    result_handle->cleanup();
767    ATF_REQUIRE(!fs::exists(stdout_file));
768    ATF_REQUIRE(!fs::exists(stderr_file));
769    result_handle.reset();
770
771    handle.cleanup();
772}
773
774
775ATF_TEST_CASE_WITHOUT_HEAD(integration__fake_result);
776ATF_TEST_CASE_BODY(integration__fake_result)
777{
778    const model::test_result fake_result(model::test_result_skipped,
779                                         "Some fake details");
780
781    model::test_cases_map test_cases;
782    test_cases.insert(model::test_cases_map::value_type(
783        "__fake__", model::test_case("__fake__", "ABC", fake_result)));
784
785    const model::test_program_ptr program(new model::test_program(
786        "mock", fs::path("the-program"), fs::current_path(), "the-suite",
787        model::metadata_builder().build(), test_cases));
788
789    const config::tree user_config = engine::empty_config();
790
791    scheduler::scheduler_handle handle = scheduler::setup();
792
793    (void)handle.spawn_test(program, "__fake__", user_config);
794
795    scheduler::result_handle_ptr result_handle = handle.wait_any();
796    const scheduler::test_result_handle* test_result_handle =
797        dynamic_cast< const scheduler::test_result_handle* >(
798            result_handle.get());
799    ATF_REQUIRE_EQ(fake_result, test_result_handle->test_result());
800    result_handle->cleanup();
801    result_handle.reset();
802
803    handle.cleanup();
804}
805
806
807ATF_TEST_CASE_WITHOUT_HEAD(integration__cleanup__head_skips);
808ATF_TEST_CASE_BODY(integration__cleanup__head_skips)
809{
810    const model::test_program_ptr program = model::test_program_builder(
811        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
812        .add_test_case("skip_me",
813                       model::metadata_builder()
814                       .add_required_config("variable-that-does-not-exist")
815                       .set_has_cleanup(true)
816                       .build())
817        .build_ptr();
818
819    const config::tree user_config = engine::empty_config();
820
821    scheduler::scheduler_handle handle = scheduler::setup();
822
823    (void)handle.spawn_test(program, "skip_me", user_config);
824
825    scheduler::result_handle_ptr result_handle = handle.wait_any();
826    const scheduler::test_result_handle* test_result_handle =
827        dynamic_cast< const scheduler::test_result_handle* >(
828            result_handle.get());
829    ATF_REQUIRE_EQ(model::test_result(
830                       model::test_result_skipped,
831                       "Required configuration property "
832                       "'variable-that-does-not-exist' not defined"),
833                   test_result_handle->test_result());
834    ATF_REQUIRE(!atf::utils::grep_file("exec_cleanup was called",
835                                       result_handle->stdout_file().str()));
836    result_handle->cleanup();
837    result_handle.reset();
838
839    handle.cleanup();
840}
841
842
843/// Runs a test to verify the behavior of cleanup routines.
844///
845/// \param test_case The name of the test case to invoke.
846/// \param exp_result The expected test result of the execution.
847static void
848do_cleanup_test(const char* test_case,
849                const model::test_result& exp_result)
850{
851    const model::test_program_ptr program = model::test_program_builder(
852        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
853        .add_test_case(test_case)
854        .set_metadata(model::metadata_builder().set_has_cleanup(true).build())
855        .build_ptr();
856
857    const config::tree user_config = engine::empty_config();
858
859    scheduler::scheduler_handle handle = scheduler::setup();
860
861    (void)handle.spawn_test(program, test_case, user_config);
862
863    scheduler::result_handle_ptr result_handle = handle.wait_any();
864    const scheduler::test_result_handle* test_result_handle =
865        dynamic_cast< const scheduler::test_result_handle* >(
866            result_handle.get());
867    ATF_REQUIRE_EQ(exp_result, test_result_handle->test_result());
868    ATF_REQUIRE(atf::utils::compare_file(
869        result_handle->stdout_file().str(),
870        "exec_cleanup was called\n"));
871    result_handle->cleanup();
872    result_handle.reset();
873
874    handle.cleanup();
875}
876
877
878ATF_TEST_CASE_WITHOUT_HEAD(integration__cleanup__body_skips);
879ATF_TEST_CASE_BODY(integration__cleanup__body_skips)
880{
881    do_cleanup_test(
882        "skip_body_pass_cleanup",
883        model::test_result(model::test_result_skipped, "Exit 0"));
884}
885
886
887ATF_TEST_CASE_WITHOUT_HEAD(integration__cleanup__body_bad__cleanup_ok);
888ATF_TEST_CASE_BODY(integration__cleanup__body_bad__cleanup_ok)
889{
890    do_cleanup_test(
891        "fail_body_pass_cleanup",
892        model::test_result(model::test_result_failed, "Signal 15"));
893}
894
895
896ATF_TEST_CASE_WITHOUT_HEAD(integration__cleanup__body_ok__cleanup_bad);
897ATF_TEST_CASE_BODY(integration__cleanup__body_ok__cleanup_bad)
898{
899    do_cleanup_test(
900        "pass_body_fail_cleanup",
901        model::test_result(model::test_result_broken, "Test case cleanup "
902                           "did not terminate successfully"));
903}
904
905
906ATF_TEST_CASE_WITHOUT_HEAD(integration__cleanup__body_bad__cleanup_bad);
907ATF_TEST_CASE_BODY(integration__cleanup__body_bad__cleanup_bad)
908{
909    do_cleanup_test(
910        "fail_body_fail_cleanup",
911        model::test_result(model::test_result_failed, "Signal 15"));
912}
913
914
915ATF_TEST_CASE_WITHOUT_HEAD(integration__cleanup__timeout);
916ATF_TEST_CASE_BODY(integration__cleanup__timeout)
917{
918    scheduler::cleanup_timeout = datetime::delta(1, 0);
919    do_cleanup_test(
920        "cleanup_timeout",
921        model::test_result(model::test_result_broken, "Test case cleanup "
922                           "timed out"));
923}
924
925
926ATF_TEST_CASE_WITHOUT_HEAD(integration__check_requirements);
927ATF_TEST_CASE_BODY(integration__check_requirements)
928{
929    const model::test_program_ptr program = model::test_program_builder(
930        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
931        .add_test_case("exit 12")
932        .set_metadata(model::metadata_builder()
933                      .add_required_config("abcde").build())
934        .build_ptr();
935
936    const config::tree user_config = engine::empty_config();
937
938    scheduler::scheduler_handle handle = scheduler::setup();
939
940    (void)handle.spawn_test(program, "exit 12", user_config);
941
942    scheduler::result_handle_ptr result_handle = handle.wait_any();
943    const scheduler::test_result_handle* test_result_handle =
944        dynamic_cast< const scheduler::test_result_handle* >(
945            result_handle.get());
946    ATF_REQUIRE_EQ(model::test_result(
947                       model::test_result_skipped,
948                       "Required configuration property 'abcde' not defined"),
949                   test_result_handle->test_result());
950    result_handle->cleanup();
951    result_handle.reset();
952
953    handle.cleanup();
954}
955
956
957ATF_TEST_CASE_WITHOUT_HEAD(integration__stacktrace);
958ATF_TEST_CASE_BODY(integration__stacktrace)
959{
960    utils::prepare_coredump_test(this);
961
962    const model::test_program_ptr program = model::test_program_builder(
963        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
964        .add_test_case("unknown-dumps-core").build_ptr();
965
966    const config::tree user_config = engine::empty_config();
967
968    scheduler::scheduler_handle handle = scheduler::setup();
969
970    (void)handle.spawn_test(program, "unknown-dumps-core", user_config);
971
972    scheduler::result_handle_ptr result_handle = handle.wait_any();
973    const scheduler::test_result_handle* test_result_handle =
974        dynamic_cast< const scheduler::test_result_handle* >(
975            result_handle.get());
976    ATF_REQUIRE_EQ(model::test_result(model::test_result_failed,
977                                      F("Signal %s") % SIGABRT),
978                   test_result_handle->test_result());
979    ATF_REQUIRE(!atf::utils::grep_file("attempting to gather stack trace",
980                                       result_handle->stdout_file().str()));
981    ATF_REQUIRE( atf::utils::grep_file("attempting to gather stack trace",
982                                       result_handle->stderr_file().str()));
983    result_handle->cleanup();
984    result_handle.reset();
985
986    handle.cleanup();
987}
988
989
990/// Runs a test to verify the dumping of the list of existing files on failure.
991///
992/// \param test_case The name of the test case to invoke.
993/// \param exp_stderr Expected contents of stderr.
994static void
995do_check_list_files_on_failure(const char* test_case, const char* exp_stderr)
996{
997    const model::test_program_ptr program = model::test_program_builder(
998        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
999        .add_test_case(test_case).build_ptr();
1000
1001    const config::tree user_config = engine::empty_config();
1002
1003    scheduler::scheduler_handle handle = scheduler::setup();
1004
1005    (void)handle.spawn_test(program, test_case, user_config);
1006
1007    scheduler::result_handle_ptr result_handle = handle.wait_any();
1008    atf::utils::cat_file(result_handle->stdout_file().str(), "child stdout: ");
1009    ATF_REQUIRE(atf::utils::compare_file(result_handle->stdout_file().str(),
1010                                         ""));
1011    atf::utils::cat_file(result_handle->stderr_file().str(), "child stderr: ");
1012    ATF_REQUIRE(atf::utils::compare_file(result_handle->stderr_file().str(),
1013                                         exp_stderr));
1014    result_handle->cleanup();
1015    result_handle.reset();
1016
1017    handle.cleanup();
1018}
1019
1020
1021ATF_TEST_CASE_WITHOUT_HEAD(integration__list_files_on_failure__none);
1022ATF_TEST_CASE_BODY(integration__list_files_on_failure__none)
1023{
1024    do_check_list_files_on_failure("fail", "This should not be clobbered\n");
1025}
1026
1027
1028ATF_TEST_CASE_WITHOUT_HEAD(integration__list_files_on_failure__some);
1029ATF_TEST_CASE_BODY(integration__list_files_on_failure__some)
1030{
1031    do_check_list_files_on_failure(
1032        "create_files_and_fail",
1033        "This should not be clobbered\n"
1034        "Files left in work directory after failure: "
1035        "dir1, first file, second-file\n");
1036}
1037
1038
1039ATF_TEST_CASE_WITHOUT_HEAD(integration__prevent_clobbering_control_files);
1040ATF_TEST_CASE_BODY(integration__prevent_clobbering_control_files)
1041{
1042    const model::test_program_ptr program = model::test_program_builder(
1043        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
1044        .add_test_case("delete_all").build_ptr();
1045
1046    const config::tree user_config = engine::empty_config();
1047
1048    scheduler::scheduler_handle handle = scheduler::setup();
1049
1050    (void)handle.spawn_test(program, "delete_all", user_config);
1051
1052    scheduler::result_handle_ptr result_handle = handle.wait_any();
1053    const scheduler::test_result_handle* test_result_handle =
1054        dynamic_cast< const scheduler::test_result_handle* >(
1055            result_handle.get());
1056    ATF_REQUIRE_EQ(model::test_result(model::test_result_passed, "Exit 0"),
1057                   test_result_handle->test_result());
1058    result_handle->cleanup();
1059    result_handle.reset();
1060
1061    handle.cleanup();
1062}
1063
1064
1065ATF_TEST_CASE_WITHOUT_HEAD(debug_test);
1066ATF_TEST_CASE_BODY(debug_test)
1067{
1068    const model::test_program_ptr program = model::test_program_builder(
1069        "mock", fs::path("the-program"), fs::current_path(), "the-suite")
1070        .add_test_case("print_params").build_ptr();
1071
1072    config::tree user_config = engine::empty_config();
1073    user_config.set_string("test_suites.the-suite.one", "first variable");
1074    user_config.set_string("test_suites.the-suite.two", "second variable");
1075
1076    scheduler::scheduler_handle handle = scheduler::setup();
1077
1078    const fs::path stdout_file("custom-stdout.txt");
1079    const fs::path stderr_file("custom-stderr.txt");
1080
1081    scheduler::result_handle_ptr result_handle = handle.debug_test(
1082        program, "print_params", user_config, stdout_file, stderr_file);
1083    const scheduler::test_result_handle* test_result_handle =
1084        dynamic_cast< const scheduler::test_result_handle* >(
1085            result_handle.get());
1086
1087    ATF_REQUIRE_EQ(program, test_result_handle->test_program());
1088    ATF_REQUIRE_EQ("print_params", test_result_handle->test_case_name());
1089    ATF_REQUIRE_EQ(model::test_result(model::test_result_passed, "Exit 0"),
1090                   test_result_handle->test_result());
1091
1092    // The original output went to a file.  It's only an artifact of
1093    // debug_test() that we later get a copy in our own files.
1094    ATF_REQUIRE(stdout_file != result_handle->stdout_file());
1095    ATF_REQUIRE(stderr_file != result_handle->stderr_file());
1096
1097    result_handle->cleanup();
1098    result_handle.reset();
1099
1100    handle.cleanup();
1101
1102    ATF_REQUIRE(atf::utils::compare_file(
1103        stdout_file.str(),
1104        "Test program: the-program\n"
1105        "Test case: print_params\n"
1106        "one=first variable\n"
1107        "two=second variable\n"));
1108    ATF_REQUIRE(atf::utils::compare_file(
1109        stderr_file.str(), "stderr: print_params\n"));
1110}
1111
1112
1113ATF_TEST_CASE_WITHOUT_HEAD(ensure_valid_interface);
1114ATF_TEST_CASE_BODY(ensure_valid_interface)
1115{
1116    scheduler::ensure_valid_interface("mock");
1117
1118    ATF_REQUIRE_THROW_RE(engine::error, "Unsupported test interface 'mock2'",
1119                         scheduler::ensure_valid_interface("mock2"));
1120    scheduler::register_interface(
1121        "mock2", std::shared_ptr< scheduler::interface >(new mock_interface()));
1122    scheduler::ensure_valid_interface("mock2");
1123
1124    // Standard interfaces should not be present unless registered.
1125    ATF_REQUIRE_THROW_RE(engine::error, "Unsupported test interface 'plain'",
1126                         scheduler::ensure_valid_interface("plain"));
1127}
1128
1129
1130ATF_TEST_CASE_WITHOUT_HEAD(registered_interface_names);
1131ATF_TEST_CASE_BODY(registered_interface_names)
1132{
1133    std::set< std::string > exp_names;
1134
1135    exp_names.insert("mock");
1136    ATF_REQUIRE_EQ(exp_names, scheduler::registered_interface_names());
1137
1138    scheduler::register_interface(
1139        "mock2", std::shared_ptr< scheduler::interface >(new mock_interface()));
1140    exp_names.insert("mock2");
1141    ATF_REQUIRE_EQ(exp_names, scheduler::registered_interface_names());
1142}
1143
1144
1145ATF_TEST_CASE_WITHOUT_HEAD(current_context);
1146ATF_TEST_CASE_BODY(current_context)
1147{
1148    const model::context context = scheduler::current_context();
1149    ATF_REQUIRE_EQ(fs::current_path(), context.cwd());
1150    ATF_REQUIRE(utils::getallenv() == context.env());
1151}
1152
1153
1154ATF_TEST_CASE_WITHOUT_HEAD(generate_config__empty);
1155ATF_TEST_CASE_BODY(generate_config__empty)
1156{
1157    const config::tree user_config = engine::empty_config();
1158
1159    const config::properties_map exp_props;
1160
1161    ATF_REQUIRE_EQ(exp_props,
1162                   scheduler::generate_config(user_config, "missing"));
1163}
1164
1165
1166ATF_TEST_CASE_WITHOUT_HEAD(generate_config__no_matches);
1167ATF_TEST_CASE_BODY(generate_config__no_matches)
1168{
1169    config::tree user_config = engine::empty_config();
1170    user_config.set_string("architecture", "foo");
1171    user_config.set_string("test_suites.one.var1", "value 1");
1172
1173    const config::properties_map exp_props;
1174
1175    ATF_REQUIRE_EQ(exp_props,
1176                   scheduler::generate_config(user_config, "two"));
1177}
1178
1179
1180ATF_TEST_CASE_WITHOUT_HEAD(generate_config__some_matches);
1181ATF_TEST_CASE_BODY(generate_config__some_matches)
1182{
1183    std::vector< passwd::user > mock_users;
1184    mock_users.push_back(passwd::user("nobody", 1234, 5678));
1185    passwd::set_mock_users_for_testing(mock_users);
1186
1187    config::tree user_config = engine::empty_config();
1188    user_config.set_string("architecture", "foo");
1189    user_config.set_string("unprivileged_user", "nobody");
1190    user_config.set_string("test_suites.one.var1", "value 1");
1191    user_config.set_string("test_suites.two.var2", "value 2");
1192
1193    config::properties_map exp_props;
1194    exp_props["unprivileged-user"] = "nobody";
1195    exp_props["var1"] = "value 1";
1196
1197    ATF_REQUIRE_EQ(exp_props,
1198                   scheduler::generate_config(user_config, "one"));
1199}
1200
1201
1202ATF_INIT_TEST_CASES(tcs)
1203{
1204    scheduler::register_interface(
1205        "mock", std::shared_ptr< scheduler::interface >(new mock_interface()));
1206
1207    ATF_ADD_TEST_CASE(tcs, integration__list_some);
1208    ATF_ADD_TEST_CASE(tcs, integration__list_check_paths);
1209    ATF_ADD_TEST_CASE(tcs, integration__list_timeout);
1210    ATF_ADD_TEST_CASE(tcs, integration__list_fail);
1211    ATF_ADD_TEST_CASE(tcs, integration__list_empty);
1212
1213    ATF_ADD_TEST_CASE(tcs, integration__run_one);
1214    ATF_ADD_TEST_CASE(tcs, integration__run_many);
1215
1216    ATF_ADD_TEST_CASE(tcs, integration__run_check_paths);
1217    ATF_ADD_TEST_CASE(tcs, integration__parameters_and_output);
1218
1219    ATF_ADD_TEST_CASE(tcs, integration__fake_result);
1220    ATF_ADD_TEST_CASE(tcs, integration__cleanup__head_skips);
1221    ATF_ADD_TEST_CASE(tcs, integration__cleanup__body_skips);
1222    ATF_ADD_TEST_CASE(tcs, integration__cleanup__body_ok__cleanup_bad);
1223    ATF_ADD_TEST_CASE(tcs, integration__cleanup__body_bad__cleanup_ok);
1224    ATF_ADD_TEST_CASE(tcs, integration__cleanup__body_bad__cleanup_bad);
1225    ATF_ADD_TEST_CASE(tcs, integration__cleanup__timeout);
1226    ATF_ADD_TEST_CASE(tcs, integration__check_requirements);
1227    ATF_ADD_TEST_CASE(tcs, integration__stacktrace);
1228    ATF_ADD_TEST_CASE(tcs, integration__list_files_on_failure__none);
1229    ATF_ADD_TEST_CASE(tcs, integration__list_files_on_failure__some);
1230    ATF_ADD_TEST_CASE(tcs, integration__prevent_clobbering_control_files);
1231
1232    ATF_ADD_TEST_CASE(tcs, debug_test);
1233
1234    ATF_ADD_TEST_CASE(tcs, ensure_valid_interface);
1235    ATF_ADD_TEST_CASE(tcs, registered_interface_names);
1236
1237    ATF_ADD_TEST_CASE(tcs, current_context);
1238
1239    ATF_ADD_TEST_CASE(tcs, generate_config__empty);
1240    ATF_ADD_TEST_CASE(tcs, generate_config__no_matches);
1241    ATF_ADD_TEST_CASE(tcs, generate_config__some_matches);
1242}
1243