1 // Refer to https://github.com/philsquared/Catch/blob/master/docs/own-main.md
2 // for providing our own main function to Catch
3 #define CATCH_CONFIG_RUNNER
4 #include <catch.hpp>
5
6 #include <leatherman/execution/execution.hpp>
7
8 #include <boost/filesystem.hpp>
9 #include <boost/algorithm/string/replace.hpp>
10 #include <boost/algorithm/string/trim.hpp>
11 #include <vector>
12 #include <fstream>
13
14 using namespace std;
15 namespace fs = boost::filesystem;
16 namespace lth_exec = leatherman::execution;
17 namespace lth_util = leatherman::util;
18
19 // Creates a unique temporary directory.
20 struct temp_directory {
temp_directorytemp_directory21 temp_directory() : dir{fs::absolute(fs::unique_path("execution_fixture_%%%%-%%%%-%%%%-%%%%"))} {
22 fs::create_directory(dir);
23 }
24
~temp_directorytemp_directory25 ~temp_directory() {
26 fs::remove_all(dir);
27 }
28
nametemp_directory29 string name() const {
30 auto s = dir.string();
31 boost::replace_all(s, "\\", "/");
32 return s;
33 }
34
35 private:
36 fs::path dir;
37 };
38
read(string filename)39 static string read(string filename)
40 {
41 ifstream file(filename);
42 return string(istreambuf_iterator<char>(file), istreambuf_iterator<char>());
43 }
44
45 static fs::path exec_prefix;
46
execute(string const & input)47 static lth_exec::result execute(string const& input)
48 {
49 return lth_exec::execute(
50 #ifdef _WIN32
51 "cmd.exe",
52 { "/c", (exec_prefix/"execution_wrapper").string() },
53 #else
54 (exec_prefix/"execution_wrapper").string(),
55 {}, // args
56 #endif
57 input,
58 0, // timeout
59 lth_util::option_set<lth_exec::execution_options> {
60 lth_exec::execution_options::thread_safe,
61 lth_exec::execution_options::merge_environment,
62 lth_exec::execution_options::inherit_locale });
63 }
64
65 TEST_CASE("runs an executable") {
66 temp_directory tmpdir;
67 auto dir = tmpdir.name();
68 auto executable = dir+"/init.bat";
69 auto input = "{\"executable\": \""+executable+"\", \"arguments\": [], "
70 "\"input\": \"{\\\"a\\\": 1, \\\"b\\\": [2, 3], \\\"c\\\": {\\\"hello\\\": \\\"goodbye foo\\\"}}\", "
71 "\"stdout\": \""+dir+"/out\", \"stderr\": \""+dir+"/err\", \"exitcode\": \""+dir+"/exit\"}";
72
73 SECTION("passes input") {
74 ofstream foo(executable);
75 #ifdef _WIN32
76 foo << "@echo off" << endl;
77 foo << "more" << endl;
78 #else
79 foo << "#!/bin/sh" << endl;
80 foo << "cat -" << endl;
81 #endif
82 foo.close();
83
84 #ifndef _WIN32
85 fs::permissions(executable, fs::owner_read|fs::owner_write|fs::owner_exe);
86 #endif
87
88 auto exec = execute(input);
89 REQUIRE(exec.output == "");
90 REQUIRE(exec.error == "");
91 REQUIRE(exec.exit_code == 0);
92
93 auto output = read(dir+"/out");
94 boost::trim(output);
95 REQUIRE(output == "{\"a\": 1, \"b\": [2, 3], \"c\": {\"hello\": \"goodbye foo\"}}");
96
97 REQUIRE(read(dir+"/err") == "");
98 REQUIRE(read(dir+"/exit") == "0");
99 }
100
101 SECTION("errors if executable not found") {
102 auto exec = execute(input);
103 REQUIRE(exec.output == "");
104 REQUIRE(exec.error == "");
105 REQUIRE(exec.exit_code == 127);
106
107 REQUIRE(read(dir+"/out") == "");
108 REQUIRE(read(dir+"/err") == "");
109 REQUIRE(read(dir+"/exit") == "127");
110 }
111
112 SECTION("executable is not executable") {
113 auto executable = dir+"/init";
114 auto input = "{\"executable\": \""+executable+"\", \"arguments\": [], \"input\": \"\", "
115 "\"stdout\": \""+dir+"/out\", \"stderr\": \""+dir+"/err\", \"exitcode\": \""+dir+"/exit\"}";
116 ofstream foo(executable);
117 foo << "";
118 foo.close();
119
120 auto exec = execute(input);
121 REQUIRE(exec.output == "");
122 REQUIRE(exec.error == "");
123 REQUIRE(exec.exit_code == 127);
124
125 REQUIRE(read(dir+"/out") == "");
126 REQUIRE(read(dir+"/err") == "");
127 REQUIRE(read(dir+"/exit") == "127");
128 }
129 }
130
main(int argc,char ** argv)131 int main(int argc, char** argv) {
132 exec_prefix = fs::absolute(fs::path(argv[0]).parent_path());
133
134 // Create the Catch session, pass CL args, and start it
135 Catch::Session test_session {};
136 return test_session.run(argc, argv);
137 }
138
139