diff --git a/caddy/caddymain/run_test.go b/caddy/caddymain/run_test.go
new file mode 100644
index 000000000..d14abffe1
--- /dev/null
+++ b/caddy/caddymain/run_test.go
@@ -0,0 +1,44 @@
+package caddymain
+
+import (
+	"runtime"
+	"testing"
+)
+
+func TestSetCPU(t *testing.T) {
+	currentCPU := runtime.GOMAXPROCS(-1)
+	maxCPU := runtime.NumCPU()
+	halfCPU := int(0.5 * float32(maxCPU))
+	if halfCPU < 1 {
+		halfCPU = 1
+	}
+	for i, test := range []struct {
+		input     string
+		output    int
+		shouldErr bool
+	}{
+		{"1", 1, false},
+		{"-1", currentCPU, true},
+		{"0", currentCPU, true},
+		{"100%", maxCPU, false},
+		{"50%", halfCPU, false},
+		{"110%", currentCPU, true},
+		{"-10%", currentCPU, true},
+		{"invalid input", currentCPU, true},
+		{"invalid input%", currentCPU, true},
+		{"9999", maxCPU, false}, // over available CPU
+	} {
+		err := setCPU(test.input)
+		if test.shouldErr && err == nil {
+			t.Errorf("Test %d: Expected error, but there wasn't any", i)
+		}
+		if !test.shouldErr && err != nil {
+			t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
+		}
+		if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected {
+			t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected)
+		}
+		// teardown
+		runtime.GOMAXPROCS(currentCPU)
+	}
+}
diff --git a/caddy/main.go b/caddy/main.go
index 4559be03a..fab5a33e7 100644
--- a/caddy/main.go
+++ b/caddy/main.go
@@ -1,7 +1,14 @@
+// By moving the application's package main logic into
+// a package other than main, it becomes much easier to
+// wrap caddy for custom builds that are go-gettable.
+// https://forum.caddyserver.com/t/my-wish-for-0-9-go-gettable-custom-builds/59?u=matt
+
 package main
 
 import "github.com/mholt/caddy/caddy/caddymain"
 
+var run = caddymain.Run // replaced for tests
+
 func main() {
-	caddymain.Run()
+	run()
 }
diff --git a/caddy/main_test.go b/caddy/main_test.go
new file mode 100644
index 000000000..52063a753
--- /dev/null
+++ b/caddy/main_test.go
@@ -0,0 +1,17 @@
+package main
+
+import "testing"
+
+// This works because it does not have the same signature as the
+// conventional "TestMain" function described in the testing package
+// godoc.
+func TestMain(t *testing.T) {
+	var ran bool
+	run = func() {
+		ran = true
+	}
+	main()
+	if !ran {
+		t.Error("Expected Run() to be called, but it wasn't")
+	}
+}
diff --git a/caddyhttp/caddyhttp_test.go b/caddyhttp/caddyhttp_test.go
new file mode 100644
index 000000000..b228ab18d
--- /dev/null
+++ b/caddyhttp/caddyhttp_test.go
@@ -0,0 +1,19 @@
+package caddyhttp
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/mholt/caddy"
+)
+
+// TODO: this test could be improved; the purpose is to
+// ensure that the standard plugins are in fact plugged in
+// and registered properly; this is a quick/naive way to do it.
+func TestStandardPlugins(t *testing.T) {
+	numStandardPlugins := 25 // importing caddyhttp plugs in this many plugins
+	s := caddy.DescribePlugins()
+	if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want {
+		t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s)
+	}
+}
diff --git a/caddyhttp/markdown/summary/summary_test.go b/caddyhttp/markdown/summary/summary_test.go
new file mode 100644
index 000000000..f87c00db0
--- /dev/null
+++ b/caddyhttp/markdown/summary/summary_test.go
@@ -0,0 +1,11 @@
+package summary
+
+import "testing"
+
+func TestMarkdown(t *testing.T) {
+	input := []byte(`Testing with just a few words.`)
+	got := string(Markdown(input, 3))
+	if want := "Testing with just"; want != got {
+		t.Errorf("Expected '%s' but got '%s'", want, got)
+	}
+}
diff --git a/dist/automate_test.go b/dist/automate_test.go
new file mode 100644
index 000000000..717114512
--- /dev/null
+++ b/dist/automate_test.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+	"runtime"
+	"testing"
+)
+
+func TestNumProcs(t *testing.T) {
+	n := numProcs()
+	if n != runtime.NumCPU()-1 {
+		t.Errorf("Expected numProcs to return NumCPU-1, got %d", n)
+	}
+}