Skip to content

Instantly share code, notes, and snippets.

@zaphar
Created July 13, 2011 03:13
Show Gist options
  • Save zaphar/1079641 to your computer and use it in GitHub Desktop.
Save zaphar/1079641 to your computer and use it in GitHub Desktop.
/*
go-tap is a simple Test Anything Protocol unittest framework for Golang.
package main
import "tap"
type MyTest struct {}
func (t *MyTest)Tests() [] []func(*tap.Recorder) {
f := func(r *tap.Recorder) {
Ok(r, true, "this is a message for %s", true)
Diag(r, "this is a diagnostic message\non %s lines", 2)
}
return []func(*tap.Recorder){f}
}
func init() { tap.Register(new(MyTest)) }
func main() { tap.Main() }
If you want to run setup or cleanup code then implement as many of the 4
(Pre|Post)(Suite|Test)Runner interfaces as appropriate.
*/
package tap
import (
"fmt"
"io"
"os"
"strings"
)
var suites []Suite
// TODO(jwall): a default consumer that examines output and informs as to
// overall failure or success
// A Tap Stream recorder. It handles outputting the Tap Stream for
// your test Suite
type Recorder struct {
expected int
count int
out io.Writer
diagOut io.Writer
}
// A Constructor for a Recorder allowing you setup your own consumer
// for a tap stream.
func NewRecorder(out, diagOut io.Writer) *Recorder {
return &Recorder{out: out, diagOut: diagOut}
}
// The default recorder for easy test setup
var DefaultRecorder = NewRecorder(os.Stdout, os.Stdout)
func (r *Recorder) reset() {
r.expected = 0
r.count = 0
}
func (r *Recorder) start() {
if r.expected > 0 {
fmt.Fprintf(r.out, "1..%d\n", r.expected)
}
}
func (r *Recorder) diag(msg string, args ...interface{}) {
for _, m := range strings.Split(fmt.Sprintf(msg, args...), "\n", -1) {
fmt.Fprintln(r.diagOut, "# "+m)
}
}
func (r *Recorder) ok(result bool, msg string, args ...interface{}) bool {
r.count++
start := fmt.Sprintf("ok %d - ", r.count)
if result {
fmt.Fprintf(r.out, start+msg+"\n", args...)
} else {
fmt.Fprintf(r.out, "not "+start+msg+"\n", args...)
}
return result
}
// Primitive Diag function for printing diagnostic messages
func Diag(r *Recorder, msg string, args ...interface{}) {
r.diag(msg, args...)
}
// Primitive Assertion test for boolean true with informative message
func Ok(r *Recorder, result bool, msg string, args ...interface{}) bool {
return r.ok(result, msg, args...)
}
// Base interface for a Test Suite
type Suite interface {
// Should return the TAP plan for the test suite
Plan() int
// return the test functions
Tests() []func(t *Recorder)
}
// Interface for a Suite with Pre Test setup code
type PreTestRunner interface {
Suite
// Run once for each test in suite at the beginning
PreTest(r *Recorder)
}
// Interface for a Suite with Post Test cleanup code
type PostTestRunner interface {
Suite
// Run once for each test in suite at the end
PostTest(r *Recorder)
}
// Interface for a Suite with Pre Suite setup code
type PreSuiteRunner interface {
Suite
// Run once for the suite at the beginning
PreSuite(r *Recorder)
}
// Interface for a Suite with Post Suite cleanup code
type PostSuiteRunner interface {
Suite
// Run once for the suite at the end
PostSuite(r *Recorder)
}
// Register a Suite with the default test harness
func Register(t... Suite) {
suites = append(suites, t...)
}
// Run a Suite with a *Recorder
func RunSuite(recorder *Recorder, t Suite) {
recorder.reset()
recorder.expected = t.Plan()
recorder.start()
// if we a PreSuite run it.
if pre, ok := t.(PreSuiteRunner); ok {
pre.PreSuite(recorder)
}
for _, tst := range t.Tests() {
if pre, ok := t.(PreTestRunner); ok {
pre.PreTest(recorder)
}
tst(recorder)
if post, ok := t.(PostTestRunner); ok {
post.PostTest(recorder)
}
}
if post, ok := t.(PostSuiteRunner); ok {
post.PostSuite(recorder)
}
}
// Run a List of Suites with a *Recorder
func RunSuites(r *Recorder, suites... Suite) {
for _, suite := range suites {
RunSuite(r, suite)
}
}
// Main entry point for the test harness with default *Recorder
func Main() {
WithRecorder(DefaultRecorder)
}
// Entry point for test harness for a custom *Recorder
func WithRecorder(r *Recorder) {
RunSuites(r, suites...)
}
// Copyright (C) 2011 Jeremy Wall (jeremy@marzhillstudios.com)
// Contents Available under the terms of the Artistic License 2.0
package main
import (
. "tap"
"os"
"strings"
)
type TapSelfTests struct{} // implements just TapSuite
func (s *TapSelfTests) Plan() int { return 2 }
func (s *TapSelfTests) Tests() []func(*Recorder) {
f := func(r *Recorder) {
Ok(r, true, "this is %t", true)
Diag(r, "a diagnostic %s", "message")
Ok(r, false, "this is %t", false)
Diag(r, "a multiline diagnostic %s\nYay!!", "message")
}
return []func(*Recorder){f}
}
type TapSelfTestPreSuites struct { // implements TapPostSuiteRunner
TapSelfTests
}
func (t *TapSelfTestPreSuites) PreSuite(r *Recorder) {
Diag(r, "Ran PreSuite")
}
type TapSelfTestPostSuites struct { // implements TapPreSuiteRunner
TapSelfTests
}
func (t *TapSelfTestPostSuites) PostSuite(r *Recorder) {
Diag(r, "Ran PostSuite")
}
type TapSelfTestPreTests struct { // implements TapPostTestRunner
TapSelfTests
}
func (t *TapSelfTestPreTests) PreTest(r *Recorder) {
Diag(r, "Ran PreTest")
}
type TapSelfTestPostTests struct { // implements TapPreTestRunner
TapSelfTests
}
func (t *TapSelfTestPostTests) PostTest(r *Recorder) {
Diag(r, "Ran PostTest")
}
var SelfTests = new(TapSelfTests)
var PreSuiteTest = new(TapSelfTestPreSuites)
var PostSuiteTest = new(TapSelfTestPostSuites)
var PreTestTest = new(TapSelfTestPreTests)
var PostTestTest = new(TapSelfTestPostTests)
type TapTestStream struct {}
func (s *TapTestStream) Plan() int { return 14 }
type StringWriter struct {
contents []byte
}
func (s *StringWriter) Write(bs []byte) (n int, err os.Error) {
s.contents = append(s.contents, bs...)
return len(bs), nil
}
func (s *StringWriter) String() string {
return string(s.contents)
}
func (s *TapTestStream) Tests() []func(*Recorder) {
f := func(r *Recorder) {
out := new(StringWriter)
testRecorder := NewRecorder(out, out)
RunSuite(testRecorder, SelfTests)
prefix := "1..2\n"
output := out.String()
Diag(r, "Output:")
Diag(r, output)
Ok(r, strings.HasPrefix(output, prefix),
"output has prefix: %s", prefix[:4])
list := strings.Split(output, "\n", -1)
Ok(r, list[1] == "ok 1 - this is true",
"Second line is a diagnostic message")
Ok(r, list[2] == "# a diagnostic message",
"Third line is a diagnostic message")
Ok(r, list[3] == "not ok 2 - this is false",
"fourth line failed test")
Ok(r, list[5] == "# Yay!!",
"Last line is last part of multiline diagnostic")
lines := len(list)
Ok(r, lines == 7, "output was %d lines", lines)
}
f2 := func(r *Recorder) {
out := new(StringWriter)
testRecorder := NewRecorder(out, out)
RunSuite(testRecorder, PreSuiteTest)
output := out.String()
Diag(r, "Output:")
Diag(r, output)
list := strings.Split(output, "\n", -1)
lines := len(list)
Ok(r, list[1] == "# Ran PreSuite",
"second line was %s", list[1])
Ok(r, lines == 8, "output was %d lines", lines)
}
f3 := func(r *Recorder) {
out := new(StringWriter)
testRecorder := NewRecorder(out, out)
RunSuite(testRecorder, PostSuiteTest)
output := out.String()
Diag(r, "Output:")
Diag(r, output)
list := strings.Split(output, "\n", -1)
lines := len(list)
Ok(r, list[6] == "# Ran PostSuite",
"second line was %s", list[6])
Ok(r, lines == 8, "output was %d lines", lines)
}
f4 := func(r *Recorder) {
out := new(StringWriter)
testRecorder := NewRecorder(out, out)
RunSuite(testRecorder, PreTestTest)
output := out.String()
Diag(r, "Output:")
Diag(r, output)
list := strings.Split(output, "\n", -1)
lines := len(list)
Ok(r, list[1] == "# Ran PreTest",
"second line was %s", list[1])
Ok(r, lines == 8, "output was %d lines", lines)
}
f5 := func(r *Recorder) {
out := new(StringWriter)
testRecorder := NewRecorder(out, out)
RunSuite(testRecorder, PostTestTest)
output := out.String()
Diag(r, "Output:")
Diag(r, output)
list := strings.Split(output, "\n", -1)
lines := len(list)
Ok(r, list[6] == "# Ran PostTest",
"second line was %s", list[6])
Ok(r, lines == 8, "output was %d lines", lines)
}
return []func(*Recorder){f, f2, f3, f4, f5}
}
func init() {
Register(new(TapTestStream))
}
// TODO(jwall): actually test the output.
func main() {
Main()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment