/*
* Copyright (c) 2012 Eric Radman <ericshane@eradman.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "entr.c"
/* globals */
extern WatchFile **files;
/* test context */
struct {
struct {
int count;
char *file;
char **argv;
} exec;
struct {
struct kevent Set[32];
struct kevent List[32];
int nset;
int nlist;
} event;
struct {
int pid;
int sig;
int count;
} signal;
struct {
int pid;
int count;
} wait;
struct {
int fd;
const char *path;
} open;
struct {
int count;
} warn;
struct {
int count;
} exit;
} ctx;
/* test runner */
int tests_run, failures;
const char* func;
int line;
static void reset_state();
static void fail();
#define _() func=__func__; line=__LINE__;
#define ok(test) do { _(); if (!(test)) { fail(); return 1; } } while(0)
#define run(test) do { reset_state(); tests_run++; test(); } while(0)
void fail() {
failures++;
fprintf(stderr, "test failure in %s() line %d\n", func, line);
}
void reset_state() {
int i;
int max_files = 32;
/* getopt(3) keeps an external reference */
optind = 1;
/* initialize global data */
clear_opt = 0;
dirwatch_opt = 0;
postpone_opt = 0;
restart_opt = 0;
shell_opt = 0;
leading_edge = 0;
files = calloc(max_files, sizeof(WatchFile *));
for (i=0; i<max_files; i++)
files[i] = calloc(1, sizeof(WatchFile));
/* initialize test context */
memset(&ctx, 0, sizeof(ctx));
setenv("SHELL", "/bin/Xsh", 1);
}
void sighandler(int signum) {
fail();
/* generate core dump */
signal(signum, SIG_DFL);
kill(getpid(), signum);
}
/* stubs */
int
fake_stat(const char *path, struct stat *sb) {
if (strncmp(path, "nosuch", 5) == 0)
return -1;
if (strncmp(path, "dir", 3) == 0)
sb->st_mode = S_IFDIR | S_IRUSR;
else
sb->st_mode = S_IFREG | S_IRUSR;
return 0;
}
pid_t
fake_waitpid(pid_t wpid, int *status, int options) {
ctx.wait.pid = wpid;
ctx.wait.count++;
return wpid;
}
char *
fake_realpath(const char *pathname, char *resolved) {
snprintf(resolved, PATH_MAX, "/home/user/%s", pathname);
return resolved;
}
int
fake_list_dir(char *path) {
return 2;
}
pid_t
fake_fork() {
return child_pid; /* pretend to be the child */
}
void
fake_free(void *ptr) {
}
int
fake_tcsetattr(int fd, int action, const struct termios *tp) {
return 0;
}
/* mock objects */
/*
* kevent(2) is used to change and retrieve events.
* This version always returns at most 2 events
*/
int
fake_kevent(int kq, const struct kevent *changelist, int nchanges, struct
kevent *eventlist, int nevents, const struct timespec *timeout) {
int decrement;
decrement = MIN(ctx.event.nlist, 2);
/* record each event that the application sets */
if (nchanges > 0) {
memcpy(&ctx.event.Set[ctx.event.nset], changelist,
sizeof(struct kevent) * nchanges);
ctx.event.nset += nchanges;
return nchanges;
}
/* return list of events that each test sets up */
if ((nevents > 0) && (ctx.event.nlist > 0)) {
memcpy(eventlist, &ctx.event.List,
sizeof(struct kevent) * ctx.event.nlist);
ctx.event.nlist -= decrement;
return decrement;
}
/* no more events, use bogus return code to cause the main loop to exit */
return -2;
}
/* spies */
int
fake_killpg(pid_t pid, int sig) {
ctx.signal.pid = pid;
ctx.signal.sig = sig;
ctx.signal.count++;
return 0;
}
int
fake_execvp(const char *file, char *const argv[]) {
ctx.exec.count++;
ctx.exec.file = (char *)file;
ctx.exec.argv = (char **)argv;
return 0;
}
int
fake_open(const char *path, int flags, ...) {
ctx.open.path = path;
ctx.open.fd++;
return ctx.open.fd;
}
void
fake_warnx(const char *msg, ...) {
ctx.warn.count++;
}
void
fake_errx(int eval, const char *msg, ...) {
ctx.exit.count++;
}
/* tests */
/*
* Read a list of user supplied files where input exceeds available watch
* descriptors
*/
int process_input_01() {
char input[] = "file1\nfile2\nfile3";
FILE *fake;
int n_files;
fake = fmemopen(input, strlen(input), "r");
n_files = process_input(fake, files, 3);
ok(n_files == -1);
ok(strcmp(files[0]->fn, "file1") == 0);
ok(strcmp(files[1]->fn, "file2") == 0);
ok(strcmp(files[2]->fn, "file3") == 0);
return 0;
}
/*
* Read a list of user supplied files and populate files array
*/
int process_input_02() {
int n_files;
FILE *fake;
char input[] = "dir1\nfile1\nfile2\nfile3";
fake = fmemopen(input, strlen(input), "r");
n_files = process_input(fake, files, 16);
ok(n_files == 3);
ok(strcmp(files[0]->fn, "file1") == 0);
ok(strcmp(files[1]->fn, "file2") == 0);
ok(strcmp(files[2]->fn, "file3") == 0);
return 0;
}
/*
* Read a list of user supplied files and directories
*/
int process_input_03() {
int n_files;
FILE *fake;
char input[] = "dir1\nfile1\nfile2\nfile3";
dirwatch_opt = 1;
fake = fmemopen(input, strlen(input), "r");
n_files = process_input(fake, files, 32);
ok(n_files == 5);
ok(strcmp(files[0]->fn, "dir1") == 0);
ok(strcmp(files[1]->fn, "file1") == 0);
ok(strcmp(files[2]->fn, ".") == 0);
ok(strcmp(files[3]->fn, "file2") == 0);
ok(strcmp(files[4]->fn, "file3") == 0);
ok(files[0]->is_dir == 1); /* dir1 */
ok(files[1]->is_dir == 0); /* file1 */
ok(files[2]->is_dir == 1); /* . */
ok(files[3]->is_dir == 0); /* file2 */
ok(files[4]->is_dir == 0); /* file3 */
return 0;
}
/*
* Read a list of user supplied files where one of the files cannot be opened
*/
int process_input_04() {
char input[] = "file1\nnosuch1\nfile2";
FILE *fake;
int n_files;
fake = fmemopen(input, strlen(input), "r");
n_files = process_input(fake, files, 4);
ok(ctx.warn.count == 1);
ok(n_files == 2);
ok(strcmp(files[0]->fn, "file1") == 0);
ok(strcmp(files[1]->fn, "file2") == 0);
return 0;
}
/*
* Remove a file
*/
int watch_fd_exec_01() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, "arg1", sizeof(files[0]->fn));
watch_file(kq, files[0]);
/* event 1/1: 4 (-4) 0x21 0x1 0 0x84d5e800 */
ctx.event.nlist = 1;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_DELETE, 0, files[0]);
watch_loop(kq, argv);
ok(ctx.event.nset == 3);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]);
ok(ctx.event.Set[1].ident);
ok(ctx.event.Set[1].filter == EVFILT_VNODE);
ok(ctx.event.Set[1].flags == EV_DELETE); /* remove */
ok(ctx.event.Set[1].fflags == (NOTE_ALL));
ok(ctx.event.Set[1].udata == files[0]);
ok(ctx.event.Set[2].ident);
ok(ctx.event.Set[2].filter == EVFILT_VNODE);
ok(ctx.event.Set[2].flags == (EV_CLEAR|EV_ADD)); /* reopen */
ok(ctx.event.Set[2].fflags == (NOTE_ALL));
ok(ctx.event.Set[2].udata == files[0]);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "prog") == 0);
ok(strcmp(ctx.exec.argv[0], "prog") == 0);
ok(strcmp(ctx.exec.argv[1], "arg1") == 0);
ok(strcmp(ctx.exec.argv[2], "arg2") == 0);
ok(ctx.exit.count == 0);
return 0;
}
/*
* Change a file attribute
*/
int watch_fd_exec_02() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, "main.py", sizeof(files[0]->fn));
watch_file(kq, files[0]);
ctx.event.nlist = 1;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_ATTRIB, 0, files[0]);
watch_loop(kq, argv);
ok(ctx.event.nset == 1);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD));
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]);
ok(ctx.exec.count == 0);
ok(ctx.exit.count == 0);
return 0;
}
/*
* Write to three files at once
*/
int watch_fd_exec_03() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, "main.py", sizeof(files[0]->fn));
watch_file(kq, files[0]);
strlcpy(files[1]->fn, "util.py", sizeof(files[1]->fn));
watch_file(kq, files[1]);
strlcpy(files[2]->fn, "app.py", sizeof(files[2]->fn));
watch_file(kq, files[2]);
ctx.event.nlist = 3;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]);
EV_SET(&ctx.event.List[1], files[1]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[1]);
EV_SET(&ctx.event.List[2], files[1]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[2]);
watch_loop(kq, argv);
ok(strcmp(leading_edge->fn, "main.py") == 0);
ok(ctx.event.nset == 3);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].data == 0);
ok(ctx.event.Set[0].udata == files[0]->fn);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "prog") == 0);
ok(strcmp(ctx.exec.argv[0], "prog") == 0);
ok(strcmp(ctx.exec.argv[1], "arg1") == 0);
ok(strcmp(ctx.exec.argv[2], "arg2") == 0);
ok(ctx.exit.count == 0);
return 0;
}
/*
* Write to a file and then remove it
*/
int watch_fd_exec_04() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, "arg1", sizeof(files[0]->fn));
watch_file(kq, files[0]);
ctx.event.nlist = 2;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]);
EV_SET(&ctx.event.List[1], files[0]->fd, EVFILT_VNODE, 0, NOTE_DELETE, 0, files[0]);
watch_loop(kq, argv);
ok(ctx.event.nset == 3);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]->fn);
ok(ctx.event.Set[1].ident);
ok(ctx.event.Set[1].filter == EVFILT_VNODE);
ok(ctx.event.Set[1].flags == EV_DELETE); /* remove */
ok(ctx.event.Set[1].fflags == (NOTE_ALL));
ok(ctx.event.Set[1].udata == files[0]->fn);
ok(ctx.event.Set[2].ident);
ok(ctx.event.Set[2].filter == EVFILT_VNODE);
ok(ctx.event.Set[2].flags == (EV_CLEAR|EV_ADD)); /* reopen */
ok(ctx.event.Set[2].fflags == (NOTE_ALL));
ok(ctx.event.Set[2].udata == files[0]->fn);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "prog") == 0);
ok(strcmp(ctx.exec.argv[0], "prog") == 0);
ok(strcmp(ctx.exec.argv[1], "arg1") == 0);
ok(strcmp(ctx.exec.argv[2], "arg2") == 0);
ok(ctx.exit.count == 0);
return 0;
}
/*
* Rename a file without removing it (e.g. Vim's backup option)
*/
int watch_fd_exec_05() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, "arg1", sizeof(files[0]->fn));
watch_file(kq, files[0]);
ctx.event.nlist = 1;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_RENAME, 0, files[0]);
watch_loop(kq, argv);
ok(ctx.event.nset == 3);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]->fn);
ok(ctx.event.Set[1].ident);
ok(ctx.event.Set[1].filter == EVFILT_VNODE);
ok(ctx.event.Set[1].flags == EV_DELETE); /* remove */
ok(ctx.event.Set[1].fflags == (NOTE_ALL));
ok(ctx.event.Set[1].udata == files[0]->fn);
ok(ctx.event.Set[2].ident);
ok(ctx.event.Set[2].filter == EVFILT_VNODE);
ok(ctx.event.Set[2].flags == (EV_CLEAR|EV_ADD)); /* reopen */
ok(ctx.event.Set[2].fflags == (NOTE_ALL));
ok(ctx.event.Set[2].udata == files[0]->fn);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "prog") == 0);
ok(strcmp(ctx.exec.argv[0], "prog") == 0);
ok(strcmp(ctx.exec.argv[1], "arg1") == 0);
ok(strcmp(ctx.exec.argv[2], "arg2") == 0);
ok(ctx.exit.count == 0);
return 0;
}
/*
* Add a file to a directory and wait for child to exit
*/
int watch_fd_exec_06() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, ".", sizeof(files[0]->fn));
files[0]->is_dir = 1;
files[0]->file_count = 1;
strlcpy(files[1]->fn, "run.sh", sizeof(files[0]->fn));
watch_file(kq, files[0]);
watch_file(kq, files[1]);
child_pid = 222;
dirwatch_opt = 1;
restart_opt = 1;
ctx.event.nlist = 1;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]);
watch_loop(kq, argv);
ok(ctx.event.nset == 2);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]->fn);
ok(ctx.event.Set[1].ident);
ok(ctx.event.Set[1].filter == EVFILT_VNODE);
ok(ctx.event.Set[1].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[1].fflags == (NOTE_ALL));
ok(ctx.event.Set[1].udata == files[1]->fn);
ok(ctx.exec.count == 0);
ok(ctx.wait.count == 1);
ok(ctx.wait.pid == 222);
ok(ctx.exit.count == 1);
return 0;
}
/*
* Add a file to a directory and write to a file
*/
int watch_fd_exec_07() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, ".", sizeof(files[0]->fn));
files[0]->is_dir = 1;
strlcpy(files[1]->fn, "run.sh", sizeof(files[0]->fn));
watch_file(kq, files[0]);
watch_file(kq, files[1]);
dirwatch_opt = 1;
ctx.event.nlist = 2;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]);
EV_SET(&ctx.event.List[1], files[1]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[1]);
watch_loop(kq, argv);
ok(ctx.event.nset == 2);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]->fn);
ok(ctx.event.Set[1].ident);
ok(ctx.event.Set[1].filter == EVFILT_VNODE);
ok(ctx.event.Set[1].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[1].fflags == (NOTE_ALL));
ok(ctx.event.Set[1].udata == files[1]->fn);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "prog") == 0);
ok(strcmp(ctx.exec.argv[0], "prog") == 0);
ok(strcmp(ctx.exec.argv[1], "arg1") == 0);
ok(strcmp(ctx.exec.argv[2], "arg2") == 0);
ok(ctx.exit.count == 1);
return 0;
}
/*
* Write to a file in directory watch mode
*/
int watch_fd_exec_08() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
dirwatch_opt = 1;
strlcpy(files[0]->fn, "src", sizeof(files[0]->fn));
files[0]->is_dir = 1;
watch_file(kq, files[0]);
strlcpy(files[1]->fn, "main.py", sizeof(files[1]->fn));
watch_file(kq, files[1]);
ctx.event.nlist = 2;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[0]);
EV_SET(&ctx.event.List[1], files[1]->fd, EVFILT_VNODE, 0, NOTE_WRITE, 0, files[1]);
watch_loop(kq, argv);
ok(ctx.event.nset == 2);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(ctx.exit.count == 1);
ok(strcmp(leading_edge->fn, "main.py") == 0);
return 0;
}
/*
* Make a file executable
*/
int watch_fd_exec_09() {
int kq = kqueue();
static char *argv[] = { "prog", "arg1", "arg2", NULL };
postpone_opt = 1;
strlcpy(files[0]->fn, "main.py", sizeof(files[0]->fn));
watch_file(kq, files[0]);
files[0]->mode = S_IFREG | S_IRUSR | S_IXUSR;
ctx.event.nlist = 1;
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_ATTRIB, 0, files[0]);
watch_loop(kq, argv);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "prog") == 0);
ok(strcmp(ctx.exec.argv[0], "prog") == 0);
ok(strcmp(ctx.exec.argv[1], "arg1") == 0);
ok(strcmp(ctx.exec.argv[2], "arg2") == 0);
ok(ctx.exit.count == 0);
return 0;
}
/*
* In submit mode only the first argment is used
*/
int watch_fd_shell_01() {
int kq = kqueue();
char *argv[] = { "ruby main.rb", NULL };
shell_opt = 1;
strlcpy(files[0]->fn, "main.rb", sizeof(files[0]->fn));
watch_file(kq, files[0]);
ctx.event.nlist = 0;
watch_loop(kq, argv);
ok(strcmp(leading_edge->fn, "main.rb") == 0);
ok(ctx.event.nset == 1);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "/bin/Xsh") == 0); /* FIXME */
ok(strcmp(ctx.exec.argv[0], "/bin/Xsh") == 0);
ok(strcmp(ctx.exec.argv[1], "-c") == 0);
ok(strcmp(ctx.exec.argv[2], "ruby main.rb") == 0);
return 0;
}
/*
* Parse command line arguments up to but not including the utility to execute
*/
int set_options_01() {
int argv_offset;
char *argv[] = { "entr", "ruby", "test1.rb", NULL };
argv_offset = set_options(argv);
ok(argv_offset == 1);
ok(restart_opt == 0);
ok(clear_opt == 0);
ok(dirwatch_opt == 0);
return 0;
}
/*
* Parse command line arguments for restart mode
*/
int set_options_02() {
int argv_offset;
char *argv[] = { "entr", "-r", "ruby", "test2.rb", NULL };
argv_offset = set_options(argv);
ok(argv_offset == 2);
ok(restart_opt == 1);
ok(clear_opt == 0);
ok(dirwatch_opt == 0);
ok(postpone_opt == 0);
return 0;
}
/*
* Parse command line arguments with the clear option
*/
int set_options_03() {
int argv_offset;
char *argv[] = { "entr", "-c", "ruby", "test3.rb", NULL };
argv_offset = set_options(argv);
ok(argv_offset == 2);
ok(restart_opt == 0);
ok(clear_opt == 1);
ok(dirwatch_opt == 0);
ok(postpone_opt == 0);
return 0;
}
/*
* Parse command line arguments with the directory watch option
*/
int set_options_04() {
int argv_offset;
char *argv[] = { "entr", "-d", "ruby", "test4.rb", NULL };
argv_offset = set_options(argv);
ok(argv_offset == 2);
ok(restart_opt == 0);
ok(clear_opt == 0);
ok(dirwatch_opt == 1);
ok(postpone_opt == 0);
return 0;
}
/*
* Ensure that command line arguments are not confused with utility arguments
*/
int set_options_05() {
int argv_offset;
char *argv[] = { "entr", "ls", "-r", "-c", NULL };
argv_offset = set_options(argv);
ok(argv_offset == 1);
ok(restart_opt == 0);
ok(clear_opt == 0);
ok(shell_opt == 0);
return 0;
}
/*
* Run arguments in a shell
*/
int set_options_06() {
int argv_offset;
char *argv[] = { "entr", "-s", "make test", NULL };
argv_offset = set_options(argv);
ok(argv_offset == 2);
ok(restart_opt == 0);
ok(clear_opt == 0);
ok(shell_opt == 1);
return 0;
}
/*
* All command must be formatted as a single argument when run in a shell
*/
int set_options_07() {
int argv_offset;
char *argv[] = { "entr", "-s", "make", "test", NULL };
argv_offset = set_options(argv);
ok(argv_offset == 2);
ok(shell_opt == 1);
ok(ctx.exit.count == 1);
return 0;
}
/*
* In restart mode the first action should be to start the server
*/
int watch_fd_restart_01() {
int kq = kqueue();
char *argv[] = { "ruby", "main.rb", NULL };
restart_opt = 1;
strlcpy(files[0]->fn, "main.rb", sizeof(files[0]->fn));
watch_file(kq, files[0]);
ctx.event.nlist = 0;
watch_loop(kq, argv);
ok(strcmp(leading_edge->fn, "main.rb") == 0);
ok(ctx.event.nset == 1);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "ruby") == 0);
ok(strcmp(ctx.exec.argv[0], "ruby") == 0);
ok(strcmp(ctx.exec.argv[1], "main.rb") == 0);
return 0;
}
/*
* Extending a file while in restart mode should result in start-kill-restart
*/
int watch_fd_restart_02() {
int kq = kqueue();
char *argv[] = { "ruby", "main.rb", NULL };
restart_opt = 1;
strlcpy(files[0]->fn, "main.rb", sizeof(files[0]->fn));
watch_file(kq, files[0]);
child_pid = 222;
ctx.event.nlist = 0;
watch_loop(kq, argv);
ok(ctx.event.nset == 1);
ok(ctx.event.Set[0].ident);
ok(ctx.event.Set[0].filter == EVFILT_VNODE);
ok(ctx.event.Set[0].flags == (EV_CLEAR|EV_ADD)); /* open */
ok(ctx.event.Set[0].fflags == (NOTE_ALL));
ok(ctx.event.Set[0].udata == files[0]);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "ruby") == 0);
ok(strcmp(ctx.exec.argv[0], "ruby") == 0);
ok(strcmp(ctx.exec.argv[1], "main.rb") == 0);
EV_SET(&ctx.event.List[0], files[0]->fd, EVFILT_VNODE, 0, NOTE_EXTEND, 0, files[0]);
ctx.event.nlist = 0;
watch_loop(kq, argv);
ok(ctx.signal.count == 1);
ok(ctx.signal.pid == 222);
ok(ctx.signal.sig == 15);
ok(ctx.exec.count == 2);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "ruby") == 0);
ok(strcmp(ctx.exec.argv[0], "ruby") == 0);
ok(strcmp(ctx.exec.argv[1], "main.rb") == 0);
return 0;
}
/*
* Substitue '/_' with the first file that leading_edge
*/
int run_utility_01() {
static char *argv[] = { "psql", "-f", "/_", NULL };
char input[] = "one.sql\ntwo.sql";
FILE *fake;
fake = fmemopen(input, strlen(input), "r");
(void) process_input(fake, files, 3);
leading_edge = files[1];
run_utility(argv);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "psql") == 0);
ok(strcmp(ctx.exec.argv[0], "psql") == 0);
ok(strcmp(ctx.exec.argv[1], "-f") == 0);
ok(strcmp(ctx.exec.argv[2], "/home/user/two.sql") == 0);
return 0;
}
/*
* Substitue only the first occurance of '/_'
*/
int run_utility_02() {
static char *argv[] = { "/_", "/_", NULL };
char input[] = "one.sh\ntwo.sh";
FILE *fake;
fake = fmemopen(input, strlen(input), "r");
(void) process_input(fake, files, 3);
leading_edge = files[0];
run_utility(argv);
ok(ctx.exec.count == 1);
ok(ctx.exec.file != 0);
ok(strcmp(ctx.exec.file, "/home/user/one.sh") == 0);
ok(strcmp(ctx.exec.argv[0], "/home/user/one.sh") == 0);
ok(strcmp(ctx.exec.argv[1], "/_") == 0);
return 0;
}
/*
* main
*/
int test_main(int argc, char *argv[]) {
signal(SIGSEGV, sighandler);
/* set up pointers to test doubles */
xstat = fake_stat;
xkevent = fake_kevent;
xkillpg = fake_killpg;
xwaitpid = fake_waitpid;
xexecvp = fake_execvp;
xfork = fake_fork;
xopen = fake_open;
xrealpath = fake_realpath;
xfree = fake_free;
xerrx = fake_errx;
xwarnx = fake_warnx;
xlist_dir = fake_list_dir;
xtcsetattr = fake_tcsetattr;
/* all tests */
run(process_input_01);
run(process_input_02);
run(process_input_03);
run(process_input_04);
run(watch_fd_exec_01);
run(watch_fd_exec_02);
run(watch_fd_exec_03);
run(watch_fd_exec_04);
run(watch_fd_exec_05);
run(watch_fd_exec_06);
run(watch_fd_exec_07);
run(watch_fd_exec_08);
run(watch_fd_exec_09);
run(watch_fd_shell_01);
run(set_options_01);
run(set_options_02);
run(set_options_03);
run(set_options_04);
run(set_options_05);
run(set_options_06);
run(set_options_07);
run(watch_fd_restart_01);
run(watch_fd_restart_02);
run(run_utility_01);
run(run_utility_02);
/* TODO: find out how we broke stdout */
fprintf(stderr, "%d of %d tests PASSED\n", tests_run-failures, tests_run);
return failures;
}
int (*test_runner_main)(int argc, char **argv) = test_main;