third_party: add github.com/monopole/mdrip

mdrip is used by $V23_ROOT/www to execute code blocks in the website content.
This enables testing a tutorial's embedded code.

Change-Id: I3c1cad7ba4fa6fdc6275594caa9329c5f8c1c08b
diff --git a/go/src/github.com/gorilla/websocket/README.google b/go/src/github.com/gorilla/websocket/README.google
index 9646889..bc12d95 100644
--- a/go/src/github.com/gorilla/websocket/README.google
+++ b/go/src/github.com/gorilla/websocket/README.google
@@ -1,6 +1,6 @@
 URL: https://github.com/gorilla/websocket/archive/92334662baa9cbebc2e6e68b8d56bc1233f85a4c.zip
 Version: 92334662baa9cbebc2e6e68b8d56bc1233f85a4c
-License: Notice License
+License: MIT
 License File: LICENSE
 
 Description:
diff --git a/go/src/github.com/monopole/mdrip/.travis.yml b/go/src/github.com/monopole/mdrip/.travis.yml
new file mode 100644
index 0000000..4f2ee4d
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/.travis.yml
@@ -0,0 +1 @@
+language: go
diff --git a/go/src/github.com/monopole/mdrip/LICENSE b/go/src/github.com/monopole/mdrip/LICENSE
new file mode 100644
index 0000000..282fddc
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Jeff Regan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/go/src/github.com/monopole/mdrip/README.google b/go/src/github.com/monopole/mdrip/README.google
new file mode 100644
index 0000000..efa3e79
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/README.google
@@ -0,0 +1,10 @@
+URL: https://github.com/monopole/mdrip/archive/44d228784e161379153583e31bcc32d9a25796e3.zip
+Version: 44d228784e161379153583e31bcc32d9a25796e3
+License: Notice License
+License File: LICENSE
+
+Description:
+mdrip rips labeled command blocks from markdown files for execution.
+
+Local Modifications:
+No modifications.
diff --git a/go/src/github.com/monopole/mdrip/README.md b/go/src/github.com/monopole/mdrip/README.md
new file mode 100644
index 0000000..30b35fe
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/README.md
@@ -0,0 +1,94 @@
+# mdrip
+
+[![Build Status](https://travis-ci.org/monopole/mdrip.svg?branch=master)](https://travis-ci.org/monopole/mdrip)
+
+`mdrip` rips labeled command blocks from markdown files for execution.
+
+`mdrip` accepts one _label_ argument and any number of _file name_
+arguments, where the files are assumed to contain markdown.  It scans
+the files for
+[fenced code blocks](https://help.github.com/articles/github-flavored-markdown/#fenced-code-blocks)
+immediately preceded by an HTML comment with embedded _@labels_.
+
+If one of the block labels matches the label argument to the command line, the associated block is extracted.  Extracted blocks are emitted to `stdout`, or, if `--subshell` is specified, concatenated to run as a subprocess.
+
+This is a markdown-based instance of language-independent
+[literate programming](http://en.wikipedia.org/wiki/Literate_programming)
+(for perspective, see the latex-based
+[noweb](http://en.wikipedia.org/wiki/Noweb)).
+It's language independent because shell scripts can
+make, build and run programs in any programming language, via [_here_
+documents](http://tldp.org/LDP/abs/html/here-docs.html) and what not.
+
+
+## Build
+
+Assuming Go installed:
+
+```
+export MDRIP=~/mdrip
+GOPATH=$MDRIP/go go get github.com/monopole/mdrip
+GOPATH=$MDRIP/go go test github.com/monopole/mdrip/util
+$MDRIP/go/bin/mdrip   # Shows usage.
+```
+
+## Example
+
+This [markdown coding tutorial](https://github.com/monopole/mdrip/blob/master/example_tutorial.md)
+(raw markdown
+[here](https://raw.githubusercontent.com/monopole/mdrip/master/example_tutorial.md))
+has bash code blocks that write, compile and run a Go program.
+
+Send code from that file to `stdout`:
+
+```
+$MDRIP/go/bin/mdrip lesson1 \
+    $MDRIP/go/src/github.com/monopole/mdrip/example_tutorial.md
+```
+
+Alternatively, run it's code in a subshell:
+```
+$MDRIP/go/bin/mdrip --subshell lesson1 \
+    $MDRIP/go/src/github.com/monopole/mdrip/example_tutorial.md
+```
+
+The above command has no output and exits with status zero if all the
+scripts labelled `@lesson1` in the given markdown succeed.  On any
+failure, however, the command dumps a report and exits with non-zero
+status.
+
+This is one way to cover documentation with feature tests.
+Keeping code and documentation describing the code in the same file makes it much easier to keep them in sync.
+
+
+## Details
+
+A _script_ is a sequence of code blocks with a common label.  If a
+block has multiple labels, it can be incorporated into multiple
+scripts.  If a block has no label, it's ignored.  The number of
+scripts that can be extracted from a set of markdown files equals the
+number of unique labels.
+
+If code blocks are in bash syntax, and the tool is itself running
+in a bash shell, then piping `mdrip` output to `source /dev/stdin` is
+equivalent to a human copy/pasting code blocks to their own shell
+prompt.  In this scenario, an error in block _N_ will not stop
+execution of block _N+1_.  To instead stop on error, pipe the output
+to `bash -e`.
+
+Alternatively, the tool can itself run extracted code in a bash subshell like this
+
+> `mdrip --subshell someLabel file1.md file2.md ...`
+
+If that command fails, so did something in a command block.  `mdrip` reports which block failed and what it's `stdout` and `stderr` saw, while otherwise capturing and discarding subshell output.
+
+There's no notion of encapsulation.  Also, there's no automatic cleanup.  A block that does cleanup can be added to the markdown.
+
+### Special labels
+
+ * The first label on a block is slightly special, in that it's
+reported as the block name for logging.  But like any label
+it can be used for selection too.
+
+ * The @sleep label causes mdrip to insert a `sleep 2` command after the block.  Appropriate if one is starting a server in the background in that block.
+
diff --git a/go/src/github.com/monopole/mdrip/bad.md b/go/src/github.com/monopole/mdrip/bad.md
new file mode 100644
index 0000000..39ae6b1
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/bad.md
@@ -0,0 +1,22 @@
+<!-- @bad @good @sleep -->
+```
+nc -l 8000 &
+PID=$!
+```
+
+<!-- @good -->
+```
+kill $PID
+```
+
+<!-- @bad -->
+```
+echo "Don't forget to: killall nc"
+```
+
+<!-- @bad @good -->
+```
+echo "About to trigger a failure."
+echo ${DONT_EXIST?} > /dev/null
+```
+
diff --git a/go/src/github.com/monopole/mdrip/example_tutorial.md b/go/src/github.com/monopole/mdrip/example_tutorial.md
new file mode 100644
index 0000000..4de47d5
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/example_tutorial.md
@@ -0,0 +1,48 @@
+First do some setup:
+
+<!-- @init @lesson1 @cleanup -->
+```
+DEMO_DIR=/tmp/mdrip_example
+mkdir -p $DEMO_DIR/src/example
+```
+
+Write a *Go* function...
+
+<!-- @makeAdder @lesson1 -->
+```
+ cat - <<EOF >$DEMO_DIR/src/example/add.go
+package main
+
+func add(x, y int) (int) { return x + y }
+EOF
+echo "the next command intended to fail"
+badCommandToTriggerTestFailure
+```
+
+...and a main program to call it:
+
+<!-- @makeMain @lesson1 -->
+```
+ cat - <<EOF >$DEMO_DIR/src/example/main.go
+package main
+
+import "fmt"
+
+func main() {
+    comment this line to avoid compiler error
+    fmt.Printf("Calling add on 1 and 2 yields %d.\n", add(1, 2))
+}
+EOF
+echo "The following compile should fail."
+GOPATH=$DEMO_DIR go install example
+$DEMO_DIR/bin/example
+```
+
+Copy/paste the above into a shell to build and run your *Go* program.
+
+Clean up with this command:
+
+<!-- @cleanup @lesson1 @sleep -->
+```
+/bin/rm -rf $DEMO_DIR
+```
diff --git a/go/src/github.com/monopole/mdrip/main.go b/go/src/github.com/monopole/mdrip/main.go
new file mode 100644
index 0000000..bbc5f70
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/main.go
@@ -0,0 +1,172 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"github.com/monopole/mdrip/util"
+	"io/ioutil"
+	"log"
+	"os"
+	"strings"
+	"time"
+)
+
+var blockTimeOut = flag.Duration("blockTimeOut", 7*time.Second,
+	"The max amount of time to wait for a command block to exit.")
+
+// dumpBucket emits the contents of a util.ScriptBucket.
+//
+// If n <= 0, dump everything, else only dump the first n blocks.  n
+// is 1 relative, i.e., if you want the first two blocks dumped, pass
+// n==2, not n==1.
+func dumpBucket(label string, bucket *util.ScriptBucket, n int) {
+	fmt.Printf("#\n# Script @%s from %s \n#\n", label, bucket.GetFileName())
+	delimFmt := "#" + strings.Repeat("-", 70) + "#  %s %d\n"
+	for i, block := range bucket.GetScript() {
+		if n > 0 && i >= n {
+			break
+		}
+		fmt.Printf(delimFmt, "Start", i+1)
+		fmt.Printf("echo \"Block '%s' (%d/%d in %s) of %s\"\n####\n",
+			block.GetLabels()[0], i+1, len(bucket.GetScript()), label, bucket.GetFileName())
+		fmt.Print(block.GetCodeText())
+		fmt.Printf(delimFmt, "End", i+1)
+		fmt.Println()
+	}
+}
+
+// emitStraightScript simply prints the contents of scriptBuckets.
+func emitStraightScript(label string, scriptBuckets []*util.ScriptBucket) {
+	for _, bucket := range scriptBuckets {
+		dumpBucket(label, bucket, 0)
+	}
+	fmt.Printf("echo \" \"\n")
+	fmt.Printf("echo \"All done.  No errors.\"\n")
+}
+
+// emitPreambledScript emits the first script normally, then emit it
+// again, as well as the the remaining scripts, so that they run in a
+// subshell.
+//
+// This allows the aggregrate script to be structured as 1) a preamble
+// initialization script that impacts the environment of the active
+// shell, followed by 2) a script that executes as a subshell that
+// exits on error.  An exit in (2) won't cause the active shell (most
+// likely a terminal) to close.
+//
+// The first script must be able to complete without exit on error
+// because its not running as a subshell.  So it should just set
+// environment variables and/or define shell funtions.
+func emitPreambledScript(label string, scriptBuckets []*util.ScriptBucket, n int) {
+	dumpBucket(label, scriptBuckets[0], n)
+	delim := "HANDLED_SCRIPT"
+	fmt.Printf(" bash -e <<'%s'\n", delim)
+	fmt.Printf("function handledTrouble() {\n")
+	fmt.Printf("  echo \" \"\n")
+	fmt.Printf("  echo \"Unable to continue!\"\n")
+	fmt.Printf("  exit 1\n")
+	fmt.Printf("}\n")
+	fmt.Printf("trap handledTrouble INT TERM\n")
+	emitStraightScript(label, scriptBuckets)
+	fmt.Printf("%s\n", delim)
+}
+
+func usage() {
+	fmt.Fprintf(os.Stderr, "\nUsage:  %s {label} {fileName}...\n", os.Args[0])
+	flag.PrintDefaults()
+	fmt.Fprintf(os.Stderr,
+		`
+Reads markdown files, extracts code blocks with a given @label, and
+either runs them in a subshell or emits them to stdout.
+
+If the markdown file contains
+
+  Blah blah blah.
+  <!-- @goHome @foo -->
+  '''
+  cd $HOME
+  '''
+  Blah blah blah.
+  <!-- @echoApple @apple -->
+  '''
+  echo "an apple a day keeps the doctor away"
+  '''
+  Blah blah blah.
+  <!-- @echoCloseStar @foo @baz -->
+  '''
+  echo "Proxima Centauri"
+  '''
+  Blah blah blah.
+
+then the command '{this} foo {fileName}' emits: 
+
+  cd $HOME
+  echo "Proxima Centauri"
+
+Pipe output to 'source /dev/stdin' to run it directly.
+
+Use --subshell to run the blocks in a subshell leaving your current
+shell env vars and pwd unchanged.  The code blocks can, however, do
+anything to your computer that you can.
+`)
+}
+
+func main() {
+	flag.Usage = usage
+	preambled := flag.Int("preambled", -1,
+		"Place all scripts in a subshell, preambled by the first {n} blocks in the first script.")
+	subshell := flag.Bool("subshell", false,
+		"Run extracted blocks in subshell (leaves your env vars and pwd unchanged).")
+	swallow := flag.Bool("swallow", false,
+		"Swallow errors from subshell (non-zero exit only on problems in driver code).")
+	flag.Parse()
+	if *swallow && !*subshell {
+		fmt.Fprintf(os.Stderr, "Makes no sense to specify --swallow but not --subshell.\n")
+		usage()
+		os.Exit(1)
+	}
+	if flag.NArg() < 2 {
+		usage()
+		os.Exit(1)
+	}
+	label := flag.Arg(0)
+	scriptBuckets := make([]*util.ScriptBucket, flag.NArg()-1)
+
+	for i := 1; i < flag.NArg(); i++ {
+		fileName := flag.Arg(i)
+		contents, err := ioutil.ReadFile(fileName)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Unable to read %q\n", fileName)
+			usage()
+			os.Exit(2)
+		}
+		m := util.Parse(string(contents))
+		script, ok := m[label]
+		if !ok {
+			fmt.Fprintf(os.Stderr, "No block labelled %q in file %q.\n", label, fileName)
+			os.Exit(3)
+		}
+		scriptBuckets[i-1] = util.NewScriptBucket(fileName, script)
+	}
+
+	if len(scriptBuckets) < 1 {
+		return
+	}
+
+	if !*subshell {
+		if *preambled >= 0 {
+			emitPreambledScript(label, scriptBuckets, *preambled)
+		} else {
+			emitStraightScript(label, scriptBuckets)
+		}
+		return
+	}
+
+	result := util.RunInSubShell(scriptBuckets, *blockTimeOut)
+	if result.GetProblem() != nil {
+		util.Complain(result, label)
+		if !*swallow {
+			log.Fatal(result.GetProblem())
+		}
+	}
+}
diff --git a/go/src/github.com/monopole/mdrip/util/buff_scanner.go b/go/src/github.com/monopole/mdrip/util/buff_scanner.go
new file mode 100644
index 0000000..d116cf0
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/util/buff_scanner.go
@@ -0,0 +1,104 @@
+package util
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"time"
+)
+
+// Special strings that might appear in shell output, signalling
+// things to the stream processors.
+const MsgHappy = "MDRIP_HAPPY_Completed_command_block"
+const MsgError = "MDRIP_ERROR_Problem_while_executing_command_block"
+const MsgTimeout = "MDRIP_TIMEOUT_Command_block_did_not_finish_in_allotted_time"
+
+// BuffScanner returns a channel to which it will write lines of text.
+//
+// The text is harvested from an io stream, which will be read until
+// the io stream hits EOF or otherwise closes - at which point the
+// returned channel is closed.
+//
+// If the io stream blocks for longer than the given wait time, the
+// function will send a special line of text to the channel and close
+// it.
+func BuffScanner(wait time.Duration, label string, stream io.ReadCloser, debug bool) <-chan string {
+	chLine := make(chan string, 1)
+	xScanner := func() <-chan string {
+		chBuffLine := make(chan string, 1)
+		go func() {
+			defer close(chBuffLine)
+			scanner := bufio.NewScanner(stream)
+			if debug {
+				fmt.Printf("DEBUG: xScanner: %s - starting up\n", label)
+			}
+			for scanner.Scan() {
+				if debug {
+					fmt.Printf("DEBUG: xScanner: %s - calling Text\n", label)
+				}
+				line := scanner.Text()
+				if debug {
+					fmt.Printf("DEBUG: xScanner: %s - got \"%s\"\n", label, line)
+				}
+				chBuffLine <- line
+				if debug {
+					fmt.Printf("DEBUG: xScanner: %s - handed \"%s\" to channel\n", label, line)
+				}
+			}
+
+			if debug {
+				fmt.Printf("DEBUG: xScanner: %s - exitted Scan loop\n", label)
+			}
+			if err := scanner.Err(); err != nil {
+				if debug {
+					fmt.Printf("DEBUG: xScanner: %s - error : %s\n", label, err.Error())
+				}
+				chBuffLine <- MsgError + " : " + err.Error()
+			}
+			if debug {
+				fmt.Printf("DEBUG: xScanner: %s - completely done\n", label)
+			}
+		}()
+		return chBuffLine
+	}
+
+	chBuffLine := xScanner()
+
+	go func() {
+		defer close(chLine)
+		for {
+			if debug {
+				fmt.Printf("DEBUG: buffScanner: %s - top of loop\n", label)
+			}
+			select {
+			case line, ok := <-chBuffLine:
+				if ok {
+					if debug {
+						fmt.Printf("DEBUG: buffScanner: %s - got line, sending on\n", label)
+					}
+					chLine <- line
+					if debug {
+						fmt.Printf("DEBUG: buffScanner: %s - sent line\n", label)
+					}
+				} else {
+					if debug {
+						fmt.Printf("DEBUG: buffScanner: %s - done reading the stream\n", label)
+					}
+					chBuffLine = nil
+					return
+				}
+			case <-time.After(wait):
+				chLine <- MsgTimeout
+				if debug {
+					fmt.Printf("DEBUG: buffScanner: %s - timed out\n", label)
+				}
+				return
+			}
+		}
+
+		if debug {
+			fmt.Printf("DEBUG: buffScanner: returning chLine\n")
+		}
+	}()
+	return chLine
+}
diff --git a/go/src/github.com/monopole/mdrip/util/buff_scanner_test.go b/go/src/github.com/monopole/mdrip/util/buff_scanner_test.go
new file mode 100644
index 0000000..43c4773
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/util/buff_scanner_test.go
@@ -0,0 +1,129 @@
+package util
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"testing"
+	"time"
+)
+
+type stalledReader struct {
+	bytes.Buffer
+}
+
+func (stalledReader) Read(p []byte) (n int, err error) {
+	time.Sleep(5 * time.Second)
+	return 0, nil
+}
+func (stalledReader) Close() error { return nil }
+
+func TestStalledReader(t *testing.T) {
+	foo := stalledReader{}
+	chOut := BuffScanner(1*time.Second, "heythere", foo, true)
+
+	line, ok := <-chOut
+	if !ok {
+		t.Fail()
+	}
+	want := MsgTimeout
+	if line != want {
+		t.Errorf("got \n\t%v\nwant\n\t%v", line, want)
+	}
+
+	line, ok = <-chOut
+	if ok {
+		t.Fail()
+	}
+}
+
+type bustedReader struct {
+	bytes.Buffer
+}
+
+func (bustedReader) Read(p []byte) (n int, err error) {
+	return 0, nil
+}
+
+func (bustedReader) Close() error { return nil }
+
+func TestBustedReader(t *testing.T) {
+	foo := bustedReader{}
+	chOut := BuffScanner(1*time.Second, "heythere", foo, true)
+
+	line, ok := <-chOut
+	if !ok {
+		t.Fail()
+	}
+	want := MsgError + " : multiple Read calls return no data or error"
+	if line != want {
+		t.Errorf("got \n\t%v\nwant\n\t%v", line, want)
+	}
+
+	line, ok = <-chOut
+	if ok {
+		t.Fail()
+	}
+}
+
+type simpleReader struct {
+	io.Reader
+}
+
+func (simpleReader) Close() error { return nil }
+
+func TestSimpleReader(t *testing.T) {
+	foo1 := simpleReader{bytes.NewBufferString("beans and\nrice")}
+	chOut := BuffScanner(1*time.Second, "heythere", foo1, true)
+
+	line, ok := <-chOut
+	if !ok {
+		t.Fail()
+	}
+	want := "beans and"
+	if line != want {
+		t.Errorf("got \n\t%v\nwant\n\t%v", line, want)
+	}
+
+	line, ok = <-chOut
+	if !ok {
+		t.Fail()
+	}
+	want = "rice"
+	if line != want {
+		t.Errorf("got \n\t%v\nwant\n\t%v", line, want)
+	}
+
+	line, ok = <-chOut
+	if ok {
+		t.Fail()
+	}
+}
+
+// An example main.
+func main() {
+	{
+		foo := simpleReader{bytes.NewBufferString("beans and\nrice")}
+		chOut := BuffScanner(1*time.Second, "heythere", foo, true)
+		for line := range chOut {
+			fmt.Println(line)
+		}
+		fmt.Println("-----------------------")
+	}
+	{
+		foo := stalledReader{}
+		chOut := BuffScanner(1*time.Second, "heythere", foo, true)
+		for line := range chOut {
+			fmt.Println(line)
+		}
+		fmt.Println("-----------------------")
+	}
+	{
+		foo := bustedReader{}
+		chOut := BuffScanner(1*time.Second, "heythere", foo, true)
+		for line := range chOut {
+			fmt.Println(line)
+		}
+		fmt.Println("-----------------------")
+	}
+}
diff --git a/go/src/github.com/monopole/mdrip/util/lexer.go b/go/src/github.com/monopole/mdrip/util/lexer.go
new file mode 100644
index 0000000..e766337
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/util/lexer.go
@@ -0,0 +1,318 @@
+// Other than the custom stateFn's, much of this is copied from
+// https://golang.org/src/pkg/text/template/parse/lex.go. Cannot use
+// stuct embedding to reuse, since all the good parts are private.
+
+package util
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"unicode/utf8"
+)
+
+type Pos int
+
+type CommandBlock struct {
+	labels   []string
+	codeText string
+}
+
+func (x CommandBlock) GetLabels() []string {
+	return x.labels
+}
+func (x CommandBlock) GetCodeText() string {
+	return x.codeText
+}
+
+type item struct {
+	typ itemType // Type of this item.
+	val string   // The value of this item.
+}
+
+func (i item) String() string {
+	switch {
+	case i.typ == itemEOF:
+		return "EOF"
+	case i.typ == itemError:
+		return i.val
+	case i.typ == itemBlockLabel:
+		return string(labelMarker) + i.val
+	case i.typ == itemCommandBlock:
+		return "--------\n" + i.val + "--------\n"
+	case len(i.val) > 10:
+		return fmt.Sprintf("%.30s...", i.val)
+	}
+	return fmt.Sprintf("%s", i.val)
+}
+
+type itemType int
+
+const (
+	itemError        itemType = iota
+	itemBlockLabel            // Label for a command block
+	itemCommandBlock          // All lines between codeFence marks
+	itemEOF
+)
+
+const (
+	labelMarker  = '@'
+	commentOpen  = "<!--"
+	commentClose = "-->"
+	codeFence    = "```\n"
+)
+
+const eof = -1
+
+type stateFn func(*lexer) stateFn
+
+type lexer struct {
+	input string    // string being scanned
+	state stateFn   // the next lexing function to enter
+	pos   Pos       // current position in 'input'
+	start Pos       // start of this item
+	width Pos       // width of last rune read
+	items chan item // channel of scanned items
+}
+
+// next returns the next rune in the input.
+func (l *lexer) next() rune {
+	if int(l.pos) >= len(l.input) {
+		l.width = 0
+		return eof
+	}
+	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
+	l.width = Pos(w)
+	l.pos += l.width
+	return r
+}
+
+func (l *lexer) peek() rune {
+	r := l.next()
+	l.backup()
+	return r
+}
+
+func (l *lexer) backup() {
+	l.pos -= l.width
+}
+
+func (l *lexer) emit(t itemType) {
+	l.items <- item{t, l.input[l.start:l.pos]}
+	l.start = l.pos
+}
+
+func (l *lexer) ignore() {
+	l.start = l.pos
+}
+
+// Consumes the next rune if it's from the valid set.
+func (l *lexer) accept(valid string) bool {
+	if strings.IndexRune(valid, l.next()) >= 0 {
+		return true
+	}
+	l.backup()
+	return false
+}
+
+// Consumes a run of runes from the valid set
+func (l *lexer) acceptRun(valid string) {
+	// is the next character of the input an element
+	// of the (defining) 'valid' set of runes (a string).
+	for strings.IndexRune(valid, l.next()) >= 0 {
+	}
+	l.backup()
+}
+
+func (l *lexer) acceptWord() {
+	l.acceptRun("012345789abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+}
+
+// errorf returns an error token and terminates the scan by passing
+// back a nil pointer that will be the next state, terminating l.nextItem.
+func (l *lexer) errorf(format string, args ...interface{}) stateFn {
+	l.items <- item{itemError, fmt.Sprintf(format, args...)}
+	return nil
+}
+
+// nextItem returns the next item from the input.
+func (l *lexer) nextItem() item {
+	item := <-l.items
+	return item
+}
+
+// newLex creates a new scanner for the input string.
+func newLex(input string) *lexer {
+	l := &lexer{
+		input: input,
+		items: make(chan item),
+	}
+	go l.run()
+	return l
+}
+
+func (l *lexer) run() {
+	for l.state = lexText; l.state != nil; {
+		l.state = l.state(l)
+	}
+}
+
+func isSpace(r rune) bool {
+	return r == ' ' || r == '\t'
+}
+
+func isEndOfLine(r rune) bool {
+	return r == '\r' || r == '\n'
+}
+
+// lexText scans until an opening comment delimiter.
+func lexText(l *lexer) stateFn {
+	for {
+		if strings.HasPrefix(l.input[l.pos:], commentOpen) {
+			return lexPutativeComment
+		}
+		if l.next() == eof {
+			l.ignore()
+			l.emit(itemEOF)
+			return nil
+		}
+	}
+}
+
+// Move to lexing a command block intended for a particular script, or to
+// lexing a simple comment.  Comment opener known to be present.
+func lexPutativeComment(l *lexer) stateFn {
+	l.pos += Pos(len(commentOpen))
+	for {
+		switch r := l.next(); {
+		case isSpace(r):
+			l.ignore()
+		case r == labelMarker:
+			l.backup()
+			return lexBlockLabels
+		default:
+			l.backup()
+			return lexCommentRemainder
+		}
+	}
+}
+
+// lexCommentRemainder assumes a comment opener was read,
+// and eats everything up to and including the comment closer.
+func lexCommentRemainder(l *lexer) stateFn {
+	i := strings.Index(l.input[l.pos:], commentClose)
+	if i < 0 {
+		return l.errorf("unclosed comment")
+	}
+	l.pos += Pos(i + len(commentClose))
+	l.ignore()
+	return lexText
+}
+
+// lexBlockLabels scans a string like "@1 @hey" emitting the labels
+// "1" and "hey".  LabelMarker known to be present.
+func lexBlockLabels(l *lexer) stateFn {
+	for {
+		switch r := l.next(); {
+		case r == eof || isEndOfLine(r):
+			return l.errorf("unclosed block label sequence")
+		case isSpace(r):
+			l.ignore()
+		case r == labelMarker:
+			l.ignore()
+			l.acceptWord()
+			if l.width == 0 {
+				return l.errorf("empty block label")
+			}
+			l.emit(itemBlockLabel)
+		default:
+			l.backup()
+			if !strings.HasPrefix(l.input[l.pos:], commentClose) {
+				return l.errorf("improperly closed block label sequence")
+			}
+			l.pos += Pos(len(commentClose))
+			l.ignore()
+			l.acceptRun(" \t")
+			l.ignore()
+			r := l.next()
+			if r != '\n' && r != '\r' {
+				return l.errorf("Expected command block marker at start of line.")
+			}
+			l.ignore()
+			if !strings.HasPrefix(l.input[l.pos:], codeFence) {
+				return l.errorf("Expected command block mark, got: " + l.input[l.pos:])
+			}
+			return lexCommandBlock
+		}
+	}
+	return lexText
+}
+
+// lexCommandBlock scans a command block.  Initial marker known to be present.
+func lexCommandBlock(l *lexer) stateFn {
+	l.pos += Pos(len(codeFence))
+	l.ignore()
+	for {
+		if strings.HasPrefix(l.input[l.pos:], codeFence) {
+			if l.pos > l.start {
+				l.emit(itemCommandBlock)
+			}
+			l.pos += Pos(len(codeFence))
+			l.ignore()
+			return lexText
+		}
+		if l.next() == eof {
+			return l.errorf("unclosed command block")
+		}
+	}
+}
+
+func shouldSleep(labels []string) bool {
+	for _, label := range labels {
+		if label == "sleep" {
+			return true
+		}
+	}
+	return false
+}
+
+// Parse lexes the incoming string into a mapping from block label to
+// CommandBlock array.  The labels are the strings after a labelMarker in
+// a comment preceding a command block.  Arrays hold command blocks in the
+// order they appeared in the input.
+func Parse(s string) (result map[string][]*CommandBlock) {
+	result = make(map[string][]*CommandBlock)
+	currentLabels := make([]string, 0, 10)
+	l := newLex(s)
+	for {
+		item := l.nextItem()
+		switch {
+		case item.typ == itemEOF || item.typ == itemError:
+			return
+		case item.typ == itemBlockLabel:
+			currentLabels = append(currentLabels, item.val)
+		case item.typ == itemCommandBlock:
+			if len(currentLabels) == 0 {
+				fmt.Println("Have an unlabelled command block:\n " + item.val)
+				os.Exit(1)
+			}
+			// If the command block has a 'sleep' label, add a brief sleep
+			// at the end.  This is hack to give servers placed in the
+			// background time to start.
+			if shouldSleep(currentLabels) {
+				item.val = item.val + "sleep 2s # Added by mdrip\n"
+			}
+			newBlock := &CommandBlock{currentLabels, item.val}
+			for _, label := range currentLabels {
+				blocks, ok := result[label]
+				if ok {
+					blocks = append(blocks, newBlock)
+				} else {
+					blocks = []*CommandBlock{newBlock}
+				}
+				result[label] = blocks
+			}
+			currentLabels = make([]string, 0, 10)
+		}
+	}
+}
diff --git a/go/src/github.com/monopole/mdrip/util/lexer_test.go b/go/src/github.com/monopole/mdrip/util/lexer_test.go
new file mode 100644
index 0000000..3347549
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/util/lexer_test.go
@@ -0,0 +1,90 @@
+package util
+
+import (
+	"fmt"
+	"testing"
+)
+
+type lexTest struct {
+	name  string // Name of the sub-test.
+	input string // Input string to be lexed.
+	want  []item // Expected items produced by lexer.
+}
+
+const (
+	block1 = "echo $PATH\n" +
+		"echo $GOPATH"
+	block2 = "kill -9 $pid"
+)
+
+var (
+	tEOF = item{itemEOF, ""}
+)
+
+var lexTests = []lexTest{
+	{"empty", "", []item{tEOF}},
+	{"spaces", " \t\n", []item{tEOF}},
+	{"text", "blah blah blinkity blah", []item{tEOF}},
+	{"comment1", "<!-- -->", []item{tEOF}},
+	{"comment2", "a <!-- --> b", []item{tEOF}},
+	{"block1", "aa <!-- @1 -->\n" +
+		"```\n" + block1 + "```\n bbb",
+		[]item{{itemBlockLabel, "1"},
+			{itemCommandBlock, block1},
+			tEOF}},
+	{"block2", "aa <!-- @1 @2-->\n" +
+		"```\n" + block1 + "```\n bb cc\n" +
+		"dd <!-- @3 @4-->\n" +
+		"```\n" + block2 + "```\n ee ff\n",
+		[]item{
+			{itemBlockLabel, "1"},
+			{itemBlockLabel, "2"},
+			{itemCommandBlock, block1},
+			{itemBlockLabel, "3"},
+			{itemBlockLabel, "4"},
+			{itemCommandBlock, block2},
+			tEOF}},
+}
+
+// collect gathers the emitted items into a slice.
+func collect(t *lexTest) (items []item) {
+	l := newLex(t.input)
+	for {
+		item := l.nextItem()
+		items = append(items, item)
+		if item.typ == itemEOF || item.typ == itemError {
+			break
+		}
+	}
+	return
+}
+
+func equal(i1, i2 []item) bool {
+	if len(i1) != len(i2) {
+		return false
+	}
+	for k := range i1 {
+		if i1[k].typ != i2[k].typ {
+			fmt.Printf("types not equal - got : %s\n", i1[k].typ)
+			fmt.Printf("types not equal - want: %s\n", i2[k].typ)
+			fmt.Printf("\n")
+			return false
+		}
+		if i1[k].val != i2[k].val {
+			fmt.Printf("vals not equal - got : %q\n", i1[k].val)
+			fmt.Printf("vals not equal - want: %q\n", i2[k].val)
+			fmt.Printf("\n")
+			return false
+		}
+	}
+	return true
+}
+
+func TestLex(t *testing.T) {
+	for _, test := range lexTests {
+		got := collect(&test)
+		if !equal(got, test.want) {
+			t.Errorf("%s:\ngot\n\t%+v\nexpected\n\t%v", test.name, got, test.want)
+		}
+	}
+}
diff --git a/go/src/github.com/monopole/mdrip/util/pid.go b/go/src/github.com/monopole/mdrip/util/pid.go
new file mode 100644
index 0000000..62051ff
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/util/pid.go
@@ -0,0 +1,40 @@
+package util
+
+import (
+	"errors"
+	"fmt"
+	"os/exec"
+	"strconv"
+	"strings"
+)
+
+// getProcessGroupId purports to get a process group Id common to all
+// subprocesses of its pid argument.
+//
+// There should be a better way to do this.
+//
+// Goal is to be able to support killing any subprocesses created by
+// RunInSubShell.  At the moment, its up to command script authors to
+// clean up after themselves.
+func getProcesssGroupId(pid int) (int, error) {
+	//  /bin/ps -o pid,pgid,rgid,ppid,cmd
+	//  /bin/ps -o pgid=12492 --no-headers
+	cmdOut, execErr := exec.Command(
+		"/bin/ps", "--pid", strconv.Itoa(pid), "-o", "pgid", "--no-headers").Output()
+	groupId := strings.TrimSpace(string(cmdOut))
+	if execErr != nil || len(groupId) < 1 {
+		return 0, errors.New(
+			"Unable to yank groupId from ps command: " + groupId + " " + execErr.Error())
+	}
+	pgid, convErr := strconv.Atoi(groupId)
+	if convErr != nil {
+		return 0, convErr
+	}
+	return pgid, nil
+}
+
+// An attempt to kill any and all child processes.
+func killProcesssGroup(pgid int) {
+	killer := exec.Command("/bin/kill", "-TERM", "--", fmt.Sprintf("-%v", pgid))
+	killer.Start()
+}
diff --git a/go/src/github.com/monopole/mdrip/util/runner.go b/go/src/github.com/monopole/mdrip/util/runner.go
new file mode 100644
index 0000000..bb0abbd
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/util/runner.go
@@ -0,0 +1,318 @@
+package util
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+)
+
+// Should switch to a logging package that supports levels,
+// e.g. https://github.com/golang/glog
+var debug = flag.Bool("debug", false,
+	"If true, dump more information during run.")
+
+// check reports the error fatally if its non-nil.
+func check(msg string, err error) {
+	if err != nil {
+		fmt.Printf("Problem with %s\n", msg, err)
+		log.Fatal(err)
+	}
+}
+
+// blockOutput pairs the output collected from a stream (i.e. stderr
+// or stdout) as a result of executing all or part of a command block
+// with a bool indicating if the output is associated with shell
+// success or shell failure.  Output can appear on stderr without
+// neccessarily being associated with shell failure.
+type blockOutput struct {
+	success bool
+	output  string
+}
+
+// accumulateOutput returns a channel to which it writes objects that
+// contain what purport to be the entire output of one command block.
+//
+// To do so, it accumulates strings off a channel representing command
+// block output until the channel closes, or until a string arrives
+// that matches a particular pattern.
+//
+// On the happy path, strings are accumulated and every so often sent
+// out with a success == true flag attached.  This continues until the
+// input channel closes.
+//
+// On a sad path, an accumulation of strings is sent with a success ==
+// false flag attached, and the function exits early, before it's
+// input channel closes.
+func accumulateOutput(prefix string, in <-chan string) <-chan *blockOutput {
+	out := make(chan *blockOutput)
+	var accum bytes.Buffer
+	go func() {
+		defer close(out)
+		for line := range in {
+			if strings.HasPrefix(line, MsgTimeout) {
+				accum.WriteString("\n" + line + "\n")
+				accum.WriteString("A subprocess might still be running.\n")
+				if *debug {
+					fmt.Printf("DEBUG: accumulateOutput %s: Timeout return.\n", prefix)
+				}
+				out <- &blockOutput{false, accum.String()}
+				return
+			}
+			if strings.HasPrefix(line, MsgError) {
+				accum.WriteString(line + "\n")
+				if *debug {
+					fmt.Printf("DEBUG: accumulateOutput %s: Error return.\n", prefix)
+				}
+				out <- &blockOutput{false, accum.String()}
+				return
+			}
+			if strings.HasPrefix(line, MsgHappy) {
+				if *debug {
+					fmt.Printf("DEBUG: accumulateOutput %s: %s\n", prefix, line)
+				}
+				out <- &blockOutput{true, accum.String()}
+				accum.Reset()
+			} else {
+				if *debug {
+					fmt.Printf("DEBUG: accumulateOutput %s: Accumulating [%s]\n", prefix, line)
+				}
+				accum.WriteString(line + "\n")
+			}
+		}
+
+		if *debug {
+			fmt.Printf("DEBUG: accumulateOutput %s: <--- This channel has closed.\n", prefix)
+		}
+		trailing := strings.TrimSpace(accum.String())
+		if len(trailing) > 0 {
+			if *debug {
+				fmt.Printf(
+					"DEBUG: accumulateOutput %s: Erroneous (missing-happy) output [%s]\n", prefix, accum.String())
+			}
+			out <- &blockOutput{false, accum.String()}
+		} else {
+			if *debug {
+				fmt.Printf("DEBUG: accumulateOutput %s: Nothing trailing.\n", prefix)
+			}
+		}
+	}()
+	return out
+}
+
+// ScriptResult pairs blockOutput with meta data about shell execution.
+type ScriptResult struct {
+	blockOutput
+	fileName string        // File in which the error occurred.
+	index    int           // Command block index.
+	block    *CommandBlock // Content of actual command block.
+	problem  error         // Error, if any.
+	message  string        // Detailed error message, if any.
+}
+
+func (x ScriptResult) GetFileName() string {
+	return x.fileName
+}
+
+func (x ScriptResult) GetProblem() error {
+	return x.problem
+}
+
+// ScriptBucket associates a list of commandBlocks with the name of the
+// file they came from.
+type ScriptBucket struct {
+	fileName string
+	script   []*CommandBlock
+}
+
+func (x ScriptBucket) GetFileName() string {
+	return x.fileName
+}
+func (x ScriptBucket) GetScript() []*CommandBlock {
+	return x.script
+}
+func NewScriptBucket(fileName string, script []*CommandBlock) *ScriptBucket {
+	return &ScriptBucket{fileName, script}
+}
+
+// userBehavior acts like a command line user.
+//
+// It writes command blocks to shell, then waits after  each block to
+// see if the block worked.  If the block appeared to complete without
+// error, the routine sends the next block, else it exits early.
+func userBehavior(stdIn io.Writer, scriptBuckets []*ScriptBucket, blockTimeout time.Duration,
+	stdOut, stdErr io.ReadCloser) (errResult *ScriptResult) {
+	emptyArray := []string{}
+
+	chOut := BuffScanner(blockTimeout, "stdout", stdOut, *debug)
+	chErr := BuffScanner(1*time.Minute, "stderr", stdErr, *debug)
+
+	chAccOut := accumulateOutput("stdOut", chOut)
+	chAccErr := accumulateOutput("stdErr", chErr)
+
+	errResult = &ScriptResult{blockOutput{false, ""}, "", -1, &CommandBlock{emptyArray, ""}, nil, ""}
+	for _, bucket := range scriptBuckets {
+		for i, block := range bucket.script {
+			blockName := block.labels[0]
+			fmt.Printf("Running %s (%d/%d) from %s\n",
+				blockName, i+1, len(bucket.script), bucket.fileName)
+			if *debug {
+				fmt.Printf("DEBUG: userBehavior: sending \"%s\"\n", block.codeText)
+			}
+			_, err := stdIn.Write([]byte(block.codeText))
+			check("write script", err)
+			if *debug {
+				fmt.Printf("DEBUG: userBehavior: sending happy\n")
+			}
+			_, err = stdIn.Write([]byte("\necho " + MsgHappy + " " + blockName + "\n"))
+			check("write msgHappy", err)
+
+			result := <-chAccOut
+
+			if result == nil || !result.success {
+				// A nil result means stdout has closed early because a
+				// sub-subprocess failed.
+				if result == nil {
+					if *debug {
+						fmt.Printf("DEBUG: userBehavior: stdout Result == nil.\n")
+						// fmt.Printf("DEBUG: userBehavior: sending warning to stdErr\n")
+					}
+					//					chErr <- MsgError + " : early termination; stdout has closed."
+				} else {
+					if *debug {
+						fmt.Printf("DEBUG: userBehavior: stdout Result: %s\n", result.output)
+					}
+					// Shell may still be alive despite a failure (e.g. an mdrip
+					// imposed timeout).  Maybe send exit.
+					exitShell(stdIn)
+					errResult.output = result.output
+					errResult.message = result.output
+				}
+				errResult.fileName = bucket.fileName
+				errResult.index = i
+				errResult.block = block
+				fillErrResult(chAccErr, errResult)
+				return
+			}
+		}
+	}
+	exitShell(stdIn)
+	fmt.Printf("All done, no errors triggered.\n")
+	return
+}
+
+// fillErrResult fills an instance of ScriptResult.
+func fillErrResult(chAccErr <-chan *blockOutput, errResult *ScriptResult) {
+	result := <-chAccErr
+	if result == nil {
+		if *debug {
+			fmt.Printf("DEBUG: userBehavior: stderr Result == nil.\n")
+		}
+		errResult.problem = errors.New("unknown")
+		return
+	}
+	errResult.problem = errors.New(result.output)
+	errResult.message = result.output
+	if *debug {
+		fmt.Printf("DEBUG: userBehavior: stderr Result: %s\n", result.output)
+	}
+}
+
+func exitShell(stdIn io.Writer) {
+	if *debug {
+		fmt.Printf("DEBUG: userBehavior: exiting subshell.\n")
+	}
+	stdIn.Write([]byte("exit\n"))
+	// Don't check for error - it either works, or we'll have
+	// already reported a failed shell.
+}
+
+func dumpCapturedOutput(name, delim, output string) {
+	fmt.Fprintf(os.Stderr, "\n%s capture:\n", name)
+	fmt.Fprintf(os.Stderr, delim)
+	fmt.Fprintf(os.Stderr, output)
+	fmt.Fprintf(os.Stderr, "\n")
+	fmt.Fprintf(os.Stderr, delim)
+}
+
+// Complain spits the contents of a ScriptResult to stderr.
+func Complain(result *ScriptResult, label string) {
+	delim := strings.Repeat("-", 70) + "\n"
+	fmt.Fprintf(os.Stderr, "Error in block '%s' (#%d of script '%s') in %s:\n",
+		result.block.labels[0], result.index+1, label, result.fileName)
+	fmt.Fprintf(os.Stderr, delim)
+	fmt.Fprintf(os.Stderr, string(result.block.codeText))
+	fmt.Fprintf(os.Stderr, delim)
+	dumpCapturedOutput("Stdout", delim, result.output)
+	if len(result.message) > 0 {
+		dumpCapturedOutput("Stderr", delim, result.message)
+	}
+}
+
+// RunInSubShell runs command blocks in a subprocess, stopping and
+// reporting on any error.  The subprocess runs with the -e flag, so
+// it will abort if any sub-subprocess (any command) fails.
+//
+// Command blocks are strings presumably holding code from some shell
+// language.  The strings may be more complex than single commands
+// delimitted by linefeeds - e.g. blocks that operate on HERE
+// documents, or multi-line commands using line continuation via '\',
+// quotes or curly brackets.
+//
+// This function itself is not a shell interpreter, so it has no idea
+// if one line of text from a command block is an individual command
+// or part of something else.
+//
+// Error reporting works by discarding output from command blocks that
+// succeeded, and only reporting the contents of stdout and stderr
+// when the subprocess exits on error.
+func RunInSubShell(scriptBuckets []*ScriptBucket, blockTimeout time.Duration) (
+	result *ScriptResult) {
+	// Adding "-e" to force the subshell to die on any error.
+	shell := exec.Command("bash", "-e")
+
+	stdOut, err := shell.StdoutPipe()
+	check("out pipe", err)
+
+	stdErr, err := shell.StderrPipe()
+	check("err pipe", err)
+
+	stdIn, err := shell.StdinPipe()
+	check("in pipe", err)
+
+	err = shell.Start()
+	check("shell start", err)
+
+	pid := shell.Process.Pid
+	if *debug {
+		fmt.Printf("DEBUG: RunInSubShell: pid = %d\n", pid)
+	}
+	pgid, err := getProcesssGroupId(pid)
+	if err == nil {
+		if *debug {
+			fmt.Printf("DEBUG: RunInSubShell:  pgid = %d\n", pgid)
+		}
+	}
+
+	result = userBehavior(stdIn, scriptBuckets, blockTimeout, stdOut, stdErr)
+
+	if *debug {
+		fmt.Printf("DEBUG: RunInSubShell:  Waiting for shell to end.\n")
+	}
+	waitError := shell.Wait()
+	if result.problem == nil {
+		result.problem = waitError
+	}
+	if *debug {
+		fmt.Printf("DEBUG: RunInSubShell:  Shell done.\n")
+	}
+
+	// killProcesssGroup(pgid)
+	return
+}
diff --git a/go/src/github.com/monopole/mdrip/util/runner_test.go b/go/src/github.com/monopole/mdrip/util/runner_test.go
new file mode 100644
index 0000000..9bffcba
--- /dev/null
+++ b/go/src/github.com/monopole/mdrip/util/runner_test.go
@@ -0,0 +1,100 @@
+package util
+
+import (
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+)
+
+var emptyArray []string = []string{}
+var emptyCommandBlock *CommandBlock = &CommandBlock{emptyArray, ""}
+
+const timeoutSeconds = 1
+
+func TestRunnerWithNothing(t *testing.T) {
+	if RunInSubShell([]*ScriptBucket{}, timeoutSeconds*time.Second).problem != nil {
+		t.Fail()
+	}
+}
+
+func doIt(blocks []*CommandBlock) *ScriptResult {
+	return RunInSubShell([]*ScriptBucket{&ScriptBucket{"iAmFileName", blocks}}, timeoutSeconds*time.Second)
+}
+
+func TestRunnerWithGoodStuff(t *testing.T) {
+	labels := []string{"foo", "bar"}
+	blocks := []*CommandBlock{
+		&CommandBlock{labels, "echo kale\ndate\n"},
+		&CommandBlock{labels, "echo beans\necho apple\n"},
+		&CommandBlock{labels, "echo hasta\necho la vista\n"}}
+	result := doIt(blocks)
+	if result.problem != nil {
+		t.Fail()
+	}
+}
+
+func checkFail(t *testing.T, got, want *ScriptResult) {
+	if got.problem == nil {
+		t.Fail()
+	}
+	if got.index != want.index {
+		t.Errorf("%s got\n\t%v\nwant\n\t%v", "script", got.index, want.index)
+	}
+	if !strings.Contains(got.message, want.message) {
+		t.Errorf("%s got\n\t%v\nwant\n\t%v", "message", got.message, want.message)
+	}
+}
+
+func TestStartWithABadCommand(t *testing.T) {
+	want := &ScriptResult{
+		blockOutput{false, "dunno"},
+		"fileNameTestStartWithABadCommand",
+		0,
+		emptyCommandBlock,
+		nil,
+		"bash: line 1: notagoodcommand: command not found"}
+
+	labels := []string{"foo", "bar"}
+	blocks := []*CommandBlock{
+		&CommandBlock{labels, "notagoodcommand\ndate\n"},
+		&CommandBlock{labels, "echo beans\necho cheese\n"}}
+	checkFail(t, doIt(blocks), want)
+}
+
+func TestBadCommandInTheMiddle(t *testing.T) {
+	want := &ScriptResult{
+		blockOutput{false, "dunno"},
+		"fileNameTestBadCommandInTheMiddle",
+		2,
+		emptyCommandBlock,
+		nil,
+		"bash: line 9: lochNessMonster: command not found"}
+
+	labels := []string{"foo", "bar"}
+
+	blocks := []*CommandBlock{
+		&CommandBlock{labels, "echo tofu\ndate\n"},
+		&CommandBlock{labels, "echo beans\necho kale\n"},
+		&CommandBlock{labels, "lochNessMonster\n"},
+		&CommandBlock{labels, "echo hasta\necho la vista\n"}}
+
+	checkFail(t, doIt(blocks), want)
+}
+
+func TestTimeOut(t *testing.T) {
+	want := &ScriptResult{
+		blockOutput{false, "dunno"},
+		"fileNameTestTimeOut",
+		0,
+		emptyCommandBlock,
+		nil,
+		MsgTimeout}
+
+	labels := []string{"foo", "bar"}
+	// Go to sleep for twice the length of the timeout.
+	blocks := []*CommandBlock{
+		&CommandBlock{labels, "date\nsleep " + strconv.Itoa(timeoutSeconds+2) + "\necho kale"},
+		&CommandBlock{labels, "echo beans\necho cheese\n"}}
+	checkFail(t, doIt(blocks), want)
+}