igr.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. #include <fcntl.h>
  2. #include <unistd.h>
  3. #include <dasynq.h>
  4. using event_loop_t = dasynq::event_loop_n;
  5. event_loop_t event_loop;
  6. // directory containing built executables
  7. std::string dinit_bindir;
  8. // directory for all test output (each test under a named subdir)
  9. std::string igr_output_basedir;
  10. // exception to be thrown on failure
  11. class igr_failure_exc
  12. {
  13. std::string message;
  14. public:
  15. igr_failure_exc(std::string message_p) : message(message_p) { }
  16. const std::string &get_message() { return message; }
  17. };
  18. // A process watcher that cleans up by terminating the child process
  19. class igr_proc_watch : public event_loop_t::child_proc_watcher_impl<igr_proc_watch>
  20. {
  21. public:
  22. bool did_exit = true;
  23. pid_t child_pid = -1;
  24. int status = 0;
  25. dasynq::rearm status_change(event_loop_t &, pid_t child, int status_p)
  26. {
  27. status = status_p;
  28. did_exit = true;
  29. child_pid = -1;
  30. return dasynq::rearm::REMOVE;
  31. }
  32. pid_t fork(event_loop_t &eloop, bool from_reserved = false, int prio = dasynq::DEFAULT_PRIORITY)
  33. {
  34. if (child_pid != -1) {
  35. throw std::logic_error("igr_proc_watch: attempted to fork when already watching a process");
  36. }
  37. did_exit = false;
  38. child_pid = event_loop_t::child_proc_watcher_impl<igr_proc_watch>::fork(eloop, from_reserved, prio);
  39. return child_pid;
  40. }
  41. ~igr_proc_watch()
  42. {
  43. if (child_pid != -1) {
  44. deregister(event_loop, child_pid);
  45. kill(child_pid, SIGKILL);
  46. }
  47. }
  48. };
  49. // A simple timer that allows watching for an arbitrary timeout
  50. class simple_timer : public event_loop_t::timer_impl<simple_timer> {
  51. bool is_registered = false;;
  52. bool is_expired = false;
  53. public:
  54. simple_timer()
  55. {
  56. add_timer(event_loop, dasynq::clock_type::MONOTONIC);
  57. is_registered = true;
  58. }
  59. dasynq::rearm timer_expiry(event_loop_t &loop, int expiry_count)
  60. {
  61. is_expired = true;
  62. is_registered = false;
  63. return dasynq::rearm::REMOVE;
  64. }
  65. void arm(const timespec &timeout)
  66. {
  67. is_expired = false;
  68. arm_timer_rel(event_loop, timeout);
  69. }
  70. bool did_expire()
  71. {
  72. return is_expired;
  73. }
  74. ~simple_timer()
  75. {
  76. if (is_registered) {
  77. deregister(event_loop);
  78. }
  79. }
  80. };
  81. // Consume and buffer and output from a pipe
  82. class pipe_consume_buffer : public event_loop_t::fd_watcher_impl<pipe_consume_buffer> {
  83. int fds[2];
  84. std::string buffer;
  85. public:
  86. pipe_consume_buffer()
  87. {
  88. if(pipe(fds) != 0) {
  89. throw std::system_error(errno, std::generic_category(), "pipe_consume_buffer: pipe");
  90. }
  91. if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) != 0) {
  92. throw std::system_error(errno, std::generic_category(), "pipe_consume_buffer: fcntl");
  93. }
  94. if (fcntl(fds[0], F_SETFL, O_NONBLOCK) != 0) {
  95. throw std::system_error(errno, std::generic_category(), "pipe_consume_buffer: fcntl");
  96. }
  97. try {
  98. add_watch(event_loop, fds[0], dasynq::IN_EVENTS);
  99. }
  100. catch (...) {
  101. close(fds[0]);
  102. close(fds[1]);
  103. throw;
  104. }
  105. }
  106. ~pipe_consume_buffer()
  107. {
  108. deregister(event_loop);
  109. }
  110. int get_output_fd()
  111. {
  112. return fds[1];
  113. }
  114. std::string get_output()
  115. {
  116. return buffer;
  117. }
  118. void clear()
  119. {
  120. buffer.clear();
  121. }
  122. dasynq::rearm fd_event(event_loop_t &loop, int fd, int flags)
  123. {
  124. // read all we can
  125. char buf[1024];
  126. ssize_t r = read(fd, buf, 1024);
  127. while (r != 0) {
  128. if (r == -1) {
  129. if (errno != EAGAIN && errno != EWOULDBLOCK) {
  130. // actual error, not expected
  131. throw std::system_error(errno, std::generic_category(), "pipe_consume_buffer: read");
  132. }
  133. // otherwise: would block, finish reading now
  134. return dasynq::rearm::REARM;
  135. }
  136. buffer.append(buf, r);
  137. r = read(fd, buf, 1024);
  138. }
  139. // leave disarmed:
  140. return dasynq::rearm::NOOP;
  141. }
  142. };
  143. // External process, for which all output is stored in a buffer (separately for stdout/stderr).
  144. class igr_proc
  145. {
  146. igr_proc_watch pwatch;
  147. pipe_consume_buffer out;
  148. pipe_consume_buffer err;
  149. public:
  150. igr_proc() {}
  151. ~igr_proc() {}
  152. // Start, in specified working directory, with given arguments
  153. void start(const char *wdir, const char *executable, std::vector<std::string> args = {},
  154. bool combine_out_err = false)
  155. {
  156. out.clear();
  157. err.clear();
  158. char **arg_arr = new char *[args.size() + 2];
  159. arg_arr[0] = const_cast<char *>(executable);
  160. unsigned i;
  161. for (i = 0; i < args.size(); ++i) {
  162. arg_arr[i + 1] = const_cast<char *>(args[i].c_str());
  163. }
  164. arg_arr[++i] = nullptr;
  165. pid_t pid = pwatch.fork(event_loop);
  166. if (pid == 0) {
  167. chdir(wdir);
  168. dup2(out.get_output_fd(), STDOUT_FILENO);
  169. if (combine_out_err) {
  170. dup2(out.get_output_fd(), STDERR_FILENO);
  171. }
  172. else {
  173. dup2(err.get_output_fd(), STDERR_FILENO);
  174. }
  175. execv(executable, arg_arr);
  176. exit(EXIT_FAILURE);
  177. }
  178. delete[] arg_arr;
  179. }
  180. int wait_for_term(dasynq::time_val timeout)
  181. {
  182. if (pwatch.did_exit) return pwatch.status;
  183. simple_timer timer;
  184. timer.arm(timeout);
  185. while (!pwatch.did_exit && !timer.did_expire()) {
  186. event_loop.run();
  187. }
  188. if (!pwatch.did_exit) {
  189. throw igr_failure_exc("timeout waiting for termination");
  190. }
  191. return pwatch.status;
  192. }
  193. std::string get_stdout()
  194. {
  195. return out.get_output();
  196. }
  197. std::string get_stderr()
  198. {
  199. return err.get_output();
  200. }
  201. void signal(int signo)
  202. {
  203. pwatch.send_signal(event_loop, signo);
  204. }
  205. };
  206. // dinit process
  207. class dinit_proc : public igr_proc
  208. {
  209. std::unique_ptr<pipe_consume_buffer> ready_pipe_ptr;
  210. public:
  211. dinit_proc() {}
  212. ~dinit_proc() {
  213. // Signal (if not yet terminated) and allow a second for termination
  214. igr_proc::signal(SIGTERM);
  215. try {
  216. wait_for_term({1, 0});
  217. }
  218. catch (igr_failure_exc &exc) {
  219. // timeout, but
  220. // a) we are in destructor and shouldn't throw
  221. // b) this isn't critical, the process will be KILL'd anyway
  222. }
  223. }
  224. void start(const char *wdir, std::vector<std::string> args = {}, bool with_ready_wait = false)
  225. {
  226. if (with_ready_wait) {
  227. ready_pipe_ptr.reset(new pipe_consume_buffer());
  228. args.insert(args.begin(), std::to_string(ready_pipe_ptr->get_output_fd()));
  229. args.insert(args.begin(), "--ready-fd");
  230. }
  231. igr_proc::start(wdir, (dinit_bindir + "/dinit").c_str(), args);
  232. if (with_ready_wait) {
  233. while (ready_pipe_ptr->get_output().empty()) {
  234. event_loop.run();
  235. }
  236. }
  237. }
  238. };
  239. // dinitctl process
  240. class dinitctl_proc : public igr_proc
  241. {
  242. public:
  243. dinitctl_proc() {}
  244. ~dinitctl_proc() {}
  245. void start(const char *wdir, std::vector<std::string> args = {})
  246. {
  247. igr_proc::start(wdir, (dinit_bindir + "/dinitctl").c_str(), args);
  248. }
  249. };
  250. // dinitcheck process
  251. class dinitcheck_proc : public igr_proc
  252. {
  253. public:
  254. dinitcheck_proc() {}
  255. ~dinitcheck_proc() {}
  256. void start(const char *wdir, std::vector<std::string> args = {})
  257. {
  258. igr_proc::start(wdir, (dinit_bindir + "/dinitcheck").c_str(), args, true /* combine stdout/err */);
  259. }
  260. };
  261. // perform basic setup for a test (with automatic teardown)
  262. class igr_test_setup
  263. {
  264. std::string output_dir;
  265. public:
  266. igr_test_setup(const std::string &test_name)
  267. {
  268. output_dir = igr_output_basedir + "/" + test_name;
  269. if (mkdir(output_dir.c_str(), 0700) == -1 && errno != EEXIST) {
  270. throw std::system_error(errno, std::generic_category(), std::string("mkdir: ") + output_dir);
  271. }
  272. setenv("IGR_OUTPUT", output_dir.c_str(), true);
  273. }
  274. ~igr_test_setup()
  275. {
  276. unsetenv("IGR_OUTPUT");
  277. }
  278. const std::string &get_output_dir()
  279. {
  280. return output_dir;
  281. }
  282. // Prepare an output file in the output directory: determine full path name, unlink any existing file
  283. std::string prep_output_file(const std::string &filename)
  284. {
  285. std::string full_filename = output_dir + "/" + filename;
  286. if (unlink(full_filename.c_str()) == -1 && errno != ENOENT) {
  287. throw std::system_error(errno, std::generic_category(),
  288. std::string("unlink " + full_filename));
  289. }
  290. return full_filename;
  291. }
  292. };
  293. // set an environment variable (with automatic restore of original value at teardown)
  294. class igr_env_var_setup
  295. {
  296. std::string orig_value;
  297. std::string var_name;
  298. bool had_value;
  299. public:
  300. igr_env_var_setup(const char *var_name_p, const char *value)
  301. {
  302. var_name = var_name_p;
  303. const char *orig_value_cp = getenv(var_name_p);
  304. had_value = (orig_value_cp != nullptr);
  305. if (had_value) {
  306. orig_value = orig_value_cp;
  307. }
  308. int r;
  309. if (value != nullptr) {
  310. r = setenv(var_name_p, value, true);
  311. }
  312. else {
  313. r = unsetenv(var_name_p);
  314. }
  315. if (r == -1) {
  316. throw std::system_error(errno, std::generic_category(), "setenv/unsetenv");
  317. }
  318. }
  319. ~igr_env_var_setup()
  320. {
  321. if (had_value) {
  322. setenv(var_name.c_str(), orig_value.c_str(), true);
  323. }
  324. else {
  325. unsetenv(var_name.c_str());
  326. }
  327. }
  328. void set(const char *value)
  329. {
  330. int r;
  331. if (value != nullptr) {
  332. r = setenv(var_name.c_str(), value, true);
  333. }
  334. else {
  335. r = unsetenv(var_name.c_str());
  336. }
  337. if (r == -1) {
  338. throw std::system_error(errno, std::generic_category(), "setenv/unsetenv");
  339. }
  340. }
  341. };
  342. // read entire file contents as a string
  343. inline std::string read_file_contents(const std::string &filename)
  344. {
  345. std::string contents;
  346. int fd = open(filename.c_str(), O_RDONLY);
  347. if (fd == -1) {
  348. throw std::system_error(errno, std::generic_category(), "read_file_contents: open");
  349. }
  350. char buf[1024];
  351. int r = read(fd, buf, 1024);
  352. while (r > 0) {
  353. contents.append(buf, r);
  354. r = read(fd, buf, 1024);
  355. }
  356. if (r == -1) {
  357. throw std::system_error(errno, std::generic_category(), "read_file_contents: read");
  358. }
  359. return contents;
  360. }
  361. inline void check_file_contents(const std::string &file_path, const std::string &expected_contents)
  362. {
  363. std::string contents = read_file_contents(file_path);
  364. if (contents != expected_contents) {
  365. std::cout << "File contents mismatch:\n";
  366. std::cout << "expected: " + expected_contents + "\n";
  367. std::cout << "actual : " + contents + "\n";
  368. throw igr_failure_exc(std::string("File contents do not match expected for file ") + file_path);
  369. }
  370. }
  371. inline void igr_assert_eq(const std::string &expected, const std::string &actual)
  372. {
  373. if (expected != actual) {
  374. throw igr_failure_exc(std::string("Test assertion failed:\n") + "Expected: " + expected
  375. + "\nActual: " + actual);
  376. }
  377. }
  378. inline void igr_assert(bool value, const char *msg)
  379. {
  380. if (!value) {
  381. throw igr_failure_exc(std::string("Test assertion failed: ") + msg);
  382. }
  383. }
  384. inline void nanosleepx(decltype(std::declval<timespec>().tv_sec) seconds,
  385. decltype(std::declval<timespec>().tv_nsec) nanoseconds)
  386. {
  387. timespec sleep_time;
  388. sleep_time.tv_sec = 0;
  389. sleep_time.tv_nsec = 1000000000 / 10; // .1 seconds
  390. if (nanosleep(&sleep_time, nullptr) == -1) {
  391. throw std::system_error(errno, std::generic_category(), "nanosleep");
  392. }
  393. }
  394. // Run dinitcheck, return { stdout combined with stderr, exit code }
  395. inline std::pair<std::string, int> run_dinitcheck(const char *wdir,
  396. std::vector<std::string> args = {})
  397. {
  398. dinitcheck_proc dc_proc;
  399. dc_proc.start(wdir, args);
  400. int exit_status = dc_proc.wait_for_term({1, 0} /* max 1 second */);
  401. return { dc_proc.get_stdout(), exit_status };
  402. }