Repository: exercism/xgo
Pull request #473 (2017-02-01 03:21:23)

hello-world: simplify to unconditional "Hello, World!"
The introductory exercise should require as little Go knowledge as
necessary. We'd like to introduce `go test` and `testVersion` here. We
skip introducing the empty string as no string, as this is more specific
knowledge.

https://github.com/exercism/x-common/issues/520

---

I wouldn't recommend merging this until the corresponding change is made in x-common.
Let's get it ready to go before then.


Patch:
diff --git a/README.md b/README.md
index 210b64f3..1efda439 100644
--- a/README.md
+++ b/README.md
@@ -31,12 +31,13 @@ This is because this repo only imports from the standard library and isn't expec
 Please be familiar with the [contributing guide](https://github.com/exercism/x-common/blob/master/CONTRIBUTING.md)
 in the x-common repository.  This describes how all the language tracks are put together, as well as details about
 the common metadata, and high-level information about contributing to existing problems and adding new problems.
+In particular, please read the [Pull Request Guidelines](https://github.com/exercism/x-common/blob/master/CONTRIBUTING.md#pull-request-guidelines) before opening a pull request.
 
 ## Problem Versioning
 
 Each problem defines a `const targetTestVersion` in the test program, and validates that the solution has defined a matching value `testVersion`.  Any xgo developer that changes the test program or test data increments `targetTestVersion`.
 
-The benefit of all this is that nipickers can see which test version a posted solution was written for and be spared confusion over why an old posted solution might not pass current tests.
+The benefit of all this is that reviewers can see which test version a posted solution was written for and be spared confusion over why an old posted solution might not pass current tests.
 
 Notice that neither the `testVersion` nor the `targetTestVersion` is exported. This is so that golint will not complain about a missing comment. In general, adding tests for unexported names is considered an anti-pattern, but in this case the trade-off seems acceptable.
 
diff --git a/config.json b/config.json
index 88fb13f1..3d5cfa64 100644
--- a/config.json
+++ b/config.json
@@ -185,9 +185,11 @@
       ]
     },
     {
-      "difficulty": 1,
+      "difficulty": 3,
       "slug": "parallel-letter-frequency",
-      "topics": []
+      "topics": [
+        "Concurrency"
+      ]
     },
     {
       "difficulty": 1,
@@ -472,6 +474,11 @@
       "difficulty": 1,
       "slug": "variable-length-quantity",
       "topics": []
+    },
+    {
+      "difficulty": 1,
+      "slug": "forth",
+      "topics": []
     }
   ]
 }
diff --git a/exercises/anagram/example.go b/exercises/anagram/example.go
index 770c596b..4fb208f3 100644
--- a/exercises/anagram/example.go
+++ b/exercises/anagram/example.go
@@ -5,6 +5,8 @@ import (
 	"strings"
 )
 
+const testVersion = 1
+
 func Detect(subject string, candidates []string) []string {
 	subject = strings.ToLower(subject)
 	var matches []string
diff --git a/exercises/atbash-cipher/example.go b/exercises/atbash-cipher/example.go
index 7f209b80..52fe7895 100644
--- a/exercises/atbash-cipher/example.go
+++ b/exercises/atbash-cipher/example.go
@@ -5,6 +5,8 @@ import (
 	"strings"
 )
 
+const testVersion = 1
+
 var alphabet = "abcdefghijklmnopqrstuvwxyz"
 
 func Atbash(s string) string {
diff --git a/exercises/bank-account/example.go b/exercises/bank-account/example.go
index 5e20e18f..138c1dd6 100644
--- a/exercises/bank-account/example.go
+++ b/exercises/bank-account/example.go
@@ -2,6 +2,8 @@ package account
 
 import "sync"
 
+const testVersion = 1
+
 type Account struct {
 	sync.RWMutex
 	open    bool
diff --git a/exercises/binary-search-tree/example.go b/exercises/binary-search-tree/example.go
index b9dca2be..c8a6f331 100644
--- a/exercises/binary-search-tree/example.go
+++ b/exercises/binary-search-tree/example.go
@@ -1,5 +1,7 @@
 package binarysearchtree
 
+const testVersion = 1
+
 type SearchTreeData struct {
 	left  *SearchTreeData
 	data  int
diff --git a/exercises/binary-search/example.go b/exercises/binary-search/example.go
index c20ddc1d..c9ada349 100644
--- a/exercises/binary-search/example.go
+++ b/exercises/binary-search/example.go
@@ -2,6 +2,8 @@ package binarysearch
 
 import "fmt"
 
+const testVersion = 1
+
 func SearchInts(s []int, k int) (i int) {
 	for j := len(s); i < j; {
 		if h := (i + j) / 2; s[h] < k {
diff --git a/exercises/difference-of-squares/example.go b/exercises/difference-of-squares/example.go
index b953a691..7d527f08 100644
--- a/exercises/difference-of-squares/example.go
+++ b/exercises/difference-of-squares/example.go
@@ -1,5 +1,7 @@
 package diffsquares
 
+const testVersion = 1
+
 func SquareOfSums(n int) int {
 	s := 0
 	for i := 0; i <= n; i++ {
diff --git a/exercises/diffie-hellman/example.go b/exercises/diffie-hellman/example.go
index f2740c34..cd6790f8 100644
--- a/exercises/diffie-hellman/example.go
+++ b/exercises/diffie-hellman/example.go
@@ -6,6 +6,8 @@ import (
 	"time"
 )
 
+const testVersion = 1
+
 var r = rand.New(rand.NewSource(time.Now().Unix()))
 var two = big.NewInt(2)
 
diff --git a/exercises/etl/example.go b/exercises/etl/example.go
index fe632904..f08c7b51 100644
--- a/exercises/etl/example.go
+++ b/exercises/etl/example.go
@@ -2,6 +2,8 @@ package etl
 
 import "strings"
 
+const testVersion = 1
+
 type legacy map[int][]string
 
 func Transform(in legacy) (out map[string]int) {
diff --git a/exercises/forth/example.go b/exercises/forth/example.go
new file mode 100644
index 00000000..c253ea62
--- /dev/null
+++ b/exercises/forth/example.go
@@ -0,0 +1,238 @@
+// Package forth implements a tiny subset of the Forth language.
+package forth
+
+import (
+	"errors"
+	"strconv"
+	"strings"
+	"unicode"
+)
+
+type operatorFn func(stack *[]int) error
+
+type operatorId byte
+
+const (
+	addOp operatorId = iota
+	subOp
+	mulOp
+	divOp
+	dropOp
+	dupOp
+	swapOp
+	overOp
+	constOp
+	userDefOp
+	endDefOp
+)
+
+type operatorTyp struct {
+	fn operatorFn
+	id operatorId
+}
+
+func Forth(input []string) (result []int, err error) {
+	if len(input) == 0 {
+		return []int{}, nil
+	}
+
+	// Allocate an initially empty stack, with arbitrary starting capacity of 8.
+	stack := make([]int, 0, 8)
+	// Allocate a map for user defined words.
+	userDefs := make(map[string][]operatorTyp, 8)
+	for _, phrase := range input {
+		// Parse one phrase of input, building up an operator list.
+		opList, err := parse(phrase, userDefs)
+		if err != nil {
+			return nil, err
+		}
+		// Perform any operators from that phrase, updating stack.
+		for _, opr := range opList {
+			err = opr.fn(&stack)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	return stack[:len(stack)], nil
+}
+
+// parse given phrase, returning an operator list, and updating
+// a userDefs map for any user definifion of words in phrase.
+func parse(phrase string, userDefs map[string][]operatorTyp) (oplist []operatorTyp, err error) {
+	words := strings.FieldsFunc(phrase,
+		func(r rune) bool {
+			return unicode.IsSpace(r) || unicode.IsControl(r)
+		})
+
+	// t is token index into words[]
+	for t := 0; t < len(words); t++ {
+		w := strings.ToUpper(words[t])
+		// Handle reference to user defined word.
+		if udef, ok := userDefs[w]; ok {
+			oplist = append(oplist, udef...)
+		} else if op, ok := builtinOps[w]; ok {
+			if op.id == userDefOp {
+				// Handle user defined word definition.
+				t++
+				if t >= len(words)-2 {
+					return nil, emptyUserDefErr
+				}
+				userword := strings.ToUpper(words[t])
+				if _, numerr := strconv.Atoi(userword); numerr == nil {
+					return nil, invalidUserDefErr
+				}
+				t++
+				var userops []operatorTyp
+				for t < len(words) {
+					oneOp, err := parse(words[t], userDefs)
+					if err != nil {
+						return nil, err
+					}
+					if oneOp[0].id == endDefOp {
+						break
+					}
+					userops = append(userops, oneOp...)
+					t++
+				}
+				if len(userops) == 0 {
+					return nil, emptyUserDefErr
+				} else {
+					userDefs[userword] = userops
+				}
+			} else {
+				// Normal builtin operator.
+				oplist = append(oplist, op)
+			}
+		} else {
+			// Handle constant literal.
+			var x int
+			x, err = strconv.Atoi(w)
+			if err != nil {
+				return nil, err
+			}
+			oplist = append(oplist,
+				operatorTyp{id: constOp,
+					fn: func(stack *[]int) error {
+						push(stack, x)
+						return nil
+					},
+				})
+		}
+	}
+	return oplist, nil
+}
+
+// builtinOps are the pre-defined operators to support.
+var builtinOps = map[string]operatorTyp{
+	"+":    {add, addOp},
+	"-":    {subtract, subOp},
+	"*":    {multiply, mulOp},
+	"/":    {divide, divOp},
+	"DUP":  {dup, dropOp},
+	"DROP": {drop, dupOp},
+	"SWAP": {swap, swapOp},
+	"OVER": {over, overOp},
+	":":    {nil, userDefOp},
+	";":    {nil, endDefOp},
+}
+
+func pop(stack *[]int) (v int, err error) {
+	slen := len(*stack)
+	if slen >= 1 {
+		v = (*stack)[slen-1]
+		*stack = (*stack)[:slen-1]
+		return v, nil
+	}
+	return 0, notEnoughOperands
+}
+
+func pop2(stack *[]int) (v1, v2 int, err error) {
+	v1, err = pop(stack)
+	if err != nil {
+		return 0, 0, err
+	}
+	v2, err = pop(stack)
+	return v1, v2, err
+}
+
+func push(stack *[]int, v int) {
+	*stack = append(*stack, v)
+}
+
+func binaryOp(stack *[]int, op func(a, b int) int) error {
+	v1, v2, err := pop2(stack)
+	if err != nil {
+		return err
+	}
+	push(stack, op(v2, v1))
+	return nil
+}
+
+func add(stack *[]int) (err error) {
+	return binaryOp(stack, func(a, b int) int { return a + b })
+}
+
+func subtract(stack *[]int) (err error) {
+	return binaryOp(stack, func(a, b int) int { return a - b })
+}
+
+func multiply(stack *[]int) error {
+	return binaryOp(stack, func(a, b int) int { return a * b })
+}
+
+func divide(stack *[]int) error {
+	v1, v2, err := pop2(stack)
+	if err != nil {
+		return err
+	}
+	if v1 == 0 {
+		return divideByZero
+	}
+	push(stack, v2/v1)
+	return nil
+}
+
+func dup(stack *[]int) error {
+	v1, err := pop(stack)
+	if err != nil {
+		return err
+	}
+	push(stack, v1)
+	push(stack, v1)
+	return nil
+}
+
+func drop(stack *[]int) error {
+	_, err := pop(stack)
+	return err
+}
+
+func over(stack *[]int) error {
+	v1, v2, err := pop2(stack)
+	if err != nil {
+		return err
+	}
+	push(stack, v2)
+	push(stack, v1)
+	push(stack, v2)
+	return nil
+}
+
+func swap(stack *[]int) error {
+	v1, v2, err := pop2(stack)
+	if err != nil {
+		return err
+	}
+	push(stack, v1)
+	push(stack, v2)
+	return nil
+}
+
+var notEnoughOperands error = errors.New("not enough operands")
+var divideByZero error = errors.New("attempt to divide by zero")
+var emptyUserDefErr error = errors.New("empty user definition")
+var invalidUserDefErr error = errors.New("invalid user def word")
+
+const testVersion = 1
diff --git a/exercises/forth/example_gen.go b/exercises/forth/example_gen.go
new file mode 100644
index 00000000..fcee967f
--- /dev/null
+++ b/exercises/forth/example_gen.go
@@ -0,0 +1,99 @@
+// +build ignore
+
+package main
+
+import (
+	"log"
+	"text/template"
+
+	"../../gen"
+)
+
+func main() {
+	t, err := template.New("").Parse(tmpl)
+	if err != nil {
+		log.Fatal(err)
+	}
+	var j js
+	if err := gen.Gen("forth", &j, t); err != nil {
+		log.Fatal(err)
+	}
+}
+
+// The JSON structure we expect to be able to unmarshal into
+type js struct {
+	ParsingAndNumbers  section `json:"parsing and numbers"`
+	Addition           section
+	Subtraction        section
+	Multiplication     section
+	Division           section
+	CombinedArithmetic section `json:"combined arithmetic"`
+	Dup                section
+	Drop               section
+	Swap               section
+	Over               section
+	UserDefinedWords   section `json:"user-defined words"`
+}
+
+// A grouped section of test cases.
+type section struct {
+	Cases []struct {
+		Description string
+		Input       []string
+		Expected    []int
+	}
+}
+
+// template applied to above data structure generates the Go test cases
+var tmpl = `package forth
+
+// Source: {{.Ori}}
+{{if .Commit}}// Commit: {{.Commit}}
+{{end}}
+
+{{/* template for repeated caseSection */}}
+
+{{define "caseSection"}} = []testCase{
+{{range $y, $c := .}}{
+	{{printf "%q" $c.Description}},
+	{{printf "%#v" $c.Input}},
+	{{printf "%#v" $c.Expected}},
+},
+{{end}}}{{end}}
+
+var parsingGroup{{template "caseSection" .J.ParsingAndNumbers.Cases}}
+
+var additionGroup{{template "caseSection" .J.Addition.Cases}}
+
+var subtractionGroup{{template "caseSection" .J.Subtraction.Cases}}
+
+var multiplicationGroup{{template "caseSection" .J.Multiplication.Cases}}
+
+var divisionGroup{{template "caseSection" .J.Division.Cases}}
+
+var arithmeticGroup{{template "caseSection" .J.CombinedArithmetic.Cases}}
+
+var dupGroup{{template "caseSection" .J.Dup.Cases}}
+
+var dropGroup{{template "caseSection" .J.Drop.Cases}}
+
+var swapGroup{{template "caseSection" .J.Swap.Cases}}
+
+var overGroup{{template "caseSection" .J.Over.Cases}}
+
+var userdefinedGroup{{template "caseSection" .J.UserDefinedWords.Cases}}
+
+var testSections = []testcaseSection{
+	{"parsing", parsingGroup},
+	{"addition(+)", additionGroup},
+	{"subtraction(-)", subtractionGroup},
+	{"multiplication(*)", multiplicationGroup},
+	{"division(/)", divisionGroup},
+	{"arithmetic", arithmeticGroup},
+	{"dup", dupGroup},
+	{"drop", dropGroup},
+	{"swap", swapGroup},
+	{"over", overGroup},
+	{"user-defined", userdefinedGroup},
+}
+`
diff --git a/exercises/grade-school/example.go b/exercises/grade-school/example.go
index 004ee6d8..0c21d4ba 100644
--- a/exercises/grade-school/example.go
+++ b/exercises/grade-school/example.go
@@ -2,6 +2,8 @@ package school
 
 import "sort"
 
+const testVersion = 1
+
 type School map[int][]string
 
 func New() *School {
diff --git a/exercises/hello-world/example.go b/exercises/hello-world/example.go
index d310e9e0..28b7e889 100644
--- a/exercises/hello-world/example.go
+++ b/exercises/hello-world/example.go
@@ -1,12 +1,8 @@
 package greeting
 
-const testVersion = 3
+const testVersion = 4
 
-// HelloWorld says hello to given name.
-// If no name is passed, it greets the world.
-func HelloWorld(name string) string {
-	if name == "" {
-		name = "World"
-	}
-	return "Hello, " + name + "!"
+// HelloWorld greets the world.
+func HelloWorld() string {
+	return "Hello, World!"
 }
diff --git a/exercises/hello-world/hello_world.go b/exercises/hello-world/hello_world.go
index 4f9a8933..7a00b0db 100644
--- a/exercises/hello-world/hello_world.go
+++ b/exercises/hello-world/hello_world.go
@@ -7,17 +7,17 @@ package greeting
 
 // testVersion identifies the version of the test program that you are
 // writing your code to. If the test program changes in the future --
-// after you have posted this code to the Exercism site -- nitpickers
+// after you have posted this code to the Exercism site -- reviewers
 // will see that your code can't necessarily be expected to pass the
 // current test suite because it was written to an earlier test version.
-const testVersion = 3
+const testVersion = 4
 
 // HelloWorld needs a comment documenting it as package does.
-func HelloWorld(string) string {
+func HelloWorld() string {
 	// Write some code here to pass the test suite.
 
 	// When you have a working solution, REMOVE ALL THE STOCK COMMENTS.
 	// They're here to help you get started but they only clutter a finished solution.
-	// If you leave them in, nitpickers will protest!
+	// If you leave them in, reviewers will protest!
 	return ""
 }
diff --git a/exercises/nth-prime/example.go b/exercises/nth-prime/example.go
index 40aeadbe..0eee03e0 100644
--- a/exercises/nth-prime/example.go
+++ b/exercises/nth-prime/example.go
@@ -1,5 +1,7 @@
 package prime
 
+const testVersion = 1
+
 func Nth(n int) (p int, ok bool) {
 	switch {
 	case n < 1:
diff --git a/exercises/react/example.go b/exercises/react/example.go
index a7953d9c..a2c3a322 100644
--- a/exercises/react/example.go
+++ b/exercises/react/example.go
@@ -1,6 +1,6 @@
 package react
 
-const testVersion = 4
+const testVersion = 5
 
 type reactor struct {
 	cells []*cell
@@ -17,6 +17,14 @@ type cell struct {
 	observers map[int]func(int)
 }
 
+type canceler struct {
+	cancel func()
+}
+
+func (c *canceler) Cancel() {
+	c.cancel()
+}
+
 func (c *cell) Value() int { return c.value }
 
 func (c *cell) SetValue(v int) {
@@ -27,15 +35,15 @@ func (c *cell) SetValue(v int) {
 	}
 }
 
-func (c *cell) AddCallback(cb func(int)) CallbackHandle {
+func (c *cell) AddCallback(cb func(int)) Canceler {
 	key := c.nextKey
 	c.nextKey++
 	c.observers[key] = cb
-	return key
-}
-
-func (c *cell) RemoveCallback(key CallbackHandle) {
-	delete(c.observers, key.(int))
+	return &canceler{
+		cancel: func() {
+			delete(c.observers, key)
+		},
+	}
 }
 
 func New() Reactor {
diff --git a/exercises/react/interfaces.go b/exercises/react/interfaces.go
index 05f3e42c..073fbbda 100644
--- a/exercises/react/interfaces.go
+++ b/exercises/react/interfaces.go
@@ -38,12 +38,12 @@ type ComputeCell interface {
 	Cell
 
 	// AddCallback adds a callback which will be called when the value changes.
-	// It returns a callback handle which can be used to remove the callback.
-	AddCallback(func(int)) CallbackHandle
-
-	// RemoveCallback removes a previously added callback, if it exists.
-	RemoveCallback(CallbackHandle)
+	// It returns a Canceler which can be used to remove the callback.
+	AddCallback(func(int)) Canceler
 }
 
-// A CallbackHandle is used to remove previously added callbacks, see ComputeCell.
-type CallbackHandle interface{}
+// A Canceler is used to remove previously added callbacks, see ComputeCell.
+type Canceler interface {
+	// Cancel removes the callback.
+	Cancel()
+}
diff --git a/exercises/variable-length-quantity/example.go b/exercises/variable-length-quantity/example.go
index 945c4966..906a28fb 100644
--- a/exercises/variable-length-quantity/example.go
+++ b/exercises/variable-length-quantity/example.go
@@ -1,5 +1,7 @@
 package variablelengthquantity
 
+const testVersion = 1
+
 // EncodeVarint returns the varint encoding of x.
 func EncodeVarint(x uint32) []byte {
 	if x>>7 == 0 {


Test patch:
diff --git a/exercises/allergies/allergies_test.go b/exercises/allergies/allergies_test.go
index 563f1481..ed26c5e1 100644
--- a/exercises/allergies/allergies_test.go
+++ b/exercises/allergies/allergies_test.go
@@ -23,10 +23,13 @@ var allergiesTests = []struct {
 	{[]string{"eggs", "shellfish", "strawberries", "tomatoes", "chocolate", "pollen", "cats"}, 509},
 }
 
-func TestAllergies(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestAllergies(t *testing.T) {
 	for _, test := range allergiesTests {
 		actual := Allergies(test.input)
 		sort.Strings(actual)
diff --git a/exercises/anagram/anagram_test.go b/exercises/anagram/anagram_test.go
index e238af77..f4e39109 100644
--- a/exercises/anagram/anagram_test.go
+++ b/exercises/anagram/anagram_test.go
@@ -6,6 +6,8 @@ import (
 	"testing"
 )
 
+const targetTestVersion = 1
+
 var testCases = []struct {
 	subject     string
 	candidates  []string
@@ -138,6 +140,12 @@ func equal(a []string, b []string) bool {
 	return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestDetectAnagrams(t *testing.T) {
 	for _, tt := range testCases {
 		actual := Detect(tt.subject, tt.candidates)
diff --git a/exercises/atbash-cipher/atbash_cipher_test.go b/exercises/atbash-cipher/atbash_cipher_test.go
index 08f274ea..32cdf677 100644
--- a/exercises/atbash-cipher/atbash_cipher_test.go
+++ b/exercises/atbash-cipher/atbash_cipher_test.go
@@ -2,6 +2,8 @@ package atbash
 
 import "testing"
 
+const targetTestVersion = 1
+
 var tests = []struct {
 	expected string
 	s        string
@@ -17,6 +19,12 @@ var tests = []struct {
 	{"gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt", "The quick brown fox jumps over the lazy dog."},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestAtbash(t *testing.T) {
 	for _, test := range tests {
 		actual := Atbash(test.s)
diff --git a/exercises/bank-account/bank_account_test.go b/exercises/bank-account/bank_account_test.go
index 26e5b2c2..d6216461 100644
--- a/exercises/bank-account/bank_account_test.go
+++ b/exercises/bank-account/bank_account_test.go
@@ -20,6 +20,14 @@ import (
 	"time"
 )
 
+const targetTestVersion = 1
+
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestSeqOpenBalanceClose(t *testing.T) {
 	// open account
 	const amt = 10
diff --git a/exercises/beer-song/beer_test.go b/exercises/beer-song/beer_test.go
index 57824043..a16b56e4 100644
--- a/exercises/beer-song/beer_test.go
+++ b/exercises/beer-song/beer_test.go
@@ -6,12 +6,6 @@ import (
 
 const targetTestVersion = 1
 
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
-
 const verse8 = "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n"
 const verse3 = "3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.\n"
 const verse2 = "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n"
@@ -54,6 +48,12 @@ var verseTestCases = []struct {
 	{"invalid verse", 104, "", true},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestBottlesVerse(t *testing.T) {
 	for _, tt := range verseTestCases {
 		actualVerse, err := Verse(tt.verse)
diff --git a/exercises/binary-search-tree/binary_search_tree_test.go b/exercises/binary-search-tree/binary_search_tree_test.go
index e419d771..70d70e4d 100644
--- a/exercises/binary-search-tree/binary_search_tree_test.go
+++ b/exercises/binary-search-tree/binary_search_tree_test.go
@@ -19,6 +19,14 @@ import (
 	"testing"
 )
 
+const targetTestVersion = 1
+
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestDataIsRetained(t *testing.T) {
 	actual := Bst(4).data
 	expected := 4
diff --git a/exercises/binary-search/binary_search_test.go b/exercises/binary-search/binary_search_test.go
index e819e98a..b7c5e1bf 100644
--- a/exercises/binary-search/binary_search_test.go
+++ b/exercises/binary-search/binary_search_test.go
@@ -25,6 +25,8 @@ import (
 	"testing"
 )
 
+const targetTestVersion = 1
+
 var testData = []struct {
 	ref   string
 	slice []int
@@ -84,6 +86,12 @@ var testData = []struct {
 		nil, 0, 0},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestSearchInts(t *testing.T) {
 	for _, test := range testData {
 		if !sort.IntsAreSorted(test.slice) {
diff --git a/exercises/binary/binary_test.go b/exercises/binary/binary_test.go
index 0bd2b4a4..64410b19 100644
--- a/exercises/binary/binary_test.go
+++ b/exercises/binary/binary_test.go
@@ -38,10 +38,13 @@ var testCases = []struct {
 	{"22", 0, false},
 }
 
-func TestParseBinary(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestParseBinary(t *testing.T) {
 	for _, tt := range testCases {
 		actual, err := ParseBinary(tt.binary)
 		if tt.ok {
diff --git a/exercises/bob/bob_test.go b/exercises/bob/bob_test.go
index e53226d0..f1322f5f 100644
--- a/exercises/bob/bob_test.go
+++ b/exercises/bob/bob_test.go
@@ -4,10 +4,13 @@ import "testing"
 
 const targetTestVersion = 2
 
-func TestHeyBob(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestHeyBob(t *testing.T) {
 	for _, tt := range testCases {
 		actual := Hey(tt.input)
 		if actual != tt.expected {
diff --git a/exercises/bracket-push/bracket_push_test.go b/exercises/bracket-push/bracket_push_test.go
index 5e2e27b9..c68866cd 100644
--- a/exercises/bracket-push/bracket_push_test.go
+++ b/exercises/bracket-push/bracket_push_test.go
@@ -48,10 +48,13 @@ var testCases = []struct {
 	},
 }
 
-func TestBracket(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
-		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestBracket(t *testing.T) {
 	for _, tt := range testCases {
 		actual, err := Bracket(tt.input)
 		// We don't expect errors for any of the test cases
diff --git a/exercises/circular-buffer/circular_buffer_test.go b/exercises/circular-buffer/circular_buffer_test.go
index 4215b210..a9e1ac57 100644
--- a/exercises/circular-buffer/circular_buffer_test.go
+++ b/exercises/circular-buffer/circular_buffer_test.go
@@ -21,12 +21,6 @@ import (
 
 const targetTestVersion = 4
 
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
-
 // Here is one way you can have a test case verify that the expected
 // interfaces are implemented.
 
@@ -94,6 +88,12 @@ func (tb testBuffer) overwrite(c byte) {
 
 // tests.  separate functions so log will have descriptive test name.
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestReadEmptyBuffer(t *testing.T) {
 	tb := nb(1, t)
 	tb.readFail()
diff --git a/exercises/clock/clock_test.go b/exercises/clock/clock_test.go
index dc683a5c..79299255 100644
--- a/exercises/clock/clock_test.go
+++ b/exercises/clock/clock_test.go
@@ -29,10 +29,13 @@ import (
 
 const targetTestVersion = 4
 
-func TestCreateClock(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestCreateClock(t *testing.T) {
 	for _, n := range timeTests {
 		if got := New(n.h, n.m); got.String() != n.want {
 			t.Fatalf("New(%d, %d) = %q, want %q", n.h, n.m, got, n.want)
diff --git a/exercises/connect/connect_test.go b/exercises/connect/connect_test.go
index ce57ddd3..2f51ea42 100644
--- a/exercises/connect/connect_test.go
+++ b/exercises/connect/connect_test.go
@@ -17,10 +17,13 @@ func prepare(lines []string) []string {
 
 const targetTestVersion = 3
 
-func TestResultOf(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestResultOf(t *testing.T) {
 	for _, tt := range testCases {
 		actual, err := ResultOf(prepare(tt.board))
 		// We don't expect errors for any of the test cases
diff --git a/exercises/crypto-square/crypto_square_test.go b/exercises/crypto-square/crypto_square_test.go
index 75a35826..99255a79 100644
--- a/exercises/crypto-square/crypto_square_test.go
+++ b/exercises/crypto-square/crypto_square_test.go
@@ -86,10 +86,13 @@ var tests = []struct {
 	},
 }
 
-func TestEncode(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestEncode(t *testing.T) {
 	for _, test := range tests {
 		if ct := Encode(test.pt); ct != test.ct {
 			t.Fatalf(`Encode(%q):
diff --git a/exercises/diamond/diamond_test.go b/exercises/diamond/diamond_test.go
index 63728789..9fc2b029 100644
--- a/exercises/diamond/diamond_test.go
+++ b/exercises/diamond/diamond_test.go
@@ -9,6 +9,8 @@ import (
 	"time"
 )
 
+const targetTestVersion = 1
+
 var config = &quick.Config{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))}
 
 type correctChar byte
@@ -38,6 +40,12 @@ func checkCorrect(requirement func(byte, []string) bool, keepSeparator bool, t *
 	}
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestFirstRowContainsOneA(t *testing.T) {
 	requirement := func(char byte, rows []string) bool {
 		return len(rows) > 0 && strings.Count(rows[0], "A") == 1
@@ -217,11 +225,3 @@ func TestCharOutOfRangeShouldGiveError(t *testing.T) {
 		t.Error(err)
 	}
 }
-
-const targetTestVersion = 1
-
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
diff --git a/exercises/difference-of-squares/difference_of_squares_test.go b/exercises/difference-of-squares/difference_of_squares_test.go
index d3fe30f9..0565c784 100644
--- a/exercises/difference-of-squares/difference_of_squares_test.go
+++ b/exercises/difference-of-squares/difference_of_squares_test.go
@@ -2,12 +2,20 @@ package diffsquares
 
 import "testing"
 
+const targetTestVersion = 1
+
 var tests = []struct{ n, sqOfSums, sumOfSq int }{
 	{5, 225, 55},
 	{10, 3025, 385},
 	{100, 25502500, 338350},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestSquareOfSums(t *testing.T) {
 	for _, test := range tests {
 		if s := SquareOfSums(test.n); s != test.sqOfSums {
diff --git a/exercises/diffie-hellman/diffie_hellman_test.go b/exercises/diffie-hellman/diffie_hellman_test.go
index 9d5afe5e..b87f1335 100644
--- a/exercises/diffie-hellman/diffie_hellman_test.go
+++ b/exercises/diffie-hellman/diffie_hellman_test.go
@@ -14,6 +14,8 @@ import (
 	"testing"
 )
 
+const targetTestVersion = 1
+
 type testCase struct {
 	g    int64    // prime, generator
 	p    *big.Int // prime, modulus
@@ -78,6 +80,12 @@ var tests = []testCase{
 
 var _one = big.NewInt(1)
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 // test that PrivateKey returns numbers in range, returns different numbers.
 func TestPrivateKey(t *testing.T) {
 	priv := func(p *big.Int) *big.Int {
diff --git a/exercises/error-handling/error_handling_test.go b/exercises/error-handling/error_handling_test.go
index 200ba743..fc82c94c 100644
--- a/exercises/error-handling/error_handling_test.go
+++ b/exercises/error-handling/error_handling_test.go
@@ -27,6 +27,14 @@ func (mr mockResource) Close() error      { return mr.close() }
 func (mr mockResource) Frob(input string) { mr.frob(input) }
 func (mr mockResource) Defrob(tag string) { mr.defrob(tag) }
 
+// If this test fails and you've properly defined testVersion the requirements
+// of the tests have changed since you wrote your submission.
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 // Use should not return an error on the "happy" path.
 func TestNoErrors(t *testing.T) {
 	var frobInput string
@@ -155,11 +163,3 @@ func TestCallCloseNonOnFrobError(t *testing.T) {
 		t.Fatalf("Close was not called")
 	}
 }
-
-// If this test fails and you've properly defined testVersion the requirements
-// of the tests have changed since you wrote your submission.
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
-	}
-}
diff --git a/exercises/etl/etl_test.go b/exercises/etl/etl_test.go
index d52af108..36b6119a 100644
--- a/exercises/etl/etl_test.go
+++ b/exercises/etl/etl_test.go
@@ -2,6 +2,8 @@ package etl
 
 import "testing"
 
+const targetTestVersion = 1
+
 type given map[int][]string
 type expectation map[string]int
 
@@ -67,6 +69,12 @@ func equal(actual map[string]int, expectation map[string]int) bool {
 	return true
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestTransform(t *testing.T) {
 	for _, tt := range transformTests {
 		actual := Transform(map[int][]string(tt.input))
diff --git a/exercises/food-chain/food_chain_test.go b/exercises/food-chain/food_chain_test.go
index 3bf847ff..a1319c36 100644
--- a/exercises/food-chain/food_chain_test.go
+++ b/exercises/food-chain/food_chain_test.go
@@ -8,12 +8,6 @@ import (
 
 const targetTestVersion = 3
 
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
-
 var ref = []string{``,
 
 	`I know an old lady who swallowed a fly.
@@ -92,6 +86,12 @@ func diff(got, want string) string {
 	}
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestVerse(t *testing.T) {
 	for v := 1; v <= 8; v++ {
 		if ret := Verse(v); ret != ref[v] {
diff --git a/exercises/forth/cases_test.go b/exercises/forth/cases_test.go
new file mode 100644
index 00000000..498e6d85
--- /dev/null
+++ b/exercises/forth/cases_test.go
@@ -0,0 +1,246 @@
+package forth
+
+// Source: exercism/x-common
+// Commit: 8804a76 Remove non-ASCII characters tests from forth exercise
+
+var parsingGroup = []testCase{
+	{
+		"empty input results in empty stack",
+		[]string{},
+		[]int{},
+	},
+	{
+		"numbers just get pushed onto the stack",
+		[]string{"1 2 3 4 5"},
+		[]int{1, 2, 3, 4, 5},
+	},
+	{
+		"all non-word characters are separators",
+		[]string{"1\x002\x133\n4\r5 6\t7"},
+		[]int{1, 2, 3, 4, 5, 6, 7},
+	},
+}
+
+var additionGroup = []testCase{
+	{
+		"can add two numbers",
+		[]string{"1 2 +"},
+		[]int{3},
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"+"},
+		[]int(nil),
+	},
+	{
+		"errors if there is only one value on the stack",
+		[]string{"1 +"},
+		[]int(nil),
+	},
+}
+
+var subtractionGroup = []testCase{
+	{
+		"can subtract two numbers",
+		[]string{"3 4 -"},
+		[]int{-1},
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"-"},
+		[]int(nil),
+	},
+	{
+		"errors if there is only one value on the stack",
+		[]string{"1 -"},
+		[]int(nil),
+	},
+}
+
+var multiplicationGroup = []testCase{
+	{
+		"can multiply two numbers",
+		[]string{"2 4 *"},
+		[]int{8},
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"*"},
+		[]int(nil),
+	},
+	{
+		"errors if there is only one value on the stack",
+		[]string{"1 *"},
+		[]int(nil),
+	},
+}
+
+var divisionGroup = []testCase{
+	{
+		"can divide two numbers",
+		[]string{"12 3 /"},
+		[]int{4},
+	},
+	{
+		"performs integer division",
+		[]string{"8 3 /"},
+		[]int{2},
+	},
+	{
+		"errors if dividing by zero",
+		[]string{"4 0 /"},
+		[]int(nil),
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"/"},
+		[]int(nil),
+	},
+	{
+		"errors if there is only one value on the stack",
+		[]string{"1 /"},
+		[]int(nil),
+	},
+}
+
+var arithmeticGroup = []testCase{
+	{
+		"addition and subtraction",
+		[]string{"1 2 + 4 -"},
+		[]int{-1},
+	},
+	{
+		"multiplication and division",
+		[]string{"2 4 * 3 /"},
+		[]int{2},
+	},
+}
+
+var dupGroup = []testCase{
+	{
+		"copies the top value on the stack",
+		[]string{"1 DUP"},
+		[]int{1, 1},
+	},
+	{
+		"is case-insensitive",
+		[]string{"1 2 Dup"},
+		[]int{1, 2, 2},
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"dup"},
+		[]int(nil),
+	},
+}
+
+var dropGroup = []testCase{
+	{
+		"removes the top value on the stack if it is the only one",
+		[]string{"1 drop"},
+		[]int{},
+	},
+	{
+		"removes the top value on the stack if it is not the only one",
+		[]string{"1 2 drop"},
+		[]int{1},
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"drop"},
+		[]int(nil),
+	},
+}
+
+var swapGroup = []testCase{
+	{
+		"swaps the top two values on the stack if they are the only ones",
+		[]string{"1 2 swap"},
+		[]int{2, 1},
+	},
+	{
+		"swaps the top two values on the stack if they are not the only ones",
+		[]string{"1 2 3 swap"},
+		[]int{1, 3, 2},
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"swap"},
+		[]int(nil),
+	},
+	{
+		"errors if there is only one value on the stack",
+		[]string{"1 swap"},
+		[]int(nil),
+	},
+}
+
+var overGroup = []testCase{
+	{
+		"copies the second element if there are only two",
+		[]string{"1 2 over"},
+		[]int{1, 2, 1},
+	},
+	{
+		"copies the second element if there are more than two",
+		[]string{"1 2 3 over"},
+		[]int{1, 2, 3, 2},
+	},
+	{
+		"errors if there is nothing on the stack",
+		[]string{"over"},
+		[]int(nil),
+	},
+	{
+		"errors if there is only one value on the stack",
+		[]string{"1 over"},
+		[]int(nil),
+	},
+}
+
+var userdefinedGroup = []testCase{
+	{
+		"can consist of built-in words",
+		[]string{": dup-twice dup dup ;", "1 dup-twice"},
+		[]int{1, 1, 1},
+	},
+	{
+		"execute in the right order",
+		[]string{": countup 1 2 3 ;", "countup"},
+		[]int{1, 2, 3},
+	},
+	{
+		"can override other user-defined words",
+		[]string{": foo dup ;", ": foo dup dup ;", "1 foo"},
+		[]int{1, 1, 1},
+	},
+	{
+		"can override built-in words",
+		[]string{": swap dup ;", "1 swap"},
+		[]int{1, 1},
+	},
+	{
+		"cannot redefine numbers",
+		[]string{": 1 2 ;"},
+		[]int(nil),
+	},
+	{
+		"errors if executing a non-existent word",
+		[]string{"foo"},
+		[]int(nil),
+	},
+}
+
+var testSections = []testcaseSection{
+	{"parsing", parsingGroup},
+	{"addition(+)", additionGroup},
+	{"subtraction(-)", subtractionGroup},
+	{"multiplication(*)", multiplicationGroup},
+	{"division(/)", divisionGroup},
+	{"arithmetic", arithmeticGroup},
+	{"dup", dupGroup},
+	{"drop", dropGroup},
+	{"swap", swapGroup},
+	{"over", overGroup},
+	{"user-defined", userdefinedGroup},
+}
diff --git a/exercises/forth/forth_test.go b/exercises/forth/forth_test.go
new file mode 100644
index 00000000..fbd7525e
--- /dev/null
+++ b/exercises/forth/forth_test.go
@@ -0,0 +1,64 @@
+package forth
+
+// API:
+// func Forth([]string) ([]int, error)
+//
+
+import (
+	"reflect"
+	"testing"
+)
+
+const targetTestVersion = 1
+
+type testCase struct {
+	description string
+	input       []string
+	expected    []int // nil slice indicates error expected.
+}
+
+type testcaseSection struct {
+	name  string
+	tests []testCase
+}
+
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
+func runTestCases(t *testing.T, sectionName string, testCases []testCase) {
+	for _, tc := range testCases {
+		description := sectionName + " - " + tc.description
+		if v, err := Forth(tc.input); err == nil {
+			var _ error = err
+			if tc.expected == nil {
+				t.Fatalf("%s\n\tForth(%#v) expected an error, got %v",
+					description, tc.input, v)
+			} else if !reflect.DeepEqual(v, tc.expected) {
+				t.Fatalf("%s\n\tForth(%#v) expected %v, got %v",
+					description, tc.input, tc.expected, v)
+			}
+		} else if tc.expected != nil {
+			t.Fatalf("%s\n\tForth(%#v) expected %v, got an error: %q",
+				description, tc.input, tc.expected, err)
+		}
+	}
+}
+
+func TestForth(t *testing.T) {
+	for _, testSection := range testSections {
+		runTestCases(t, testSection.name, testSection.tests)
+	}
+}
+
+func BenchmarkForth(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		for _, testSection := range testSections {
+			for _, test := range testSection.tests {
+				Forth(test.input)
+			}
+		}
+	}
+}
diff --git a/exercises/gigasecond/gigasecond_test.go b/exercises/gigasecond/gigasecond_test.go
index 4e1f3a9d..c7727ae5 100644
--- a/exercises/gigasecond/gigasecond_test.go
+++ b/exercises/gigasecond/gigasecond_test.go
@@ -16,10 +16,13 @@ const (
 	fmtDT = "2006-01-02T15:04:05"
 )
 
-func TestAddGigasecond(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
-		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestAddGigasecond(t *testing.T) {
 	for _, tc := range addCases {
 		in := parse(tc.in, t)
 		want := parse(tc.want, t)
diff --git a/exercises/grade-school/grade_school_test.go b/exercises/grade-school/grade_school_test.go
index 33d85dfc..b14d4ca7 100644
--- a/exercises/grade-school/grade_school_test.go
+++ b/exercises/grade-school/grade_school_test.go
@@ -19,6 +19,14 @@ import (
 	"testing"
 )
 
+const targetTestVersion = 1
+
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestNewSchoolIsEmpty(t *testing.T) {
 	if len(New().Enrollment()) != 0 {
 		t.Error("New school not empty")
diff --git a/exercises/grains/grains_test.go b/exercises/grains/grains_test.go
index 942ba780..55dec7f4 100644
--- a/exercises/grains/grains_test.go
+++ b/exercises/grains/grains_test.go
@@ -23,6 +23,12 @@ var squareTests = []struct {
 	{-1, 0, true},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestSquare(t *testing.T) {
 	for _, test := range squareTests {
 		actualVal, actualErr := Square(test.input)
@@ -51,12 +57,6 @@ func TestTotal(t *testing.T) {
 	}
 }
 
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
-
 func BenchmarkSquare(b *testing.B) {
 	b.StopTimer()
 
diff --git a/exercises/hamming/hamming_test.go b/exercises/hamming/hamming_test.go
index 4f5792bb..8f0c98fe 100644
--- a/exercises/hamming/hamming_test.go
+++ b/exercises/hamming/hamming_test.go
@@ -6,7 +6,7 @@ const targetTestVersion = 5
 
 func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
 	}
 }
 
diff --git a/exercises/hello-world/hello_test.go b/exercises/hello-world/hello_test.go
index 88969275..79598d90 100644
--- a/exercises/hello-world/hello_test.go
+++ b/exercises/hello-world/hello_test.go
@@ -2,28 +2,25 @@ package greeting
 
 import "testing"
 
-// Define a function HelloWorld(string) string.
+// Define a function named HelloWorld that takes no arguments,
+// and returns a string.
+// In other words, define a function with the following signature:
+// HelloWorld() string
 //
 // Also define a testVersion with a value that matches
 // the targetTestVersion here.
 
-const targetTestVersion = 3
-
-func TestHelloWorld(t *testing.T) {
-	tests := []struct {
-		name, expected string
-	}{
-		{"", "Hello, World!"},
-		{"Gopher", "Hello, Gopher!"},
-		{"ゴーファー", "Hello, ゴーファー!"},
-	}
-	for _, test := range tests {
-		if observed := HelloWorld(test.name); observed != test.expected {
-			t.Fatalf("HelloWorld(%s) = %v, want %v", test.name, observed, test.expected)
-		}
-	}
+const targetTestVersion = 4
 
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
 }
+
+func TestHelloWorld(t *testing.T) {
+	expected := "Hello, World!"
+	if observed := HelloWorld(); observed != expected {
+		t.Fatalf("HelloWorld() = %v, want %v", observed, expected)
+	}
+}
diff --git a/exercises/hexadecimal/hexadecimal_test.go b/exercises/hexadecimal/hexadecimal_test.go
index 673bff01..aba9e1cf 100644
--- a/exercises/hexadecimal/hexadecimal_test.go
+++ b/exercises/hexadecimal/hexadecimal_test.go
@@ -17,12 +17,6 @@ import (
 
 const targetTestVersion = 1
 
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
-
 var testCases = []struct {
 	in      string
 	out     int64
@@ -41,6 +35,12 @@ var testCases = []struct {
 	{"9223372036854775809", 0, "range"},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestParseHex(t *testing.T) {
 	for _, test := range testCases {
 		out, err := ParseHex(test.in)
diff --git a/exercises/house/house_test.go b/exercises/house/house_test.go
index 58575012..ee768184 100644
--- a/exercises/house/house_test.go
+++ b/exercises/house/house_test.go
@@ -112,6 +112,12 @@ that lay in the house that Jack built.`
 	verses = strings.Split(song, "\n\n")
 )
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestVerse(t *testing.T) {
 	for v := 0; v < len(verses); v++ {
 		if ret := Verse(v + 1); ret != verses[v] {
@@ -146,9 +152,3 @@ func TestSong(t *testing.T) {
 	}
 	t.Fatalf("Song() line %d =\n%q\n want \n%q", i+1, g, w)
 }
-
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
-	}
-}
diff --git a/exercises/isogram/isogram_test.go b/exercises/isogram/isogram_test.go
index ba8d35f0..29ee67dc 100644
--- a/exercises/isogram/isogram_test.go
+++ b/exercises/isogram/isogram_test.go
@@ -2,6 +2,8 @@ package isogram
 
 import "testing"
 
+const targetTestVersion = 1
+
 var testCases = []struct {
 	word     string
 	expected bool
@@ -18,6 +20,12 @@ var testCases = []struct {
 	{"éléphant", false},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestIsIsogram(t *testing.T) {
 	for _, c := range testCases {
 		if IsIsogram(c.word) != c.expected {
@@ -40,11 +48,3 @@ func BenchmarkIsIsogram(b *testing.B) {
 		b.StopTimer()
 	}
 }
-
-const targetTestVersion = 1
-
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
diff --git a/exercises/kindergarten-garden/kindergarten_garden_test.go b/exercises/kindergarten-garden/kindergarten_garden_test.go
index 678d2f60..3d728bbf 100644
--- a/exercises/kindergarten-garden/kindergarten_garden_test.go
+++ b/exercises/kindergarten-garden/kindergarten_garden_test.go
@@ -26,12 +26,6 @@ import (
 
 const targetTestVersion = 1
 
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
-
 type lookup struct {
 	child  string
 	plants []string
@@ -125,6 +119,12 @@ RVGCCGCV`, append([]string{}, test6names...), true, []lookup{
 	}}
 )
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestGarden(t *testing.T) {
 	for _, test := range tests {
 		g, err := NewGarden(test.diagram, test.children)
diff --git a/exercises/leap/leap_test.go b/exercises/leap/leap_test.go
index b14ddeb1..67a1d4f9 100644
--- a/exercises/leap/leap_test.go
+++ b/exercises/leap/leap_test.go
@@ -9,10 +9,13 @@ import "testing"
 
 const targetTestVersion = 3
 
-func TestLeapYears(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestLeapYears(t *testing.T) {
 	for _, test := range testCases {
 		observed := IsLeapYear(test.year)
 		if observed != test.expected {
diff --git a/exercises/ledger/ledger_test.go b/exercises/ledger/ledger_test.go
index 0039201d..716f23e1 100644
--- a/exercises/ledger/ledger_test.go
+++ b/exercises/ledger/ledger_test.go
@@ -250,10 +250,13 @@ var failureTestCases = []struct {
 	},
 }
 
-func TestFormatLedgerSuccess(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestFormatLedgerSuccess(t *testing.T) {
 	for _, tt := range successTestCases {
 		actual, err := FormatLedger(tt.currency, tt.locale, tt.entries)
 		// We don't expect errors for any of the test cases
diff --git a/exercises/luhn/luhn_test.go b/exercises/luhn/luhn_test.go
index 3dcd7bea..765f03f0 100644
--- a/exercises/luhn/luhn_test.go
+++ b/exercises/luhn/luhn_test.go
@@ -26,7 +26,7 @@ func TestTestVersion(t *testing.T) {
 func TestValid(t *testing.T) {
 	for _, test := range testCases {
 		if ok := Valid(test.input); ok != test.ok {
-			t.Fatalf("Valid(%s): %s\n\t Expected: %t\n\t Got: %t", test.input, test.description, ok, test.ok)
+			t.Fatalf("Valid(%s): %s\n\t Expected: %t\n\t Got: %t", test.input, test.description, test.ok, ok)
 		}
 	}
 }
diff --git a/exercises/matrix/matrix_test.go b/exercises/matrix/matrix_test.go
index b90a0b94..4d14809e 100644
--- a/exercises/matrix/matrix_test.go
+++ b/exercises/matrix/matrix_test.go
@@ -142,6 +142,12 @@ var tests = []struct {
 	// {"",     // valid?, 0 rows, 0 columns
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestNew(t *testing.T) {
 	for _, test := range tests {
 		m, err := New(test.in)
@@ -266,9 +272,3 @@ func TestSet(t *testing.T) {
 		}
 	}
 }
-
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
diff --git a/exercises/meetup/meetup_test.go b/exercises/meetup/meetup_test.go
index 28f3d9f8..f5d555f1 100644
--- a/exercises/meetup/meetup_test.go
+++ b/exercises/meetup/meetup_test.go
@@ -25,10 +25,13 @@ var weekName = map[WeekSchedule]string{
 	Last:   "last",
 }
 
-func TestDay(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestDay(t *testing.T) {
 	for _, test := range testCases {
 		res := Day(test.week, test.weekday, test.month, test.year)
 		if res != test.expDay {
diff --git a/exercises/minesweeper/minesweeper_test.go b/exercises/minesweeper/minesweeper_test.go
index 530cbf08..ad15bc0b 100644
--- a/exercises/minesweeper/minesweeper_test.go
+++ b/exercises/minesweeper/minesweeper_test.go
@@ -151,6 +151,12 @@ func clear(s string) Board {
 	return bytes.Split(b, []byte{'\n'})[1:]
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestValid(t *testing.T) {
 	for _, ref := range validBoards {
 		b := clear(ref)
@@ -175,12 +181,6 @@ func TestBad(t *testing.T) {
 	}
 }
 
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
-
 func BenchmarkCount(b *testing.B) {
 	m := clear(`
 +------+
diff --git a/exercises/nth-prime/nth_prime_test.go b/exercises/nth-prime/nth_prime_test.go
index 03f23e80..ae5203bb 100644
--- a/exercises/nth-prime/nth_prime_test.go
+++ b/exercises/nth-prime/nth_prime_test.go
@@ -2,6 +2,8 @@ package prime
 
 import "testing"
 
+const targetTestVersion = 1
+
 var tests = []struct {
 	n  int
 	p  int
@@ -17,6 +19,12 @@ var tests = []struct {
 	{0, 0, false},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
+	}
+}
+
 func TestNth(t *testing.T) {
 	for _, test := range tests {
 		switch p, ok := Nth(test.n); {
diff --git a/exercises/nucleotide-count/nucleotide_count_test.go b/exercises/nucleotide-count/nucleotide_count_test.go
index 0c3d7b61..057a4f6e 100644
--- a/exercises/nucleotide-count/nucleotide_count_test.go
+++ b/exercises/nucleotide-count/nucleotide_count_test.go
@@ -5,6 +5,8 @@ import (
 	"testing"
 )
 
+const targetTestVersion = 2
+
 var tallyTests = []struct {
 	strand     DNA
 	nucleotide byte
@@ -16,6 +18,12 @@ var tallyTests = []struct {
 	{"GGGGGTAACCCGG", 'T', 1},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestNucleotideCounts(t *testing.T) {
 	for _, tt := range tallyTests {
 		if count, err := tt.strand.Count(tt.nucleotide); err != nil {
@@ -101,11 +109,3 @@ func BenchmarkSequenceHistograms(b *testing.B) {
 		}
 	}
 }
-
-const targetTestVersion = 2
-
-func TestTestVersion(t *testing.T) {
-	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
-	}
-}
diff --git a/exercises/pangram/pangram_test.go b/exercises/pangram/pangram_test.go
index 0ef2fb4d..24ced592 100644
--- a/exercises/pangram/pangram_test.go
+++ b/exercises/pangram/pangram_test.go
@@ -26,7 +26,7 @@ var testCases = []testCase{
 
 func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
-		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
 	}
 }
 
@@ -41,3 +41,11 @@ func TestPangram(t *testing.T) {
 		}
 	}
 }
+
+func BenchmarkPangram(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		for _, test := range testCases {
+			IsPangram(test.input)
+		}
+	}
+}
diff --git a/exercises/react/react_test.go b/exercises/react/react_test.go
index 0cc5f250..bab58df8 100644
--- a/exercises/react/react_test.go
+++ b/exercises/react/react_test.go
@@ -11,7 +11,7 @@ import (
 // Also define a testVersion with a value that matches
 // the targetTestVersion here.
 
-const targetTestVersion = 4
+const targetTestVersion = 5
 
 // This is a compile time check to see if you've properly implemented New().
 var _ Reactor = New()
@@ -182,7 +182,7 @@ func TestCallbackAddRemove(t *testing.T) {
 	if len(observed2) != 1 || observed2[0] != 3 {
 		t.Fatalf("observed2 not properly called")
 	}
-	c.RemoveCallback(cb1)
+	cb1.Cancel()
 	i.SetValue(3)
 	if len(observed1) != 1 {
 		t.Fatalf("observed1 called after removal")
@@ -200,11 +200,11 @@ func TestMultipleCallbackRemoval(t *testing.T) {
 	numCallbacks := 5
 
 	calls := make([]int, numCallbacks)
-	handles := make([]CallbackHandle, numCallbacks)
+	cancelers := make([]Canceler, numCallbacks)
 	for i := 0; i < numCallbacks; i++ {
 		// Rebind i, otherwise all callbacks will use i = numCallbacks
 		i := i
-		handles[i] = c.AddCallback(func(v int) { calls[i]++ })
+		cancelers[i] = c.AddCallback(func(v int) { calls[i]++ })
 	}
 
 	inp.SetValue(2)
@@ -212,7 +212,7 @@ func TestMultipleCallbackRemoval(t *testing.T) {
 		if calls[i] != 1 {
 			t.Fatalf("callback %d/%d should be called 1 time, was called %d times", i+1, numCallbacks, calls[i])
 		}
-		c.RemoveCallback(handles[i])
+		cancelers[i].Cancel()
 	}
 
 	inp.SetValue(3)
@@ -231,7 +231,7 @@ func TestRemoveIdempotence(t *testing.T) {
 	cb1 := output.AddCallback(func(int) {})
 	output.AddCallback(func(int) { timesCalled++ })
 	for i := 0; i < 10; i++ {
-		output.RemoveCallback(cb1)
+		cb1.Cancel()
 	}
 	inp.SetValue(2)
 	if timesCalled != 1 {
diff --git a/exercises/variable-length-quantity/variable_length_quantity_test.go b/exercises/variable-length-quantity/variable_length_quantity_test.go
index 0118eeac..23148c88 100644
--- a/exercises/variable-length-quantity/variable_length_quantity_test.go
+++ b/exercises/variable-length-quantity/variable_length_quantity_test.go
@@ -5,6 +5,8 @@ import (
 	"testing"
 )
 
+const targetTestVersion = 1
+
 var testCases = []struct {
 	input  []byte
 	output uint32
@@ -23,6 +25,12 @@ var testCases = []struct {
 	10: {[]byte{0x81, 0x10}, 144, 1},
 }
 
+func TestTestVersion(t *testing.T) {
+	if testVersion != targetTestVersion {
+		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
+	}
+}
+
 func TestDecodeVarint(t *testing.T) {
 	for i, tc := range testCases {
 		o, size := DecodeVarint(tc.input)
diff --git a/exercises/word-count/cases_test.go b/exercises/word-count/cases_test.go
index 42b4bc0a..e4e79228 100644
--- a/exercises/word-count/cases_test.go
+++ b/exercises/word-count/cases_test.go
@@ -1,6 +1,7 @@
 package wordcount
 
 // Source: exercism/x-common
+// Commit: f2ab262 word-count: replace underscore with space in description (#483)
 
 var testCases = []struct {
 	description string
diff --git a/exercises/word-count/word_count_test.go b/exercises/word-count/word_count_test.go
index b30f665c..865507c2 100644
--- a/exercises/word-count/word_count_test.go
+++ b/exercises/word-count/word_count_test.go
@@ -7,10 +7,13 @@ import (
 
 const targetTestVersion = 3
 
-func TestWordCount(t *testing.T) {
+func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
 		t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
 	}
+}
+
+func TestWordCount(t *testing.T) {
 	for _, tt := range testCases {
 		expected := tt.output
 		actual := WordCount(tt.input)