New utility for the collection: 'buildrun', a rewrite of my previous
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 2 Aug 2012 18:00:54 +0000 (18:00 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Thu, 2 Aug 2012 18:00:54 +0000 (18:00 +0000)
program of the same name for starting a test run in one window after a
build completes in another. The previous version (still in 'misc' in
this repository) relied on Linux inotify(7) to get all the blocking
behaviour I wanted; this implementation uses nothing more strange than
a named pipe and a flock, so it should be more portable.

git-svn-id: svn://svn.tartarus.org/sgt/utils@9596 cda61777-01e9-0310-a592-d414129be87e

Makefile
buildrun/Makefile [new file with mode: 0644]
buildrun/buildrun.but [new file with mode: 0644]
buildrun/buildrun.c [new file with mode: 0644]
buildrun/test.sh [new file with mode: 0755]
buildrun/testout.txt [new file with mode: 0644]

index fc9b314..8873184 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-SUBDIRS = after base64 beep cvt-utf8 lns multi nntpid reservoir xcopy
+SUBDIRS = after base64 beep buildrun cvt-utf8 lns multi nntpid reservoir xcopy
 
 # for `make html' and `make release'; should be a relative path
 DESTDIR = .
 
 # for `make html' and `make release'; should be a relative path
 DESTDIR = .
diff --git a/buildrun/Makefile b/buildrun/Makefile
new file mode 100644 (file)
index 0000000..42a6a58
--- /dev/null
@@ -0,0 +1,45 @@
+# for `make release' and `make html'
+DESTDIR = .
+
+# for `make install'
+PREFIX = /usr/local
+BINDIR = $(PREFIX)/bin
+SCRIPTDIR = $(PREFIX)/bin
+MANDIR = $(PREFIX)/man/man1
+INSTALL = install
+IPROG =#   flags for installing programs (default none)
+IDATA = -m 0644  # flags for installing data
+
+all: buildrun.1 buildrun
+progs: buildrun
+man: buildrun.1
+
+buildrun: buildrun.c
+       $(CC) $(CFLAGS) -o $@ $<
+
+%.1: %.but
+       halibut --man=$@ $<
+
+clean:
+       rm -f *.1 buildrun *.html *.tar.gz
+
+html:
+       halibut --html=buildrun.html buildrun.but
+       mv buildrun.html $(DESTDIR)
+
+release: buildrun.1
+       mkdir -p reltmp/buildrun
+       ln -s ../../buildrun.c reltmp/buildrun
+       ln -s ../../buildrun.1 reltmp/buildrun
+       ln -s ../../buildrun.but reltmp/buildrun
+       ln -s ../../Makefile reltmp/buildrun
+       tar -C reltmp -chzf $(DESTDIR)/buildrun.tar.gz buildrun
+       rm -rf reltmp
+
+install: install-progs install-man
+install-progs: buildrun
+       mkdir -p $(BINDIR)
+       $(INSTALL) $(IPROG) buildrun $(BINDIR)/buildrun
+install-man: buildrun.1
+       mkdir -p $(MANDIR)
+       $(INSTALL) $(IDATA) buildrun.1 $(MANDIR)/buildrun.1
diff --git a/buildrun/buildrun.but b/buildrun/buildrun.but
new file mode 100644 (file)
index 0000000..cf7a261
--- /dev/null
@@ -0,0 +1,176 @@
+\cfg{man-identity}{buildrun}{1}{2012-08-01}{Simon Tatham}{Simon Tatham}
+
+\title Man page for \cw{buildrun}
+
+\U NAME
+
+\cw{buildrun} - run one program after another has completed successfully
+
+\U SYNOPSIS
+
+\c buildrun -w control-directory   command1 [ argument... ]
+\e bbbbbbbb bb iiiiiiiiiiiiiiiii   iiiiiiii   iiiiiiii
+\c buildrun -r control-directory [ command2 [ argument... ] ]
+\e bbbbbbbb bb iiiiiiiiiiiiiiiii   iiiiiiii   iiiiiiii
+
+\U DESCRIPTION
+
+\cw{buildrun} is a utility which you can use to wrap two separate
+commands, and it will wait to run the second command until the first
+command is not currently running and its last run completed
+successfully. This includes waiting while the first one runs several
+times (if the first few runs fail), and also includes not waiting at
+all if it's \e{already} true that the most recent run of the first
+command was successful.
+
+You might use \cw{buildrun} in situations where you want to run two
+commands in succession, but (for one reason or another) you'd rather
+have them run in separate shell sessions and therefore you don't want
+to take the obvious approach of simply issuing a compound command
+using the shell's \cw{&&} operator.
+
+A typical scenario involves the first command being a software build
+process (a compile command, or \cw{make}, or similar), and the second
+being some attempt to run the resulting program or its test suite or a
+debugger. You might want to run your build and test commands in
+separate shell sessions for several reasons: if it's convenient to
+give them different working directories, or in order to separate the
+shell command-recall histories (so that the build terminal has all the
+commands related to editing and searching source files, and the test
+terminal has the ones related to adjusting the test environment), or
+in order to separate their output (so that successive test runs appear
+adjacent to each other in the test terminal and can be easily
+compared, while the output from build commands is available for
+checking if it's needed but doesn't keep scrolling the test output off
+the screen).
+
+To use \cw{buildrun}, you must first decide on a location for a
+\e{control directory} which the two \cw{buildrun} processes can use to
+communicate between themselves. Then, in one window, you run the
+command you want to run first (e.g. the compile operation), prefixed
+with a \cw{buildrun -w} command giving the pathname of the control
+directory. For instance, you might run
+
+\c buildrun -w /tmp/controldir make
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+Then, in another window, run the second command (e.g. a test or
+debugging command) prefixed with a similar \cw{buildrun -r} command,
+for example
+
+\c buildrun -r /tmp/controldir ./test.sh
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+The instance of \cw{buildrun} run with \cw{-w} will immediately run
+its command; the instance run with \cw{-r} will wait for the \cw{-w}
+command to complete, and then run its own command. If the first
+command fails, the \cw{buildrun -r} process will continue to sleep;
+you can then correct the problem and re-run the compile, and
+\cw{buildrun -r} will only wake up once an instance of \cw{buildrun
+-w}'s command completes successfully.
+
+If you then immediately re-run \cw{buildrun -r} without starting
+another build, it will run its command instantly without waiting. So
+you can repeat your testing or debugging, or run several different
+test runs, by simply recalling the same test command \e{including} the
+\cw{buildrun -r} prefix.
+
+(The option names \cw{-w} and \cw{-r} are intended to imply that the
+first command is writing some kind of resource, such as a compiled
+program or data file, and that the second command intends to read that
+resource and hence needs the first command to have written it
+correctly.)
+
+\U OPTIONS
+
+You must specify one of \cw{-w} and \cw{-r}.
+
+\dt \cw{-w}
+
+\dd Runs \cw{buildrun} in write mode. You must provide a command line;
+\cw{buildrun -w} will run that command, and use the control directory
+to signal to any waiting \cw{buildrun -r} instances when it has
+completed successfully.
+
+\dt \cw{-r}
+
+\dd Runs \cw{buildrun} in read mode. If a command wrapped by
+\cw{buildrun -w} is currently in progress, or if the last such command
+failed, then \cw{buildrun -r} will wait until one succeeds before
+doing anything. However, if no \cw{buildrun -w} is currently running
+and the last one completed successfully, \cw{buildrun -r} will not
+wait at all.
+
+\lcont{
+
+If a command line is provided, \cw{buildrun -r} will run that command
+after it finishes waiting. If no command line is provided,
+\cw{buildrun -r} will simply return success when it finishes waiting.
+
+}
+
+\U ARGUMENTS
+
+\dt \e{control-directory}
+
+\dd This argument is mandatory in both \cw{-w} and \cw{-r} modes. It
+must give the pathname of a directory which the two instances of
+\cw{buildrun} will use to communicate with each other.
+
+\lcont{
+
+You need not create the control directory; \cw{buildrun -w} will
+create it the first time it runs. It will also be deleted every time
+\cw{buildrun -w}'s command completes successfully, because that's how
+success is communicated to \cw{buildrun -r}. (A side effect of that is
+that if \cw{buildrun -r} is run before \e{ever} running \cw{buildrun
+-w}, it will behave as if there had been a successful \cw{-w} run.)
+
+The control directory should not be on a network-mounted filesystem,
+since network filesystems sometimes diverge from the normal Unix
+behaviour which \cw{buildrun} relies on. Using a directory in
+\cw{/tmp} is recommended.
+
+Of course, instances of \cw{buildrun} using different control
+directories will be completely independent of each other. So you can
+simultaneously run two or more pairs of commands each linked by their
+own pair of \cw{buildrun}s, and as long as each pair has a separaet
+control directory, they won't interfere with each other.
+
+}
+
+\dt \e{command} (and optional arguments)
+
+\dd The first word on \cw{buildrun}'s command line which is not an
+option or the control directory name will be treated as a command to
+be run by \cw{buildrun}, and anything following that word will be
+treated as arguments to that command. The command will be run directly
+using the \cw{execvp}(\e{3}) function, so shell syntax (pipes,
+redirections etc) is not supported. If you need your command to
+contain things like that, you can achieve it by explicitly invoking a
+shell, e.g.
+
+\lcont{
+
+\c buildrun -w sh -c '(cmd1; cmd2 | cmd3) > outfile'
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+In \cw{buildrun -r} mode, an alternative to doing that is simply not
+to provide a command at all, and instead tell your shell to run a
+complex command \e{after} \cw{buildrun}, e.g.
+
+\c buildrun -r && (cmd1; cmd2 | cmd3) > outfile
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+(This alternative is not available with \cw{buildrun -w}, since that
+has to run its command as a subprocess so that it can wait for it to
+finish and see whether it worked.)
+
+}
+
+\U LICENCE
+
+\cw{buildrun} is free software, distributed under the MIT licence.
+Type \cw{buildrun --licence} to see the full licence text.
+
+\versionid $Id$
diff --git a/buildrun/buildrun.c b/buildrun/buildrun.c
new file mode 100644 (file)
index 0000000..6b89e52
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * buildrun.c: wait to run one command until an instance of another
+ * completes successfully
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/*
+ * Implementation strategy
+ * -----------------------
+ *
+ * The basic plan for making buildrun -r wait for buildrun -w is that
+ * buildrun -w creates a named pipe, opens it for writing, and does
+ * not actually write to it; then buildrun -r opens the same named
+ * pipe for reading. When buildrun -w terminates, anything which was
+ * trying to read from the pipe receives EOF, so buildrun -r can wake
+ * up and look to see if the run was successful.
+ *
+ * buildrun -w's report of success or failure is done by the very
+ * simple approach of deleting the pipe file before terminating, if
+ * the run was successful. So after buildrun -r receives EOF on the
+ * pipe, it immediately tries to open it again - and if it gets
+ * ENOENT, that's the success condition. Otherwise it reopens the pipe
+ * and blocks again until another buildrun -w succeeds.
+ *
+ * If the run was unsuccessful, buildrun -r must block immediately and
+ * stay blocked until buildrun -w next runs. This is achieved
+ * automatically by the above strategy, since attempting to open a
+ * pipe for reading when nothing is trying to write it will cause the
+ * open(2) call to block until something opens the pipe for writing.
+ *
+ * Likewise, if the last buildrun -w was unsuccessful and no -w run is
+ * active at the moment buildrun -r starts in the first place,
+ * buildrun -r must block until one starts up - and again, this is
+ * done automatically by the attempt to open the pipe for reading.
+ *
+ * Of course, the blocking open behaviour of named pipes works both
+ * ways: if buildrun -w tried to open the pipe for writing when no
+ * buildrun -r is trying to read it, _it_ would block. For that
+ * reason, buildrun -w actually opens with O_RDWR, so it can't block.
+ *
+ * Just in case the user tries to run two buildrun -w instances on the
+ * same control file, we also flock(LOCK_EX) the named pipe once we've
+ * opened it, so that the second -w instance can cleanly report the
+ * error condition.
+ *
+ * Finally, there is a small race condition in the mechanism I've just
+ * described. Suppose one buildrun -w instance finishes and deletes
+ * its pipe file - but before buildrun -r manages to unblock and
+ * notice the file's absence, another buildrun -w starts up and
+ * recreates it. This is a more or less theoretical risk in the
+ * interactive environments where I expect this tool to be used, but
+ * even so, I like to solve these problems where I can; what I do is
+ * to create not just a named pipe, but a _subdirectory_ containing a
+ * named pipe. So a successful buildrun -w deletes the pipe and
+ * removes the containing directory; a subsequent buildrun -w will
+ * create a fresh directory and pipe. Then buildrun -r starts by
+ * setting its _cwd_ to the directory containing the pipe - which
+ * means that if one buildrun -w finishes and another one starts,
+ * buildrun -r will still have its cwd pointing at the orphaned
+ * directory from the first, and won't accidentally find the pipe file
+ * created by the second in its own directory.
+ */
+
+#define PIPEFILE "pipe"
+
+char *ctldir, *pipepath, **command;
+
+int writemode(void)
+{
+    pid_t pid;
+    int fd, status;
+
+    if (mkdir(ctldir, 0700) < 0 && errno != EEXIST) {
+        fprintf(stderr, "buildrun: %s: mkdir: %s\n", ctldir, strerror(errno));
+        return 1;
+    }
+    if (mknod(pipepath, S_IFIFO | 0600, 0) < 0 && errno != EEXIST) {
+        fprintf(stderr, "buildrun: %s: mknod: %s\n", pipepath,strerror(errno));
+        return 1;
+    }
+    fd = open(pipepath, O_RDWR);
+    if (fd < 0) {
+        fprintf(stderr, "buildrun: %s: open: %s\n", pipepath, strerror(errno));
+        return 1;
+    }
+    if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
+        if (errno == EAGAIN) {
+            fprintf(stderr, "buildrun: another write process "
+                    "is already active\n");
+        } else {
+            fprintf(stderr, "buildrun: %s: flock: %s\n",
+                    pipepath, strerror(errno));
+        }
+        return 1;
+    }
+
+    pid = fork();
+    if (pid < 0) {
+        fprintf(stderr, "buildrun: fork: %s\n", strerror(errno));
+        return 1;
+    } else if (pid == 0) {
+        close(fd);
+        execvp(command[0], command);
+        fprintf(stderr, "buildrun: %s: exec: %s\n",
+                command[0], strerror(errno));
+        return 127;
+    }
+
+    while (1) {
+        if (waitpid(pid, &status, 0) < 0) {
+            fprintf(stderr, "buildrun: wait: %s\n", strerror(errno));
+            return 1;
+        }
+        if (WIFEXITED(status)) {
+            status = WEXITSTATUS(status);
+            break;
+        } else if (WIFSIGNALED(status)) {
+            status = 128 | WTERMSIG(status);
+            break;
+        }
+    }
+
+    if (status == 0) {
+        if (remove(pipepath) < 0) {
+            fprintf(stderr, "buildrun: %s: remove: %s\n", pipepath,
+                    strerror(errno));
+            return 1;
+        }
+        if (rmdir(ctldir) < 0) {
+            fprintf(stderr, "buildrun: %s: rmdir: %s\n", ctldir,
+                    strerror(errno));
+            return 1;
+        }
+    }
+
+    return status;
+}
+
+int readmode()
+{
+    pid_t pid;
+    int fd, ret, status;
+    char buf[1];
+
+    /*
+     * Our strategy against race conditions relies on changing our cwd
+     * to the control dir, but that will confuse the user command if
+     * we exec one afterwards. So we do most of our work in a
+     * subprocess, and don't pollute our main process context.
+     */
+    pid = fork();
+    if (pid < 0) {
+        fprintf(stderr, "buildrun: fork: %s\n", strerror(errno));
+        return 1;
+    } else if (pid == 0) {
+        /*
+         * The main show.
+         */
+        if (chdir(ctldir) < 0) {
+            if (errno == ENOENT) {
+                /*
+                 * If the control directory doesn't exist at all, it
+                 * must be because the last -w run completed
+                 * successfully, so we immediately return success.
+                 */
+                return 0;
+            }
+            fprintf(stderr, "buildrun: %s: chdir: %s\n",
+                    ctldir, strerror(errno));
+            return 1;
+        }
+        while (1) {
+            fd = open(PIPEFILE, O_RDONLY);
+            if (fd < 0) {
+                if (errno == ENOENT)
+                    return 0;          /* success! */
+
+                fprintf(stderr, "buildrun: %s: open: %s\n",
+                        pipepath, strerror(errno));
+                return 1;
+            }
+
+            ret = read(fd, buf, 1);
+            if (ret < 0) {
+                fprintf(stderr, "buildrun: %s: read: %s\n",
+                        pipepath, strerror(errno));
+                return 1;
+            } else if (ret > 0) {
+                fprintf(stderr, "buildrun: %s: read: unexpectedly received "
+                        "some data!\n", pipepath, strerror(errno));
+                return 1;
+            }
+            close(fd);
+        }
+    }
+
+    while (1) {
+        if (waitpid(pid, &status, 0) < 0) {
+            fprintf(stderr, "buildrun: wait: %s\n", strerror(errno));
+            return 1;
+        }
+        if (WIFEXITED(status)) {
+            status = WEXITSTATUS(status);
+            break;
+        } else if (WIFSIGNALED(status)) {
+            status = 128 | WTERMSIG(status);
+            break;
+        }
+    }
+
+    if (status != 0)                   /* something went wrong in child */
+        return status;
+
+    /*
+     * In -r mode, running a command is optional; in the absence of
+     * one, we simply return true, so the user can invoke as 'buildrun
+     * -r file && complex shell command'.
+     */
+    if (!command)
+        return 0;
+
+    execvp(command[0], command);
+    fprintf(stderr, "buildrun: %s: exec: %s\n", command[0], strerror(errno));
+    return 127;
+}
+
+const char usagemsg[] =
+    "usage: buildrun -w <control_dir> <command> [<args>...]\n"
+    "   or: buildrun -r <control_dir> [<command> [<args>...]]\n"
+    "where: -w               write mode: run a build command\n"
+    "       -r               read mode: wait for a build command to succeed\n"
+    "       <control_dir>    directory to use for communication; suggest in /tmp\n"
+    " also: buildrun --version              report version number\n"
+    "       buildrun --help                 display this help text\n"
+    "       buildrun --licence              display the (MIT) licence text\n"
+    ;
+
+void usage(void) {
+    fputs(usagemsg, stdout);
+}
+
+const char licencemsg[] =
+    "buildrun is copyright 2012 Simon Tatham.\n"
+    "\n"
+    "Permission is hereby granted, free of charge, to any person\n"
+    "obtaining a copy of this software and associated documentation files\n"
+    "(the \"Software\"), to deal in the Software without restriction,\n"
+    "including without limitation the rights to use, copy, modify, merge,\n"
+    "publish, distribute, sublicense, and/or sell copies of the Software,\n"
+    "and to permit persons to whom the Software is furnished to do so,\n"
+    "subject to the following conditions:\n"
+    "\n"
+    "The above copyright notice and this permission notice shall be\n"
+    "included in all copies or substantial portions of the Software.\n"
+    "\n"
+    "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
+    "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
+    "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
+    "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
+    "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
+    "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
+    "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
+    "SOFTWARE.\n"
+    ;
+
+void licence(void) {
+    fputs(licencemsg, stdout);
+}
+
+void version(void) {
+#define SVN_REV "$Revision: 8227 $"
+    char rev[sizeof(SVN_REV)];
+    char *p, *q;
+
+    strcpy(rev, SVN_REV);
+
+    for (p = rev; *p && *p != ':'; p++);
+    if (*p) {
+        p++;
+        while (*p && isspace(*p)) p++;
+        for (q = p; *q && *q != '$'; q++);
+        if (*q) *q = '\0';
+        printf("buildrun revision %s\n", p);
+    } else {
+        printf("buildrun: unknown version\n");
+    }
+}
+
+int main(int argc, char **argv)
+{
+    enum { UNSPEC, READ, WRITE } mode = UNSPEC;
+    int doing_opts = 1;
+
+    ctldir = NULL;
+    command = NULL;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (doing_opts && *p == '-') {
+            if (!strcmp(p, "-w")) {
+                mode = WRITE;
+            } else if (!strcmp(p, "-r")) {
+                mode = READ;
+            } else if (!strcmp(p, "--")) {
+                doing_opts = 0;
+            } else if (!strcmp(p, "--version")) {
+                version();
+                return 0;
+            } else if (!strcmp(p, "--help")) {
+                usage();
+                return 0;
+            } else if (!strcmp(p, "--licence") || !strcmp(p, "--license")) {
+                licence();
+                return 0;
+            } else {
+                fprintf(stderr, "buildrun: unrecognised option '%s'\n", p);
+                return 1;
+            }
+        } else {
+            if (!ctldir) {
+                ctldir = p;
+            } else {
+                command = argv;
+                break;
+            }
+        }
+    }
+
+    if (mode == UNSPEC) {
+        fprintf(stderr, "buildrun: expected -w or -r\n");
+        return 1;
+    }
+
+    if (!ctldir) {
+        fprintf(stderr, "buildrun: expected a control directory name\n");
+        return 1;
+    }
+
+    pipepath = malloc(strlen(ctldir) + strlen(PIPEFILE) + 2);
+    if (!pipepath) {
+        fprintf(stderr, "buildrun: out of memory\n");
+        return 1;
+    }
+    sprintf(pipepath, "%s/%s", ctldir, PIPEFILE);
+
+    if (mode == WRITE) {
+        return writemode();
+    } else if (mode == READ) {
+        return readmode();
+    }
+}
diff --git a/buildrun/test.sh b/buildrun/test.sh
new file mode 100755 (executable)
index 0000000..8991674
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# Test script for buildrun.
+
+# Can be used with my original inotify-based implementation too,
+# provided you pass --old on the command line.
+
+rm -rf testctl
+rm -f testlog
+
+# The inotify-based version wouldn't cope if the control file didn't
+# exist to begin with. Create an empty one.
+if test "x--old" = "x$1"; then touch testctl; fi
+
+testwrite() { # usage: testwrite <identifier> <exitstatus>
+    buildrun -w testctl sh -c "echo write start $1 >> testlog; sleep 2; echo write end $1 returning $2 >> testlog; exit $2"
+}
+testread() { # usage: testread <identifier>
+    echo read start $1 >> testlog
+    buildrun -r testctl sh -c "echo read end $1 >> testlog"
+}
+
+# Test 1: start buildrun -w first, then buildrun -r. The former
+# completes successfully, and the latter should then finish.
+( testwrite 1 0 ) &
+sleep 0.5
+testread 1
+
+# Test 2: same, but this time the first -w fails and we need a second
+# one that succeeds.
+( testwrite 2a 1; sleep 0.5; testwrite 2b 0 ) &
+sleep 0.5
+testread 2
+
+# Test 3: this time we arrange a failure, and then start up a buildrun
+# -r while no -w is running at all, demonstrating that it can block
+# even when there's nothing to block on.
+testwrite 3pre 1
+( testread 3 ) &
+sleep 0.5
+testwrite 3 0
+sleep 0.5
+
+# Test 4: same, but again we have a failure before a success.
+testwrite 4pre 1
+( testread 4 ) &
+sleep 0.5
+testwrite 4a 1; sleep 0.5; testwrite 4b 0
+sleep 0.5
+
+# Test 5: now that no -w run is active and the last one was
+# successful, do a -r run on its own and check that it returns success
+# without blocking.
+testread 5
+
+if diff -u testout.txt testlog; then
+    # Files match. Report success and clear up.
+    echo "Tests passed."
+    rm -rf testctl
+    rm -f testlog
+else
+    # They don't match. Leave the temporary files lying around and
+    # return failure.
+    exit 1
+fi
diff --git a/buildrun/testout.txt b/buildrun/testout.txt
new file mode 100644 (file)
index 0000000..85c3480
--- /dev/null
@@ -0,0 +1,26 @@
+write start 1
+read start 1
+write end 1 returning 0
+read end 1
+write start 2a
+read start 2
+write end 2a returning 1
+write start 2b
+write end 2b returning 0
+read end 2
+write start 3pre
+write end 3pre returning 1
+read start 3
+write start 3
+write end 3 returning 0
+read end 3
+write start 4pre
+write end 4pre returning 1
+read start 4
+write start 4a
+write end 4a returning 1
+write start 4b
+write end 4b returning 0
+read end 4
+read start 5
+read end 5