Hello community, here is the log from the commit of package entr for openSUSE:Factory checked in at 2017-11-08 15:10:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/entr (Old) and /work/SRC/openSUSE:Factory/.entr.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "entr" Wed Nov 8 15:10:41 2017 rev:2 rq:539446 version:3.9 Changes: -------- --- /work/SRC/openSUSE:Factory/entr/entr.changes 2017-07-10 11:07:33.162868728 +0200 +++ /work/SRC/openSUSE:Factory/.entr.new/entr.changes 2017-11-08 15:10:46.460644641 +0100 @@ -1,0 +2,18 @@ +Tue Nov 7 03:00:10 UTC 2017 - aavind...@gmail.com + +- Bump to 3.9 + * Fix use of poll(2) to avoid possible busy-loop on Linux + * Disable keyboard input if STDIN read fails +- Includes changes from 3.8 + * Run the utility if spacebar is pressed + * 'q' for quit +- Includes changes from 3.7 + * Terminate subprocess in restart mode if a file under watch + disappears + * Allow NOTE_ATTRIB to set '/_' only if file mode changes + * New '-s' option executes commands using $SHELL -c + * Print usage and exit if input is from tty instead of pipe +- Switch to bz2 download (smaller filesize) +- Cleanup with spec-cleaner + +------------------------------------------------------------------- Old: ---- entr-3.6.tar.gz New: ---- entr-3.9.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ entr.spec ++++++ --- /var/tmp/diff_new_pack.joH7Je/_old 2017-11-08 15:10:47.240616166 +0100 +++ /var/tmp/diff_new_pack.joH7Je/_new 2017-11-08 15:10:47.244616020 +0100 @@ -1,7 +1,7 @@ # # spec file for package entr # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. # Copyright (c) 2016 Daniel Lichtenberger # # All modifications and additions to the file contributed by third parties @@ -18,13 +18,13 @@ Name: entr -Version: 3.6 +Version: 3.9 Release: 0 Summary: A utility for running arbitrary commands when files change License: ISC Group: Development/Tools/Other Url: https://bitbucket.org/eradman/entr -Source0: https://bitbucket.org/eradman/entr/get/entr-%{version}.tar.gz +Source0: https://bitbucket.org/eradman/entr/get/entr-%{version}.tar.bz2 Source1: LICENSE %description @@ -50,7 +50,6 @@ make %{?_smp_mflags} test %files -%defattr(-,root,root,-) %doc LICENSE NEWS README.md %{_bindir}/%{name} %{_mandir}/man1/%{name}.1%{ext_man} ++++++ entr-3.6.tar.gz -> entr-3.9.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/.hg_archival.txt new/eradman-entr-332fd96a324a/.hg_archival.txt --- old/eradman-entr-c15b0be493fc/.hg_archival.txt 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/.hg_archival.txt 2017-09-19 20:58:52.000000000 +0200 @@ -1,4 +1,4 @@ repo: 49108c05f40cf0f2abceacff753c2df1cd22e1ec -node: c15b0be493fc70e6e13c2a7fa451486aa1bc71a3 +node: 332fd96a324a7771b0d2252027c44eb972fd38dc branch: default -tag: entr-3.6 +tag: entr-3.9 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/.hgtags new/eradman-entr-332fd96a324a/.hgtags --- old/eradman-entr-c15b0be493fc/.hgtags 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/.hgtags 2017-09-19 20:58:52.000000000 +0200 @@ -110,3 +110,6 @@ 0000000000000000000000000000000000000000 entr-3.4 5321b01c9dabf8cf6d5bacd10c56b126215a1fe3 entr-3.4 6977480460701561ce674ff03173bd9a8722fee7 entr-3.5 +c15b0be493fc70e6e13c2a7fa451486aa1bc71a3 entr-3.6 +c5b62bde107d1b30af43fccc0398a5694a9b6b67 entr-3.7 +592856d50559bc48fef4b27cc5c2bc4eacb69428 entr-3.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/Makefile.bsd new/eradman-entr-332fd96a324a/Makefile.bsd --- old/eradman-entr-c15b0be493fc/Makefile.bsd 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/Makefile.bsd 2017-09-19 20:58:52.000000000 +0200 @@ -1,6 +1,6 @@ PREFIX ?= /usr/local MANPREFIX ?= ${PREFIX}/man -RELEASE = 3.6 +RELEASE = 3.9 CPPFLAGS += -DRELEASE=\"${RELEASE}\" all: versioncheck entr diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/NEWS new/eradman-entr-332fd96a324a/NEWS --- old/eradman-entr-c15b0be493fc/NEWS 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/NEWS 2017-09-19 20:58:52.000000000 +0200 @@ -1,5 +1,22 @@ = Release History +== 3.9: September 19, 2017 + + - Fix use of poll(2) to avoid possible busy-loop on Linux + - Disable keyboard input if reading STDIN fails + +== 3.8: August 11, 2017 + + - Run the utility if the spacebar is pressed + - 'q' for quit + +== 3.7: February 27, 2017 + + - Terminate subprocess in restart mode if a file under watch disappears + - Allow NOTE_ATTRIB to set '/_' only if file mode changes + - New '-s' option executes commands using $SHELL -c + - Print usage and exit if input is from a terminal instead of a pipe + == 3.6: July 01, 2016 - Do not print warning if _TTY_PATH cannot be opened (for chroot, docker, ...) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/README.md new/eradman-entr-332fd96a324a/README.md --- old/eradman-entr-c15b0be493fc/README.md 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/README.md 2017-09-19 20:58:52.000000000 +0200 @@ -1,13 +1,12 @@ Event Notify Test Runner ======================== -A utility for running arbitrary commands when files change. Uses -[kqueue(2)][kqueue_2] or [inotify(7)][inotify_7] to avoid polling. `entr` was -written to make rapid feedback and automated testing natural and completely -ordinary. +A utility for running arbitrary commands when files change. Uses [kqueue(2)] or +[inotify(7)] to avoid polling. `entr` was written to make rapid feedback and +automated testing natural and completely ordinary. -Installation - BSD, Mac OS, and Linux -------------------------------------- +Source Installation - BSD, Mac OS, and Linux +-------------------------------------------- ./configure make test @@ -15,23 +14,18 @@ To see available build options run `./configure -h` -Installation - Mac OS/Homebrew ------------------------------- +Binary Installation +------------------- - brew install entr +The following distributions provide `entr` as part of their main +package repository: -Installation - Ports --------------------- +* OpenBSD and FreeBSD +* Mac OS using Homebrew or MacPorts +* Debian, Ubuntu, Fedora, and Alpine Linux -Available in OpenBSD ports, FreeBSD ports, and pkgsrc under `sysutils/entr`. - -Installation - Debian ---------------------- - - apt-get install entr - -Examples from `man entr` ------------------------- +Man Page Examples +----------------- Rebuild a project if source files change, limiting output to the first 20 lines: @@ -41,6 +35,10 @@ $ ls *.js | entr -r node app.js +Launch and auto-reload a node.js server as a background task: + + $ (ls *.js | entr -r node app.js &) + Clear the screen and run a query after the SQL script is updated: $ echo my.sql | entr -p psql -f /_ @@ -53,15 +51,15 @@ ---- A release history as well as features in the upcoming release are covered in the -[NEWS][NEWS] file. +[NEWS] file. License ------- -Source is under and ISC-style license. See the [LICENSE][LICENSE] file for more -detailed information on the license used for compatibility libraries. +Source is under and ISC-style license. See the [LICENSE] file for more detailed +information on the license used for compatibility libraries. -[kqueue_2]: http://www.openbsd.org/cgi-bin/man.cgi?query=kqueue&manpath=OpenBSD+Current&format=html -[inotify_7]: http://man.he.net/?section=all&topic=inotify +[kqueue(2)]: http://man.openbsd.org/OpenBSD-current/man2/kqueue.2 +[inotify(7)]: http://man.he.net/?section=all&topic=inotify [NEWS]: http://www.bitbucket.org/eradman/entr/src/default/NEWS [LICENSE]: http://www.bitbucket.org/eradman/entr/src/default/LICENSE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/entr.1 new/eradman-entr-332fd96a324a/entr.1 --- old/eradman-entr-c15b0be493fc/entr.1 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/entr.1 2017-09-19 20:58:52.000000000 +0200 @@ -13,7 +13,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd May 17, 2016 +.Dd September 14, 2017 .Dt ENTR 1 .Os .Sh NAME @@ -21,10 +21,9 @@ .Nd run arbitrary commands when files change .Sh SYNOPSIS .Nm -.Op Fl cdpr +.Op Fl cdprs .Ar utility -.Op Ar argument ... -.Op Ar /_ +.Op Ar argument /_ ... .Sh DESCRIPTION A list of files provided on the standard input and the .Ar utility @@ -64,21 +63,47 @@ waits for the .Ar utility to exit to ensure that resources such as sockets have been closed. +.It Fl s +Evaluate the first argument using the interpreter specified by the +.Ev SHELL +environment variable. +When this flag is set, the name of the shell and exit code is printed after each +invocation. .El .Pp -The first occurrence of +The first argument named .Ar /_ -on the command line will be replaced with the absolute path of the first file that was modified. +is replaced with the absolute path of the first file to trigger an event. If the restart option is used the first file under watch is treated as the default. +.Sh COMMANDS +.Nm +listens for keyboard input and responds to the following commands: +.Bl -tag -width Ic +.It SPACE +Execute the utility immediately. +If the +.Ql -r +option is set this will terminate and restart the child process as if a file +change event had occurred. +.It q +Quit; equivalent pressing Ctrl\-C. +.El .Sh ENVIRONMENT If .Ev PAGER is undefined, -.Nm entr +.Nm will assign .Pa /bin/cat to prevent interactive utilities from waiting for keyboard input if output does not fit on the screen. +.Pp +If +.Ev SHELL +is undefined, +.Nm entr +will use +.Pa /bin/sh . .Sh EXIT STATUS The .Nm @@ -98,12 +123,16 @@ .Sh EXAMPLES Rebuild a project if source files change, limiting output to the first 20 lines: .Pp -.Dl $ find src/ | entr sh -c 'make | head -n 20' +.Dl $ find src/ | entr -s 'make | head -n 20' .Pp Launch and auto-reload a node.js server: .Pp .Dl $ ls *.js | entr -r node app.js .Pp +Launch and auto-reload a node.js server as a background task: +.Pp +.Dl $ (ls *.js | entr -r node app.js &) +.Pp Clear the screen and run a query after the SQL script is updated: .Pp .Dl $ echo my.sql | entr -p psql -f /_ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/entr.c new/eradman-entr-332fd96a324a/entr.c --- old/eradman-entr-c15b0be493fc/entr.c 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/entr.c 2017-09-19 20:58:52.000000000 +0200 @@ -33,6 +33,7 @@ #include <string.h> #include <time.h> #include <unistd.h> +#include <termios.h> #include "missing/compat.h" @@ -63,6 +64,7 @@ void (*xwarnx)(const char *, ...); void (*xerrx)(int, const char *, ...); int (*xlist_dir)(char *); +int (*xtcsetattr)(int fd, int action, const struct termios *tp); /* globals */ @@ -75,6 +77,7 @@ int dirwatch_opt; int restart_opt; int postpone_opt; +int shell_opt; /* forwards */ @@ -102,6 +105,7 @@ short argv_index; int n_files; int i; + struct kevent evSet; if ((*test_runner_main)) return(test_runner_main(argc, argv)); @@ -119,6 +123,7 @@ xwarnx = warnx; xerrx = errx; xlist_dir = list_dir; + xtcsetattr = tcsetattr; /* call usage() if no command is supplied */ if (argc < 2) usage(); @@ -142,12 +147,19 @@ /* prevent interactive utilities from paging output */ setenv("PAGER", "/bin/cat", 0); + /* ensure a shell is available to use */ + setenv("SHELL", "/bin/sh", 0); + /* sequential scan may depend on a 0 at the end */ files = calloc(rl.rlim_cur+1, sizeof(WatchFile *)); if ((kq = kqueue()) == -1) err(1, "cannot create kqueue"); + /* expect file list from a pipe */ + if (isatty(fileno(stdin))) + usage(); + /* read input and populate watch list, skipping non-regular files */ n_files = process_input(stdin, files, rl.rlim_cur); if (n_files == 0) @@ -167,6 +179,11 @@ close(ttyfd); } + /* Use keyboard input as a trigger */ + EV_SET(&evSet, STDIN_FILENO, EVFILT_READ, EV_ADD, NOTE_LOWAT, 1, NULL); + if (xkevent(kq, &evSet, 1, NULL, 0, NULL) == -1) + err(1, "failed to register stdin"); + watch_loop(kq, argv+argv_index); return 1; } @@ -176,7 +193,7 @@ void usage() { fprintf(stderr, "release: %s\n", RELEASE); - fprintf(stderr, "usage: entr [-cdpr] utility [args, [/_], ...] < filenames\n"); + fprintf(stderr, "usage: entr [-cdprs] utility [argument [/_] ...] < filenames\n"); exit(1); } @@ -281,7 +298,7 @@ /* read arguments until we reach a command */ for (argc=1; argv[argc] != 0 && argv[argc][0] == '-'; argc++); - while ((ch = getopt(argc, argv, "cdpr")) != -1) { + while ((ch = getopt(argc, argv, "cdprs")) != -1) { switch (ch) { case 'c': clear_opt = 1; @@ -295,13 +312,17 @@ case 'r': restart_opt = 1; break; + case 's': + shell_opt = 1; + break; default: usage(); } } - /* no command to run */ if (argv[optind] == '\0') usage(); + if ((shell_opt == 1) && (argv[optind+1] != '\0')) + xerrx(1, "-s requires commands to be formatted as a single argument"); return optind; } @@ -322,21 +343,34 @@ if (restart_opt == 1) terminate_utility(); - /* clone argv on each invocation to make the implementation of more - * complex subsitution rules possible and easy - */ - for (argc=0; argv[argc]; argc++); - arg_buf = malloc(ARG_MAX); - new_argv = calloc(argc+1, sizeof(char *)); - for (m=0, i=0, p=arg_buf; i<argc; i++) { - new_argv[i] = p; - if ((m < 1) && (strcmp(argv[i], "/_")) == 0) { - p += strlen(xrealpath(leading_edge->fn, p)); - m++; + if (shell_opt == 1) { + /* run argv[1] with a shell using the leading edge as $0 */ + argc = 4; + arg_buf = malloc(ARG_MAX); + new_argv = calloc(argc+1, sizeof(char *)); + (void) xrealpath(leading_edge->fn, arg_buf); + new_argv[0] = getenv("SHELL"); + new_argv[1] = "-c"; + new_argv[2] = argv[0]; + new_argv[3] = arg_buf; + } + else { + /* clone argv on each invocation to make the implementation of more + * complex subsitution rules possible and easy + */ + for (argc=0; argv[argc]; argc++); + arg_buf = malloc(ARG_MAX); + new_argv = calloc(argc+1, sizeof(char *)); + for (m=0, i=0, p=arg_buf; i<argc; i++) { + new_argv[i] = p; + if ((m < 1) && (strcmp(argv[i], "/_")) == 0) { + p += strlen(xrealpath(leading_edge->fn, p)); + m++; + } + else + p += strlcpy(p, argv[i], ARG_MAX - (p - arg_buf)); + p++; } - else - p += strlcpy(p, argv[i], ARG_MAX - (p - arg_buf)); - p++; } pid = xfork(); @@ -360,8 +394,12 @@ } child_pid = pid; - if (restart_opt == 0) + if (restart_opt == 0) { xwaitpid(pid, &status, 0); + if (shell_opt == 1) + fprintf(stdout, "%s returned exit code %d\n", + basename(getenv("SHELL")), WEXITSTATUS(status)); + } xfree(arg_buf); xfree(new_argv); @@ -386,8 +424,10 @@ if (file->fd == -1) nanosleep(&delay, NULL); else break; } - if (file->fd == -1) + if (file->fd == -1) { + terminate_utility(); err(1, "cannot open '%s'", file->fn); + } EV_SET(&evSet, file->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_ALL, 0, file); @@ -445,37 +485,62 @@ int dir_modified = 0; int leading_edge_set = 0; struct stat sb; + struct termios canonical_tty, character_tty; + char c; leading_edge = files[0]; /* default */ if (postpone_opt == 0) run_utility(argv); + /* disabling/restore line buffering and local echo */ + tcgetattr(STDIN_FILENO, &canonical_tty); + character_tty = canonical_tty; + character_tty.c_lflag &= ~(ICANON|ECHO); + main: - if ((reopen_only == 1) || (collate_only == 1)) + xtcsetattr(STDIN_FILENO, TCSADRAIN, &character_tty); + if ((reopen_only == 1) || (collate_only == 1)) { nev = xkevent(kq, NULL, 0, evList, 32, &evTimeout); + } else { nev = xkevent(kq, NULL, 0, evList, 32, NULL); dir_modified = 0; } + + if (nev == -1) + err(1, "kevent failed"); + /* escape for test runner */ if ((nev == -2) && (collate_only == 0)) return; for (i=0; i<nev; i++) { + if (evList[i].filter == EVFILT_READ) { + if (read(STDIN_FILENO, &c, 1) < 1) { + EV_SET(&evSet, STDIN_FILENO, EVFILT_READ, + EV_DELETE, NOTE_LOWAT, 0, NULL); + if (xkevent(kq, &evSet, 1, NULL, 0, NULL) == -1) + err(1, "failed to remove READ event"); + } + else { + if (c == ' ') + do_exec = 1; + if (c == 'q') + kill(getpid(), SIGINT); + } + } if (evList[i].filter != EVFILT_VNODE) continue; file = (WatchFile *)evList[i].udata; if (file->is_dir == 1) dir_modified += compare_dir_contents(file); - else if (leading_edge_set == 0) - if ((reopen_only == 0) && (collate_only == 0)) { - leading_edge = file; - leading_edge_set = 1; - } } + xtcsetattr(0, TCSADRAIN, &canonical_tty); collate_only = 0; for (i=0; i<nev; i++) { + if (evList[i].filter != EVFILT_VNODE) + continue; file = (WatchFile *)evList[i].udata; if (evList[i].fflags & NOTE_DELETE || evList[i].fflags & NOTE_RENAME) { @@ -495,6 +560,8 @@ } for (i=0; i<nev && reopen_only == 0; i++) { + if (evList[i].filter != EVFILT_VNODE) + continue; file = (WatchFile *)evList[i].udata; if ((file->is_dir == 1) && (dir_modified == 0)) continue; @@ -513,6 +580,12 @@ do_exec = 1; file->mode = sb.st_mode; } + else if (evList[i].fflags & NOTE_ATTRIB) + continue; + if ((file->is_dir == 0) && (leading_edge_set == 0)) { + leading_edge = file; + leading_edge_set = 1; + } } if (collate_only == 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/entr_spec.c new/eradman-entr-332fd96a324a/entr_spec.c --- old/eradman-entr-c15b0be493fc/entr_spec.c 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/entr_spec.c 2017-09-19 20:58:52.000000000 +0200 @@ -84,12 +84,15 @@ 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) { @@ -139,6 +142,11 @@ fake_free(void *ptr) { } +int +fake_tcsetattr(int fd, int action, const struct termios *tp) { + return 0; +} + /* mock objects */ /* @@ -639,6 +647,37 @@ } /* + * 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() { @@ -717,6 +756,38 @@ 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; } @@ -856,6 +927,7 @@ xerrx = fake_errx; xwarnx = fake_warnx; xlist_dir = fake_list_dir; + xtcsetattr = fake_tcsetattr; /* all tests */ run(process_input_01); @@ -871,11 +943,14 @@ 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); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/missing/kqueue_inotify.c new/eradman-entr-332fd96a324a/missing/kqueue_inotify.c --- old/eradman-entr-c15b0be493fc/missing/kqueue_inotify.c 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/missing/kqueue_inotify.c 2017-09-19 20:58:52.000000000 +0200 @@ -17,10 +17,12 @@ #include <sys/event.h> #include <sys/types.h> +#include <err.h> #include <errno.h> #include <limits.h> #include <poll.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> @@ -32,6 +34,7 @@ /* globals */ extern WatchFile **files; +int read_stdin; /* forwards */ @@ -54,7 +57,7 @@ #define EVENT_SIZE (sizeof (struct inotify_event)) #define EVENT_BUF_LEN (32 * (EVENT_SIZE + 16)) -#define IN_ALL IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MODIFY|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE +#define IN_ALL IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE /* * Conveniently inotify and kqueue ids both have the type `int` @@ -65,8 +68,9 @@ } /* - * Emulate kqueue(2). Only the flags used in entr.c are considered - * Returns the number of eventlist structs filled by this call + * Emulate kqueue(2). Only monitors STDIN for EVFILT_READ and only the + * EVFILT_VNODE flags used in entr.c are considered. Returns the number of + * eventlist structs filled by this call */ int kevent(int kq, const struct kevent *changelist, int nchanges, struct @@ -81,14 +85,31 @@ u_int fflags; const struct kevent *kev; int ignored; - struct pollfd pfd; - struct stat sb; + struct pollfd *pfd; + int nfds; + + pfd = calloc(2, sizeof(struct pollfd)); + pfd[0].fd = kq; + pfd[0].events = POLLIN; + pfd[1].fd = STDIN_FILENO; + pfd[1].events = POLLIN; if (nchanges > 0) { ignored = 0; for (n=0; n<nchanges; n++) { kev = changelist + (sizeof(struct kevent)*n); file = (WatchFile *)kev->udata; + + if (kev->filter == EVFILT_READ) { + if (kev->flags & EV_ADD) + read_stdin = 1; + if (kev->flags & EV_DELETE) + read_stdin = 0; + } + + if (kev->filter != EVFILT_VNODE) + continue; + if (kev->flags & EV_DELETE) { inotify_rm_watch(kq /* ifd */, kev->ident); file->fd = -1; /* invalidate */ @@ -107,50 +128,74 @@ return nchanges - ignored; } - pfd.fd = kq; - pfd.events = POLLIN; - if (timeout != 0 && (poll(&pfd, 1, timeout->tv_nsec/1000000) == 0)) - return 0; + if (read_stdin == 1) + nfds = 2; /* inotify and stdin */ + else + nfds = 1; /* inotify */ + if (timeout == NULL) + poll(pfd, nfds, -1); + else + poll(pfd, nfds, timeout->tv_nsec/1000000); n = 0; do { - pos = 0; - len = read(kq /* ifd */, &buf, EVENT_BUF_LEN); - if (len < 0) { - /* SA_RESTART doesn't work for inotify fds */ - if (errno == EINTR) - continue; - else - perror("read"); + if (pfd[0].revents & (POLLERR|POLLNVAL)) + errx(1, "bad fd %d", pfd[0].fd); + if (pfd[0].revents & POLLIN) { + pos = 0; + len = read(kq /* ifd */, &buf, EVENT_BUF_LEN); + if (len < 0) { + /* SA_RESTART doesn't work for inotify fds */ + if (errno == EINTR) + continue; + else + errx(1, "read of fd %d failed", pfd[0].fd); + } + while ((pos < len) && (n < nevents)) { + iev = (struct inotify_event *) &buf[pos]; + pos += EVENT_SIZE + iev->len; + + /* convert iev->mask; to comparable kqueue flags */ + fflags = 0; + if (iev->mask & IN_DELETE_SELF) fflags |= NOTE_DELETE; + if (iev->mask & IN_CLOSE_WRITE) fflags |= NOTE_WRITE; + if (iev->mask & IN_CREATE) fflags |= NOTE_WRITE; + if (iev->mask & IN_MOVE_SELF) fflags |= NOTE_RENAME; + if (iev->mask & IN_ATTRIB) fflags |= NOTE_ATTRIB; + if (fflags == 0) continue; + + /* merge events if we're not acting on a new file descriptor */ + if ((n > 0) && (eventlist[n-1].ident == iev->wd)) + fflags |= eventlist[--n].fflags; + + eventlist[n].ident = iev->wd; + eventlist[n].filter = EVFILT_VNODE; + eventlist[n].flags = 0; + eventlist[n].fflags = fflags; + eventlist[n].data = 0; + eventlist[n].udata = file_by_descriptor(iev->wd); + if (eventlist[n].udata) + n++; + } } - while ((pos < len) && (n < nevents)) { - iev = (struct inotify_event *) &buf[pos]; - pos += EVENT_SIZE + iev->len; - - /* convert iev->mask; to comparable kqueue flags */ - fflags = 0; - if (iev->mask & IN_DELETE_SELF) fflags |= NOTE_DELETE; - if (iev->mask & IN_CLOSE_WRITE) fflags |= NOTE_WRITE; - if (iev->mask & IN_CREATE) fflags |= NOTE_WRITE; - if (iev->mask & IN_MOVE_SELF) fflags |= NOTE_RENAME; - if (iev->mask & IN_ATTRIB) fflags |= NOTE_ATTRIB; - if (fflags == 0) continue; - - /* merge events if we're not acting on a new file descriptor */ - if ((n > 0) && (eventlist[n-1].ident == iev->wd)) - fflags |= eventlist[--n].fflags; - - eventlist[n].ident = iev->wd; - eventlist[n].filter = EVFILT_VNODE; - eventlist[n].flags = 0; - eventlist[n].fflags = fflags; - eventlist[n].data = 0; - eventlist[n].udata = file_by_descriptor(iev->wd); - if (eventlist[n].udata) + if (read_stdin == 1) { + if (pfd[1].revents & (POLLERR|POLLNVAL)) + errx(1, "bad fd %d", pfd[1].fd); + else if (pfd[1].revents & (POLLHUP|POLLIN)) { + fflags = 0; + eventlist[n].ident = pfd[1].fd; + eventlist[n].filter = EVFILT_READ; + eventlist[n].flags = 0; + eventlist[n].fflags = fflags; + eventlist[n].data = 0; + eventlist[n].udata = NULL; n++; + break; + } } } - while ((poll(&pfd, 1, 50) > 0)); - + while ((poll(pfd, nfds, 50) > 0)); + + (void) free(pfd); return n; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/missing/sys/event.h new/eradman-entr-332fd96a324a/missing/sys/event.h --- old/eradman-entr-c15b0be493fc/missing/sys/event.h 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/missing/sys/event.h 2017-09-19 20:58:52.000000000 +0200 @@ -27,6 +27,7 @@ #include <sys/types.h> #include <sys/time.h> +#define EVFILT_READ (-1) #define EVFILT_VNODE (-4) /* attached to vnodes */ /* actions */ @@ -40,6 +41,11 @@ #define EV_CLEAR 0x0020 /* clear event state after reporting */ /* + * data/hint flags for EVFILT_{READ|WRITE}, shared with userspace + */ +#define NOTE_LOWAT 0x0001 /* low water mark */ + +/* * data/hint flags for EVFILT_VNODE, shared with userspace */ #define NOTE_DELETE 0x0001 /* vnode was removed */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eradman-entr-c15b0be493fc/system_test.sh new/eradman-entr-332fd96a324a/system_test.sh --- old/eradman-entr-c15b0be493fc/system_test.sh 2016-07-01 16:20:59.000000000 +0200 +++ new/eradman-entr-332fd96a324a/system_test.sh 2017-09-19 20:58:52.000000000 +0200 @@ -17,7 +17,7 @@ # test runner trap 'printf "$0: exit code $? on line $LINENO\nFAIL: $this\n"; exit 1' ERR \ - 2> /dev/null || exec bash $0 "$@" + 2> /dev/null || exec bash $0 "$@" typeset -i tests=0 function try { let tests+=1; this="$1"; } @@ -29,11 +29,17 @@ function zz { sleep 0.25; } function setup { rm -f $tmp/*; touch $tmp/file{1,2}; zz; } -tmp=$(cd $(mktemp -dt entr_system_test.XXXXXXXXXX); pwd -P) +tmp=$(cd $(mktemp -d ${TMPDIR:-/tmp}/entr-system-test-XXXXXX); pwd -P) +tsession=$(basename $tmp) + +clear_tty='test -t 0 && stty echo icanon' +clear_tmux='tmux kill-session -t $tsession 2>/dev/null || true' +clear_tmp='rm -r $tmp' +trap "$clear_tty; $clear_tmux; $clear_tmp" EXIT # required utilities -utils="hg vim" +utils="hg vim tmux" for util in $utils; do p=$(which $util 2> /dev/null) || { echo "ERROR: could not locate the '$util' utility" >&2 @@ -42,12 +48,16 @@ } done -# tests +# fast tests try "no arguments" ./entr 2> /dev/null || code=$? assert $code 1 +try "no input" + ./entr echo "vroom" 2> /dev/null || code=$? + assert $code 1 + try "reload and clear options with no utility to run" ./entr -r -c 2> /dev/null || code=$? assert $code 1 @@ -62,6 +72,27 @@ rmdir $tmp/dir1 assert $code 1 +# terminal tests + +unset TMUX + +try "spacebar triggers utility" + setup + tmux new-session -s $tsession -d + echo "waiting" > $tmp/file1 + echo "finished" > $tmp/file2 + tmux send-keys -t $tsession:0 \ + "ls $tmp/file2 | ./entr -p cp $tmp/file2 $tmp/file1" C-m ; zz + assert "$(cat $tmp/file1)" "waiting" + tmux send-keys -t $tsession:0 "xyz" C-m ; zz + assert "$(cat $tmp/file1)" "waiting" + tmux send-keys -t $tsession:0 " " ; zz + assert "$(cat $tmp/file1)" "finished" + tmux send-keys -t $tsession:0 "q" ; zz + tmux kill-session -t $tsession + +# file system tests + try "exec single shell utility and exit when a file is added to an implicit watch path" setup ls $tmp/file* | ./entr -dp sh -c 'echo ping' >$tmp/exec.out 2>$tmp/exec.err \ @@ -252,12 +283,10 @@ setup ls $tmp/file* | ./entr -p cat /_ > $tmp/exec.out & bgpid=$! ; zz - echo 123 > $tmp/file2 ; zz - echo 456 > $tmp/file1 - echo 789 > $tmp/file2 ; zz + echo 456 > $tmp/file1 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" - assert "$(cat $tmp/exec.out)" "$(printf '123\n456')" + assert "$(cat $tmp/exec.out)" "456" try "exec single shell utility using utility substitution" setup @@ -283,19 +312,50 @@ try "exec an interactive utility when a file changes" setup - ls $tmp/file* | ./entr -p sh -c 'tty | colrm 9' 2> /dev/null > $tmp/exec.out & + ls $tmp/file* | ./entr -p sh -c 'tty | cut -c1-8' 2> /dev/null > $tmp/exec.out & bgpid=$! ; zz echo 456 >> $tmp/file2 ; zz kill -INT $bgpid wait $bgpid || assert "$?" "130" - if ! tty > /dev/null ; then + if ! test -t 0 ; then skip "A TTY is not available" else assert "$(cat $tmp/exec.out | tr '/pts' '/tty')" "/dev/tty" fi -# cleanup -rm -r $tmp +try "exec a command using shell option" + setup + ls $tmp/file* | ./entr -ps 'file $0; exit 2' >$tmp/exec.out 2>$tmp/exec.err & + bgpid=$! ; zz + echo 456 >> $tmp/file2 ; zz + kill -INT $bgpid + wait $bgpid || assert "$?" "130" + assert "$(cat $tmp/exec.err)" "" + assert "$(head -n1 $tmp/exec.out)" "$(printf ${tmp}'/file2: ASCII text')" + +try "exec a command as a background task" + setup + (ls $tmp/file* | ./entr -ps 'echo terminating; kill $$' >$tmp/exec.out 2>$tmp/exec.err &) + zz + echo 456 >> $tmp/file2 ; zz + assert "$(cat $tmp/exec.err)" "" + assert "$(head -n1 $tmp/exec.out)" "terminating" + +# extra slow tests that rely on timeouts + +try "ensure that all subprocesses are terminated in restart mode when a file is removed" + setup + cat <<-SCRIPT > $tmp/go.sh + #!/bin/sh + trap 'echo "caught signal"; exit' TERM + echo "running"; sleep 10 + SCRIPT + chmod +x $tmp/go.sh + ls $tmp/file2 | ./entr -r sh -c "$tmp/go.sh" 2> /dev/null > $tmp/exec.out & + bgpid=$! ; zz + rm $tmp/file2; sleep 2 + pgrep -P $bgpid > /dev/null || assert "$?" "1" + assert "$(cat $tmp/exec.out)" "$(printf 'running\ncaught signal')" +this="exit 0" echo; echo "$tests tests PASSED" -exit 0