add script invocation helpers
This commit is contained in:
parent
69dc2817a4
commit
bb07e35925
|
@ -33,7 +33,7 @@ clean: clean_libyachtrock
|
|||
|
||||
LIBYACHTROCK_GENERATED_SRC :=
|
||||
LIBYACHTROCK_GENERATED_SRC := $(patsubst %,$(LIBYACHTROCK_DIR)src/%,$(LIBYACHTROCK_GENERATED_SRC))
|
||||
LIBYACHTROCK_STATIC_SRC := yachtrock.c runtime.c testcase.c results.c yrutil.c selector.c version.c
|
||||
LIBYACHTROCK_STATIC_SRC := yachtrock.c runtime.c testcase.c results.c yrutil.c selector.c script_helpers.c version.c
|
||||
ifeq ($(YACHTROCK_MULTIPROCESS),1)
|
||||
LIBYACHTROCK_STATIC_SRC += multiprocess.c multiprocess_inferior.c multiprocess_superior.c
|
||||
endif
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
test: test_libyachtrock
|
||||
|
||||
LIBYACHTROCK_TESTSRC := selftests_collection.c basic_tests.c result_store_tests.c assertion_tests.c testcase_tests.c run_under_store_tests.c selector_tests.c
|
||||
LIBYACHTROCK_TESTSRC := selftests_collection.c basic_tests.c result_store_tests.c assertion_tests.c testcase_tests.c run_under_store_tests.c selector_tests.c script_helpers_tests.c
|
||||
ifeq ($(YACHTROCK_MULTIPROCESS),1)
|
||||
LIBYACHTROCK_TESTSRC += multiprocess_basic_tests.c
|
||||
endif
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
#ifdef __cplusplus
|
||||
#define YACHTROCK_EXTERN extern "C"
|
||||
#define YACHTROCK_NORETURN
|
||||
#define YACHTROCK_RESTRICT
|
||||
#else
|
||||
#define YACHTROCK_EXTERN extern
|
||||
#include <stdnoreturn.h>
|
||||
#define YACHTROCK_NORETURN noreturn
|
||||
#define YACHTROCK_RESTRICT restrict
|
||||
#endif
|
||||
|
||||
#define YR_STR(X) #X
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef YACHTROCK_SCRIPT_HELPERS_H
|
||||
#define YACHTROCK_SCRIPT_HELPERS_H
|
||||
|
||||
#include <yachtrock/base.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define YACHTROCK_HAS_SCRIPT_HELPERS (YACHTROCK_UNIXY && YACHTROCK_POSIXY)
|
||||
|
||||
#if YACHTROCK_HAS_SCRIPT_HELPERS
|
||||
|
||||
/**
|
||||
* Invoke a subprocess and wait for it to complete.
|
||||
*
|
||||
* The subprocess is invoked with the given arguments, environment, and file descriptors.
|
||||
*
|
||||
* The program to be executed is taken from the first element in the argument array, which is
|
||||
* NULL-terminated and may be relative. The argument array must be provided and it's length must be
|
||||
* at least 1. The environment array is optional; if not provided, the environment of the caller is
|
||||
* copied. The given file descriptors are optional; if they are -1, the corresponding stream is
|
||||
* closed in the subprocess.
|
||||
*
|
||||
* If the subprocess was invoked successfully its exit status is written to the int pointed to by
|
||||
* stat_loc, if it is not a NULL pointer.
|
||||
*
|
||||
* If the subprocess was created and waited for successfully, the function returns true. Otherwise,
|
||||
* it returns false and sets errno.
|
||||
*/
|
||||
YACHTROCK_EXTERN bool yr_invoke_subprocess(const char * const * YACHTROCK_RESTRICT argv,
|
||||
const char * const * YACHTROCK_RESTRICT envp,
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
int *stat_loc);
|
||||
|
||||
/**
|
||||
* yr_invoke_subprocess, but it automatically asserts that the invoked process exits with a code of
|
||||
* 0.
|
||||
*/
|
||||
YACHTROCK_EXTERN bool yr_invoke_subprocess_with_assert(const char * const * YACHTROCK_RESTRICT argv,
|
||||
const char * const * YACHTROCK_RESTRICT envp,
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
int *stat_loc);
|
||||
#endif // YACHTROCK_HAS_SCRIPT_HELPERS
|
||||
|
||||
#endif // ndef YACHTROCK_SCRIPT_HELPERS_H
|
|
@ -18,11 +18,11 @@ typedef void (*yr_test_case_teardown_function)(yr_test_case_t testcase);
|
|||
typedef void (*yr_test_suite_setup_function)(yr_test_suite_t suite);
|
||||
typedef void (*yr_test_suite_teardown_function)(yr_test_suite_t suite);
|
||||
|
||||
#define __YR_DEVARIADICIFY_2(dummy, A, ...) A
|
||||
#define YR_DEVARIADICIFY_2(dummy, A, ...) A
|
||||
/**
|
||||
* Helper macro to define a testcase function.
|
||||
*/
|
||||
#define YR_TESTCASE(name, ...) void name(yr_test_case_t __YR_DEVARIADICIFY_2(dummy, ##__VA_ARGS__ , testcase) )
|
||||
#define YR_TESTCASE(name, ...) void name(yr_test_case_t YR_DEVARIADICIFY_2(dummy, ##__VA_ARGS__ , testcase) )
|
||||
|
||||
/**
|
||||
* A single test case.
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
#include <yachtrock/base.h>
|
||||
#include <yachtrock/assert.h>
|
||||
#include <yachtrock/testcase.h>
|
||||
#include <yachtrock/script_helpers.h>
|
||||
#include <yachtrock/results.h>
|
||||
#include <yachtrock/selector.h>
|
||||
#include <yachtrock/version.h>
|
||||
|
||||
/**
|
||||
* Runtime callbacks and result hooks that print status to stderr.
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
#include <yachtrock/script_helpers.h>
|
||||
#include <yachtrock/assert.h>
|
||||
|
||||
#if YACHTROCK_HAS_SCRIPT_HELPERS
|
||||
|
||||
#include <spawn.h>
|
||||
|
||||
#include "yrutil.h"
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static int add_stream_actions(posix_spawn_file_actions_t *actions, int fd, int nominal_fd)
|
||||
{
|
||||
int result = 0;
|
||||
if ( fd == -1 ) {
|
||||
result = posix_spawn_file_actions_addclose(actions, nominal_fd);
|
||||
if ( result != 0 ) {
|
||||
yr_warnc(result, "posix_spawn_file_actions_addclose failed");
|
||||
}
|
||||
} else {
|
||||
result = posix_spawn_file_actions_adddup2(actions, fd, nominal_fd);
|
||||
if ( result != 0 ) {
|
||||
yr_warnc(result, "posix_spawn_file_actions_adddup2 failed");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool yr_invoke_subprocess(const char * const * YACHTROCK_RESTRICT argv,
|
||||
const char * const * YACHTROCK_RESTRICT envp,
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
int *stat_loc)
|
||||
{
|
||||
bool ok = false;
|
||||
int scratch_err = 0;
|
||||
YR_RUNTIME_ASSERT(argv && argv[0], "bad argv in %s!", __FUNCTION__);
|
||||
const char *prog = argv[0];
|
||||
if ( envp == NULL ) {
|
||||
envp = (const char **)environ; // we solemnly swear not to perform Shenanigans
|
||||
}
|
||||
|
||||
posix_spawn_file_actions_t file_actions;
|
||||
if ( (scratch_err = posix_spawn_file_actions_init(&file_actions)) != 0 ) {
|
||||
errno = scratch_err;
|
||||
goto out_noactions;
|
||||
}
|
||||
|
||||
if ( (scratch_err = add_stream_actions(&file_actions, stdin_fd, 0)) != 0 ) {
|
||||
goto out;
|
||||
}
|
||||
if ( (scratch_err = add_stream_actions(&file_actions, stdout_fd, 1)) != 0 ) {
|
||||
goto out;
|
||||
}
|
||||
if ( (scratch_err = add_stream_actions(&file_actions, stderr_fd, 2)) != 0 ) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
pid_t pid = 0;
|
||||
if ( (scratch_err =
|
||||
posix_spawn(&pid, prog, &file_actions, NULL, (char **)argv, (char **)envp)) != 0 ) {
|
||||
yr_warnc(scratch_err, "posix_spawn failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
pid_t awaited;
|
||||
int wait_stat_loc;
|
||||
do {
|
||||
awaited = waitpid(pid, &wait_stat_loc, 0);
|
||||
} while ( awaited >= 0 && !(WIFEXITED(wait_stat_loc) || WIFSIGNALED(wait_stat_loc)) );
|
||||
|
||||
if ( awaited < 0 ) {
|
||||
yr_warn("waitpid failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
YR_RUNTIME_ASSERT(awaited == pid, "waitpid() on %d returned bogus pid %d?",
|
||||
(int)pid, (int)awaited);
|
||||
|
||||
if ( stat_loc ) {
|
||||
*stat_loc = wait_stat_loc;
|
||||
}
|
||||
|
||||
/* All done and it feels so good! */
|
||||
ok = true;
|
||||
|
||||
out:
|
||||
posix_spawn_file_actions_destroy(&file_actions);
|
||||
|
||||
out_noactions:
|
||||
return ok;
|
||||
}
|
||||
|
||||
YACHTROCK_EXTERN bool yr_invoke_subprocess_with_assert(const char * const * YACHTROCK_RESTRICT argv,
|
||||
const char * const * YACHTROCK_RESTRICT envp,
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
int *stat_loc)
|
||||
{
|
||||
int my_stat_loc;
|
||||
bool invoke_ok = yr_invoke_subprocess(argv, envp, stdin_fd, stdout_fd, stderr_fd, &my_stat_loc);
|
||||
YR_ASSERT(invoke_ok, "failed to start subprocess!");
|
||||
if ( invoke_ok ) {
|
||||
YR_ASSERT(WIFEXITED(my_stat_loc) && WEXITSTATUS(my_stat_loc) == 0,
|
||||
"subprocess exited uncleanly!");
|
||||
}
|
||||
if ( invoke_ok && stat_loc ) {
|
||||
*stat_loc = my_stat_loc;
|
||||
}
|
||||
return invoke_ok;
|
||||
}
|
||||
|
||||
#endif // YACHTROCK_HAS_SCRIPT_HELPERS
|
|
@ -0,0 +1,135 @@
|
|||
#include <yachtrock/script_helpers.h>
|
||||
|
||||
#if YACHTROCK_HAS_SCRIPT_HELPERS
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "yrtests.h"
|
||||
|
||||
static const char basic_invoke_script[] =
|
||||
"#!/bin/sh\n"
|
||||
"read line\n"
|
||||
"if [ \"$line\" = \"cool script\" ]; then\n"
|
||||
" echo invoke success woo\n"
|
||||
" exit 0\n"
|
||||
"else\n"
|
||||
" echo invoke failure boo >&2\n"
|
||||
" exit 12\n"
|
||||
"fi\n";
|
||||
static bool write_basic_invoke_script(char *out_path_buf)
|
||||
{
|
||||
char temp_file_name[] = "/tmp/tempscript.XXXX";
|
||||
int tmpfd = mkstemp(temp_file_name);
|
||||
YR_ASSERT(tmpfd >= 0);
|
||||
if ( tmpfd < 0 ) { return false; /* bail */ }
|
||||
YR_ASSERT_EQUAL(fchmod(tmpfd, 0700), 0);
|
||||
FILE *f = fdopen(tmpfd, "w");
|
||||
size_t nwritten = 0;
|
||||
while ( nwritten < sizeof(basic_invoke_script) && !ferror(f) ) {
|
||||
size_t this_write = sizeof(basic_invoke_script) - nwritten;
|
||||
nwritten += fwrite(basic_invoke_script + nwritten, 1, this_write, f);
|
||||
}
|
||||
YR_ASSERT_EQUAL(nwritten, sizeof(basic_invoke_script));
|
||||
fclose(f);
|
||||
strcpy(out_path_buf, temp_file_name);
|
||||
return nwritten == sizeof(basic_invoke_script);
|
||||
}
|
||||
|
||||
static YR_TESTCASE(test_script_basic_invoke)
|
||||
{
|
||||
char execpath[PATH_MAX];
|
||||
if ( !write_basic_invoke_script(execpath) ) {
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "gonna exec %s\n", execpath);
|
||||
int p[2];
|
||||
YR_ASSERT_EQUAL(pipe(p), 0);
|
||||
const char *data = "cool script\n";
|
||||
YR_ASSERT_EQUAL(write(p[1], data, strlen(data)), strlen(data));
|
||||
const char *script_argv[] = { execpath, NULL };
|
||||
YR_ASSERT(yr_invoke_subprocess_with_assert(script_argv, NULL, p[0], 1, 2, NULL));
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
static YR_TESTCASE(test_script_test_pipe)
|
||||
{
|
||||
char execpath[PATH_MAX];
|
||||
if ( !write_basic_invoke_script(execpath) ) {
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "gonna exec %s\n", execpath);
|
||||
int sub_stdin_pipe[2];
|
||||
int sub_stderr_pipe[2];
|
||||
YR_ASSERT_EQUAL(pipe(sub_stdin_pipe), 0);
|
||||
YR_ASSERT_EQUAL(pipe(sub_stderr_pipe), 0);
|
||||
const char *script_argv[] = { execpath, NULL };
|
||||
int stat_loc;
|
||||
close(sub_stdin_pipe[1]);
|
||||
YR_ASSERT(yr_invoke_subprocess(script_argv, NULL, sub_stdin_pipe[0], 1, sub_stderr_pipe[1], &stat_loc));
|
||||
char read_buf[128];
|
||||
memset(read_buf, 0, sizeof(read_buf));
|
||||
char *expected_read = "invoke failure boo\n";
|
||||
size_t nread = read(sub_stderr_pipe[0], read_buf, sizeof(read_buf) - 1);
|
||||
YR_ASSERT_EQUAL(nread, strlen(expected_read), "read %zu bytes instead of %zu",
|
||||
nread, strlen(expected_read));
|
||||
fwrite(read_buf, 1, nread, stderr);
|
||||
close(sub_stderr_pipe[0]);
|
||||
close(sub_stderr_pipe[1]);
|
||||
YR_ASSERT(WIFEXITED(stat_loc));
|
||||
YR_ASSERT_EQUAL(WEXITSTATUS(stat_loc), 12);
|
||||
}
|
||||
|
||||
static YR_TESTCASE(test_test_fails_if_script_fails_invoke_expected_fail)
|
||||
{
|
||||
char execpath[PATH_MAX];
|
||||
if ( !write_basic_invoke_script(execpath) ) {
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "gonna exec %s\n", execpath);
|
||||
const char *script_argv[] = { execpath, NULL };
|
||||
yr_invoke_subprocess_with_assert(script_argv, NULL, -1, 1, 2, NULL);
|
||||
}
|
||||
|
||||
static YR_TESTCASE(test_test_fails_if_script_cant_spawn_invoke_expected_fail)
|
||||
{
|
||||
char execpath[PATH_MAX];
|
||||
if ( !write_basic_invoke_script(execpath) ) {
|
||||
return;
|
||||
}
|
||||
const char *script_argv[] = { "/tmp/nonexistent", NULL };
|
||||
yr_invoke_subprocess_with_assert(script_argv, NULL, -1, 1, 2, NULL);
|
||||
}
|
||||
|
||||
static YR_TESTCASE(test_test_fails_if_script_fails)
|
||||
{
|
||||
// Script is going to die a flaming death without any stdin at all, but it should still fail.
|
||||
yr_test_suite_t suite =
|
||||
yr_create_suite_from_functions("testing test fails if script fails",
|
||||
NULL, YR_NO_CALLBACKS,
|
||||
test_test_fails_if_script_fails_invoke_expected_fail);
|
||||
bool ok = yr_basic_run_suite(suite);
|
||||
free(suite);
|
||||
YR_ASSERT_FALSE(ok);
|
||||
|
||||
suite =
|
||||
yr_create_suite_from_functions("testing test fails if script can't spawn",
|
||||
NULL, YR_NO_CALLBACKS,
|
||||
test_test_fails_if_script_cant_spawn_invoke_expected_fail);
|
||||
ok = yr_basic_run_suite(suite);
|
||||
free(suite);
|
||||
YR_ASSERT_FALSE(ok);
|
||||
}
|
||||
|
||||
yr_test_suite_t yr_create_script_helpers_suite(void)
|
||||
{
|
||||
return yr_create_suite_from_functions("script helpers tests", NULL, YR_NO_CALLBACKS,
|
||||
test_script_basic_invoke, test_script_test_pipe,
|
||||
test_test_fails_if_script_fails);
|
||||
}
|
||||
|
||||
#endif // YACHTROCK_HAS_SCRIPT_HELPERS
|
|
@ -10,6 +10,9 @@ yr_test_suite_collection_t yachtrock_selftests_collection(void)
|
|||
yr_create_run_under_store_suite(),
|
||||
#if YACHTROCK_MULTIPROCESS
|
||||
yr_create_multiprocess_suite(),
|
||||
#endif
|
||||
#if YACHTROCK_HAS_SCRIPT_HELPERS
|
||||
yr_create_script_helpers_suite(),
|
||||
#endif
|
||||
yr_create_selector_suite(),
|
||||
};
|
||||
|
|
|
@ -11,4 +11,8 @@ extern yr_test_suite_t yr_create_selector_suite(void);
|
|||
extern yr_test_suite_t yr_create_multiprocess_suite(void);
|
||||
#endif
|
||||
|
||||
#if YACHTROCK_HAS_SCRIPT_HELPERS
|
||||
extern yr_test_suite_t yr_create_script_helpers_suite(void);
|
||||
#endif
|
||||
|
||||
extern yr_test_suite_collection_t yachtrock_selftests_collection(void);
|
||||
|
|
Loading…
Reference in New Issue