├── .gitignore ├── COPYING ├── Makefile ├── README.markdown ├── bin └── specify └── src ├── spec_helpers.go ├── dotReporter.go ├── report.go ├── example_spec.go ├── Makefile ├── specdocReporter.go ├── assertion.go ├── beMatcher.go ├── location.go ├── complexExample.go ├── exampleCollection.go ├── equalityMatcher.go ├── before_spec.go ├── after_spec.go ├── runner.go ├── outputReporter.go ├── basicReporter.go ├── simpleExample.go ├── basic_spec.go ├── matcher_spec.go ├── specify.go └── spec_matchers.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | *.8 3 | *.a 4 | _test 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2010 Samuel Tesla 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2010 Samuel Tesla 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | 22 | test: 23 | cd src; make test 24 | 25 | clean: 26 | cd src; make clean 27 | 28 | install: 29 | cd src; make install 30 | cp bin/specify $(GOROOT)/bin 31 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # gospecify 2 | 3 | This provides a BDD syntax for testing your Go code. It should be familiar to anybody who has used libraries such as rspec. 4 | 5 | ## Installation 6 | 7 | The Makefile assumes that you have the environment variables that are typically set for using the Go language (GOROOT, GOARCH, GOOS, and optionally GOBIN). Please refer to the GO installation instructions (http://golang.org/doc/install.html) for more information on properly setting them. 8 | 9 | Once those variables are set you can: 10 | 11 | $ make test 12 | $ make install # This will install the specify script in $HOME/bin 13 | $ make install GOBIN=$GOBIN # This will install the specify script in $GOBIN 14 | 15 | ## Usage 16 | 17 | Take a look at src/example_spec.go for a simple example of how to write specifications using gospecify. Just put the code in package main and import your own code in. You can then use the specify command to compile and run your specs. 18 | 19 | $ specify *_spec.go 20 | 21 | Or if you need to specify a package path you can do this: 22 | 23 | $ specify -I/path/to/pkg *_spec.go 24 | 25 | You can look at src/Makefile to see how gospecify runs the command to test itself. 26 | 27 | ## Contributing 28 | 29 | Contributions are always welcome. Just clone the git repo and hack away. You can submit pull requests or email me patches. 30 | 31 | * GitHub: http://github.com/stesla/gospecify 32 | * Email: samuel.tesla@gmail.com 33 | 34 | Happy Testing! 35 | -------------------------------------------------------------------------------- /bin/specify: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GOROOT=${GOROOT:-$HOME/go} 4 | if [ ! -d "$GOROOT" ]; then 5 | echo 'Must set $GOROOT to a directory' 1>&2 6 | exit 2 7 | fi 8 | 9 | eval $(gomake -j1 --no-print-directory -f $GOROOT/src/Make.inc go-env) 10 | if [ -z "$MAKE_GO_ENV_WORKED" ]; then 11 | echo 'Failed to initialize Go environment' 1>&2 12 | exit 2 13 | fi 14 | 15 | while [ $# -ne 0 ]; do 16 | param=$1 17 | shift 18 | case $param in 19 | -I) lib=$1 ; libs="$libs -I$lib"; shift ;; 20 | -format) format=$1; shift;; 21 | *) files="$param $files" 22 | esac 23 | done 24 | 25 | function create_main { 26 | cat > _specify_.go < 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import ( 25 | "os" 26 | t "../src/_test/specify" 27 | ) 28 | 29 | func makeError(s string) os.Error { return os.NewError(s) } 30 | 31 | func testRun(name string, block func(t.Runner)) (reporter t.ReporterSummary) { 32 | runner := t.NewRunner() 33 | runner.Describe(name, func() { block(runner) }) 34 | reporter = t.NewBasicReporter() 35 | runner.Run(reporter) 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /src/dotReporter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "fmt" 26 | ) 27 | 28 | type dotFormat int 29 | 30 | func makeDotReporter() ReporterSummary { return makeOutputReporter(dotFormat(0)) } 31 | 32 | func (dotFormat) Error(r Report) { fmt.Print("E") } 33 | func (dotFormat) Fail(r Report) { fmt.Print("F") } 34 | func (dotFormat) Pass(r Report) { fmt.Print(".") } 35 | func (dotFormat) Pending(r Report) { fmt.Print("*") } 36 | -------------------------------------------------------------------------------- /src/report.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import "os" 25 | 26 | type report struct { 27 | title string 28 | err os.Error 29 | loc Location 30 | } 31 | 32 | func newReport(title string, err os.Error, loc Location) report { 33 | return report{title, err, loc} 34 | } 35 | 36 | func (self report) Title() string { return self.title } 37 | func (self report) Error() os.Error { return self.err } 38 | func (self report) Location() Location { return self.loc } 39 | -------------------------------------------------------------------------------- /src/example_spec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import . "specify" 25 | 26 | func init() { 27 | Describe("Math", func() { 28 | It("adds", func(e Example) { e.Value(1 + 1).Should(Be(2)) }) 29 | 30 | It("multiplies", func(e Example) { 31 | e.Value(3 * 3).Should(Be(9)) 32 | e.Value(2 * 4).ShouldNot(Be(6)) 33 | }) 34 | }) 35 | 36 | Describe("String", func() { 37 | It("concatenates", func(e Example) { 38 | e.Value("Doctor" + "Donna").Should(Be("DoctorDonna")) 39 | e.Value("foo" + "bar").ShouldNot(Be("bar")) 40 | }) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2010 Samuel Tesla 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | include $(GOROOT)/src/Make.inc 22 | 23 | TARG=specify 24 | GOFILES=\ 25 | assertion.go \ 26 | basicReporter.go \ 27 | beMatcher.go \ 28 | complexExample.go \ 29 | dotReporter.go \ 30 | equalityMatcher.go \ 31 | exampleCollection.go \ 32 | location.go \ 33 | outputReporter.go \ 34 | report.go \ 35 | runner.go \ 36 | simpleExample.go \ 37 | specdocReporter.go \ 38 | specify.go 39 | 40 | include $(GOROOT)/src/Make.pkg 41 | 42 | format: 43 | gofmt -w *.go 44 | 45 | test: testpackage 46 | specify spec_*.go *_spec.go -------------------------------------------------------------------------------- /src/specdocReporter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "fmt" 26 | ) 27 | 28 | type specdocFormat int 29 | 30 | func makeSpecdocReporter() ReporterSummary { return makeOutputReporter(specdocFormat(0)) } 31 | 32 | func (specdocFormat) Error(r Report) { fmt.Printf("- %s (ERROR)\n", r.Title()) } 33 | func (specdocFormat) Fail(r Report) { fmt.Printf("- %s (FAILED)\n", r.Title()) } 34 | func (specdocFormat) Pass(r Report) { fmt.Printf("- %s\n", r.Title()) } 35 | func (specdocFormat) Pending(r Report) { 36 | fmt.Printf("- %s (Not yet implemented)\n", r.Title()) 37 | } 38 | -------------------------------------------------------------------------------- /src/assertion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "os" 26 | "runtime" 27 | ) 28 | 29 | type assertion struct { 30 | example *simpleExample 31 | value interface{} 32 | } 33 | 34 | func (self assertion) fail(err os.Error) { 35 | self.example.fail <- newReport(self.example.Title(), err, newAssertionLocation()) 36 | runtime.Goexit() 37 | } 38 | 39 | func (self assertion) Should(matcher Matcher) { 40 | if err := matcher.Should(self.value); err != nil { 41 | self.fail(err) 42 | } 43 | } 44 | 45 | func (self assertion) ShouldNot(matcher Matcher) { 46 | if err := matcher.ShouldNot(self.value); err != nil { 47 | self.fail(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/beMatcher.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "fmt" 26 | "os" 27 | ) 28 | 29 | type beMatcher struct { 30 | expected interface{} 31 | } 32 | 33 | func makeBeMatcher(value interface{}) Matcher { return beMatcher{value} } 34 | 35 | func (self beMatcher) Should(actual interface{}) (error os.Error) { 36 | if actual != self.expected { 37 | error = os.NewError(fmt.Sprintf("expected `%v` to be `%v`", actual, self.expected)) 38 | } 39 | return 40 | } 41 | 42 | func (self beMatcher) ShouldNot(actual interface{}) (error os.Error) { 43 | if actual == self.expected { 44 | error = os.NewError(fmt.Sprintf("expected `%v` not to be `%v`", actual, self.expected)) 45 | } 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /src/location.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "fmt" 26 | "runtime" 27 | ) 28 | 29 | type location struct { 30 | file string 31 | line int 32 | } 33 | 34 | const ( 35 | assertionDepth int = 5 36 | errorDepth int = 3 37 | ) 38 | 39 | var blockDepth int = 3 40 | 41 | func AdjustBlockDepth(delta int) { blockDepth += delta } 42 | func newAssertionLocation() location { return newLocation(assertionDepth) } 43 | func newErrorLocation() location { return newLocation(errorDepth) } 44 | func newBlockLocation() location { return newLocation(blockDepth) } 45 | 46 | func newLocation(depth int) location { 47 | if _, file, line, ok := runtime.Caller(depth); ok { 48 | return location{file, line} 49 | } 50 | panic("newLocation") 51 | } 52 | 53 | func (self location) String() string { return fmt.Sprintf("%v:%d", self.file, self.line) } 54 | -------------------------------------------------------------------------------- /src/complexExample.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | type complexExample struct { 25 | name string 26 | afterBlock afterBlock 27 | beforeBlock BeforeBlock 28 | block ExampleGroupBlock 29 | *exampleCollection 30 | } 31 | 32 | func makeComplexExample(name string, block ExampleGroupBlock) *complexExample { 33 | return &complexExample{name, emptyAfter, emptyBefore, block, makeExampleCollection()} 34 | } 35 | 36 | func (self *complexExample) AddBefore(block BeforeBlock) { 37 | self.beforeBlock = block 38 | } 39 | 40 | func (self *complexExample) AddAfter(block afterBlock) { 41 | self.afterBlock = block 42 | } 43 | 44 | func (self *complexExample) Init() { self.block() } 45 | func (self *complexExample) Run(reporter Reporter, _ BeforeBlock, _ afterBlock) { 46 | /* TODO: Nested describes get weird with before blocks */ 47 | self.exampleCollection.Run(reporter, self.beforeBlock, self.afterBlock) 48 | } 49 | -------------------------------------------------------------------------------- /src/exampleCollection.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import "container/list" 25 | 26 | type example interface { 27 | Run(r Reporter, before BeforeBlock, after afterBlock) 28 | } 29 | 30 | type exampleCollection struct { 31 | examples *list.List 32 | } 33 | 34 | func makeExampleCollection() *exampleCollection { 35 | return &exampleCollection{list.New()} 36 | } 37 | 38 | func (self *exampleCollection) Add(e example) { self.examples.PushBack(e) } 39 | 40 | func (self *exampleCollection) Init(runner *runner) { 41 | for e := self.examples.Front(); e != nil; e = e.Next() { 42 | if ex, ok := e.Value.(*complexExample); ok { 43 | runner.currentExample = ex 44 | ex.Init() 45 | } 46 | } 47 | } 48 | 49 | func (self *exampleCollection) Run(reporter Reporter, before BeforeBlock, after afterBlock) { 50 | for e := self.examples.Front(); e != nil; e = e.Next() { 51 | if ex, ok := e.Value.(example); ok { 52 | ex.Run(reporter, before, after) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/equalityMatcher.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "fmt" 26 | "os" 27 | ) 28 | 29 | type equalto interface { 30 | EqualTo(interface{}) bool 31 | } 32 | 33 | type equalityMatcher struct { 34 | expected interface{} 35 | } 36 | 37 | func newEqualityMatcher(value interface{}) equalityMatcher { 38 | return equalityMatcher{value} 39 | } 40 | 41 | func (self equalityMatcher) test(val interface{}) bool { 42 | switch t := self.expected.(type) { 43 | case equalto: 44 | return t.EqualTo(val) 45 | } 46 | return val == self.expected 47 | } 48 | 49 | func (self equalityMatcher) Should(actual interface{}) os.Error { 50 | if !self.test(actual) { 51 | return os.NewError(fmt.Sprintf("expected `%v` to be equal to `%v`", actual, self.expected)) 52 | } 53 | return nil 54 | } 55 | 56 | func (self equalityMatcher) ShouldNot(actual interface{}) os.Error { 57 | if self.test(actual) { 58 | return os.NewError(fmt.Sprintf("expected `%v` not to be equal to `%v`", actual, self.expected)) 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /src/before_spec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import ( 25 | . "specify" 26 | t "./_test/specify" 27 | ) 28 | 29 | func init() { 30 | Describe("Before", func() { 31 | It("should run the block before each test", func(e Example) { 32 | reporter := testRun("", func(r t.Runner) { 33 | r.Before(func(e t.Example) { e.SetField("value", 42) }) 34 | 35 | r.It("should set the value this time", func(e t.Example) { 36 | e.Field("value").Should(t.Be(42)) 37 | e.SetField("value", 24) 38 | }) 39 | 40 | r.It("should set this time too", func(e t.Example) { 41 | e.Field("value").Should(t.Be(42)) 42 | e.SetField("value", 24) 43 | }) 44 | }) 45 | 46 | e.Value(reporter).Should(HavePassing(2)) 47 | }) 48 | 49 | It("should fail examples if assertions fail in the before block", func(e Example) { 50 | reporter := testRun("", func(r t.Runner) { 51 | r.Before(func(e t.Example) { e.Value(1).Should(Be(2)) }) 52 | r.It("should fail in before", func(t.Example) {}) 53 | }) 54 | 55 | e.Value(reporter).Should(HaveFailing(1)) 56 | }) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /src/after_spec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import ( 25 | . "specify" 26 | t "./_test/specify" 27 | ) 28 | 29 | func pass(t.Example) {} 30 | 31 | func init() { 32 | Describe("After", func() { 33 | It("should run the block after each test", func(e Example) { 34 | ch := make(chan bool, 1) 35 | testRun("", func(r t.Runner) { 36 | r.It("should pass", pass) 37 | r.After(func(t.Context) { ch <- true }) 38 | }) 39 | _, ok := <-ch 40 | e.Value(ok).Should(Be(true)) 41 | }) 42 | 43 | It("should fail a test if the after has an error", func(e Example) { 44 | reporter := testRun("", func(r t.Runner) { 45 | r.It("should pass", pass) 46 | r.After(func(c t.Context) { c.Error(makeError("boom")) }) 47 | }) 48 | e.Value(reporter).Should(HaveErrorAt("after_spec.go:46")) 49 | }) 50 | 51 | It("should see the fields set in the example", func(e Example) { 52 | ch := make(chan interface{}, 1) 53 | testRun("", func(r t.Runner) { 54 | r.It("should pass", func(e t.Example) { e.SetField("foo", "bar") }) 55 | r.After(func(c t.Context) { ch <- c.GetField("foo") }) 56 | }) 57 | e.Value(<-ch).Should(Be("bar")) 58 | }) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /src/runner.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | type runner struct { 25 | examples *exampleCollection 26 | currentExample *complexExample 27 | } 28 | 29 | type afterBlock struct { 30 | f AfterFunc 31 | loc Location 32 | } 33 | 34 | var emptyAfter = afterBlock{func(Context) {}, nil} 35 | var emptyBefore = func(Example) {} 36 | 37 | func makeRunner() *runner { return &runner{examples: makeExampleCollection()} } 38 | 39 | func (self *runner) After(f AfterFunc) { 40 | if self.currentExample != nil { 41 | block := afterBlock{f, newBlockLocation()} 42 | self.currentExample.AddAfter(block) 43 | } 44 | } 45 | 46 | func (self *runner) Before(block BeforeBlock) { 47 | if self.currentExample != nil { 48 | self.currentExample.AddBefore(block) 49 | } 50 | } 51 | 52 | func (self *runner) Describe(name string, block ExampleGroupBlock) { 53 | self.examples.Add(makeComplexExample(name, block)) 54 | } 55 | 56 | func (self *runner) It(name string, block ExampleBlock) { 57 | if self.currentExample != nil { 58 | loc := newBlockLocation() 59 | self.currentExample.Add(makeSimpleExample(self.currentExample, name, block, loc)) 60 | } 61 | } 62 | 63 | func (self *runner) Run(reporter Reporter) { 64 | self.examples.Init(self) 65 | self.examples.Run(reporter, emptyBefore, emptyAfter) 66 | reporter.Finish() 67 | } 68 | -------------------------------------------------------------------------------- /src/outputReporter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "fmt" 26 | ) 27 | 28 | type OutputStrategy interface { 29 | Error(Report) 30 | Fail(Report) 31 | Pass(Report) 32 | Pending(Report) 33 | } 34 | 35 | type outputReporter struct { 36 | *basicReporter 37 | output OutputStrategy 38 | } 39 | 40 | func makeOutputReporter(s OutputStrategy) ReporterSummary { 41 | return &outputReporter{NewBasicReporter(), s} 42 | } 43 | 44 | func (self *outputReporter) Error(r Report) { 45 | self.basicReporter.Error(r) 46 | self.output.Error(r) 47 | } 48 | 49 | func (self *outputReporter) Fail(r Report) { 50 | self.basicReporter.Fail(r) 51 | self.output.Fail(r) 52 | } 53 | 54 | func printList(label string, reports <-chan Report) { 55 | fmt.Printf("\n%v:\n", label) 56 | for r := range reports { 57 | fmt.Printf("\n- %v - %v\n %v\n", r.Title(), r.Error(), r.Location()) 58 | } 59 | } 60 | 61 | func (self *outputReporter) Finish() { 62 | fmt.Printf("\nPassing: %v Failing: %v Pending: %v Errors: %v\n", self.PassingCount(), self.FailingCount(), self.PendingCount(), self.ErrorCount()) 63 | if self.ErrorCount() > 0 { 64 | printList("Errors", self.EachError()) 65 | } 66 | if self.FailingCount() > 0 { 67 | printList("Failing Examples", self.EachFailure()) 68 | } 69 | if self.PendingCount() > 0 { 70 | printList("Pending Examples", self.EachPending()) 71 | } 72 | } 73 | 74 | func (self *outputReporter) Pass(r Report) { 75 | self.basicReporter.Pass(r) 76 | self.output.Pass(r) 77 | } 78 | 79 | func (self *outputReporter) Pending(r Report) { 80 | self.basicReporter.Pending(r) 81 | self.output.Pending(r) 82 | } 83 | -------------------------------------------------------------------------------- /src/basicReporter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import "container/list" 25 | 26 | type basicReporter struct { 27 | passing int 28 | failing, pending, errors *list.List 29 | } 30 | 31 | func NewBasicReporter() *basicReporter { 32 | return &basicReporter{failing: list.New(), pending: list.New(), errors: list.New()} 33 | } 34 | 35 | func (self *basicReporter) ErrorCount() int { return self.errors.Len() } 36 | func (self *basicReporter) FailingCount() int { return self.failing.Len() } 37 | func (self *basicReporter) PassingCount() int { return self.passing } 38 | func (self *basicReporter) PendingCount() int { return self.pending.Len() } 39 | func (self *basicReporter) Error(r Report) { self.errors.PushBack(r) } 40 | 41 | func (self *basicReporter) EachError() <-chan Report { 42 | return eachReport(self.errors) 43 | } 44 | func (self *basicReporter) EachFailure() <-chan Report { 45 | return eachReport(self.failing) 46 | } 47 | func (self *basicReporter) EachPending() <-chan Report { 48 | return eachReport(self.pending) 49 | } 50 | 51 | func (self *basicReporter) Fail(r Report) { self.failing.PushBack(r) } 52 | 53 | func (self *basicReporter) Finish() {} 54 | 55 | func (self *basicReporter) Pass(r Report) { self.passing++ } 56 | 57 | func (self *basicReporter) Pending(r Report) { self.pending.PushBack(r) } 58 | 59 | func eachReport(l *list.List) <-chan Report { 60 | ch := make(chan Report, l.Len()) 61 | for e := l.Front(); e != nil; e = e.Next() { 62 | if name, ok := e.Value.(Report); !ok { 63 | panic("typecast error") 64 | } else { 65 | ch <- name 66 | } 67 | } 68 | close(ch) 69 | return ch 70 | } 71 | -------------------------------------------------------------------------------- /src/simpleExample.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "fmt" 26 | "os" 27 | ) 28 | 29 | type simpleExample struct { 30 | parent *complexExample 31 | name string 32 | block ExampleBlock 33 | fields map[string]interface{} 34 | error chan Report 35 | fail chan Report 36 | loc Location 37 | } 38 | 39 | func makeSimpleExample(parent *complexExample, name string, block ExampleBlock, loc Location) *simpleExample { 40 | return &simpleExample{parent, name, block, make(map[string]interface{}), make(chan Report), make(chan Report), loc} 41 | } 42 | 43 | func (self *simpleExample) Title() string { 44 | return fmt.Sprintf("%v %v", self.parent.name, self.name) 45 | } 46 | 47 | func (self *simpleExample) Run(reporter Reporter, before BeforeBlock, after afterBlock) { 48 | if self.block == nil { 49 | reporter.Pending(newReport(self.Title(), os.NewError("not implemented"), self.loc)) 50 | return 51 | } 52 | 53 | pass := make(chan bool) 54 | go func() { 55 | if before != nil { 56 | before(self) 57 | } 58 | self.block(self) 59 | after.f(self) 60 | pass <- true 61 | }() 62 | 63 | select { 64 | case report := <-self.error: 65 | reporter.Error(report) 66 | case report := <-self.fail: 67 | reporter.Fail(report) 68 | case <-pass: 69 | reporter.Pass(newReport(self.Title(), nil, nil)) 70 | } 71 | } 72 | 73 | func (self *simpleExample) Error(err os.Error) { 74 | self.error <- newReport(self.Title(), err, newErrorLocation()) 75 | } 76 | 77 | func (self *simpleExample) GetField(field string) (result interface{}) { 78 | result, _ = self.fields[field] 79 | return 80 | } 81 | 82 | func (self *simpleExample) Field(field string) Assertion { 83 | return self.Value(self.GetField(field)) 84 | } 85 | 86 | func (self *simpleExample) SetField(field string, value interface{}) { 87 | self.fields[field] = value 88 | } 89 | 90 | func (self *simpleExample) Value(value interface{}) Assertion { 91 | return assertion{self, value} 92 | } 93 | -------------------------------------------------------------------------------- /src/basic_spec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import ( 25 | . "specify" 26 | t "./_test/specify" 27 | ) 28 | 29 | func init() { 30 | Describe("Running", func() { 31 | Before(func(e Example) { 32 | reporter := testRun("basic", func(r t.Runner) { 33 | r.It("pass", func(e t.Example) { 34 | e.Value(7 * 6).Should(t.Be(42)) 35 | e.Value(1).ShouldNot(t.Be(2)) 36 | }) 37 | 38 | r.It("fail", func(e t.Example) { 39 | e.Value(7 * 6).ShouldNot(t.Be(42)) 40 | e.Value(1).Should(t.Be(2)) 41 | }) 42 | 43 | r.It("pending", nil) 44 | 45 | r.It("error", func(e t.Example) { e.Error(makeError("catastrophe")) }) 46 | }) 47 | e.SetField("reporter", reporter) 48 | }) 49 | 50 | /* Passing Examples */ 51 | It("counts passing examples", func(e Example) { e.Field("reporter").Should(HavePassing(1)) }) 52 | 53 | /* Failing Examples */ 54 | It("counts failing examples", func(e Example) { e.Field("reporter").Should(HaveFailing(1)) }) 55 | It("reports the example name when failing", func(e Example) { e.Field("reporter").Should(HaveFailureIncluding("basic fail")) }) 56 | It("reports the file and line of a failing expectation", func(e Example) { e.Field("reporter").Should(HaveFailureAt("basic_spec.go:39")) }) 57 | 58 | /* Pending Examples */ 59 | It("counts pending examples", func(e Example) { e.Field("reporter").Should(HavePending(1)) }) 60 | It("reports the example name when pending", func(e Example) { e.Field("reporter").Should(HavePendingIncluding("basic pending")) }) 61 | It("reports the file and line of a pending example", func(e Example) { e.Field("reporter").Should(HavePendingAt("basic_spec.go:43")) }) 62 | 63 | /* Errored Examples */ 64 | It("counts errored examples", func(e Example) { e.Field("reporter").Should(HaveErrors(1)) }) 65 | It("reports the example name when errored", func(e Example) { e.Field("reporter").Should(HaveErrorIncluding("basic error")) }) 66 | It("reports the file and line of an error", func(e Example) { e.Field("reporter").Should(HaveErrorAt("basic_spec.go:45")) }) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /src/matcher_spec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import ( 25 | "bytes" 26 | . "specify" 27 | t "./_test/specify" 28 | ) 29 | 30 | func init() { 31 | Describe("Be", func() { 32 | It("should match reference equality", func(e Example) { 33 | var a, b int 34 | e.Value(&a).Should(t.Be(&a)) 35 | e.Value(&a).ShouldNot(t.Be(&b)) 36 | }) 37 | 38 | It("should not care about the value", func(e Example) { 39 | a := 42 40 | b := 42 41 | e.Value(&a).ShouldNot(t.Be(&b)) 42 | }) 43 | }) 44 | 45 | Describe("BeNil", func() { 46 | It("should match nil", func(e Example) { e.Value(nil).Should(t.BeNil()) }) 47 | It("should not match non-nil values", func(e Example) { e.Value(42).ShouldNot(t.BeNil()) }) 48 | It("should not match zero", func(e Example) { e.Value(0).ShouldNot(t.BeNil()) }) 49 | It("should not match false", func(e Example) { e.Value(false).ShouldNot(t.BeNil()) }) 50 | }) 51 | 52 | Describe("BeFalse", func() { 53 | It("should match false", func(e Example) { e.Value(false).Should(t.BeFalse()) }) 54 | It("should not match true", func(e Example) { e.Value(true).ShouldNot(t.BeFalse()) }) 55 | It("should not match nil", func(e Example) { e.Value(nil).ShouldNot(t.BeFalse()) }) 56 | It("should not match zero", func(e Example) { e.Value(0).ShouldNot(t.BeFalse()) }) 57 | It("should not match other values", func(e Example) { e.Value(42).ShouldNot(t.BeFalse()) }) 58 | }) 59 | 60 | Describe("BeTrue", func() { 61 | It("should match true", func(e Example) { e.Value(true).Should(t.BeTrue()) }) 62 | It("should not match false", func(e Example) { e.Value(false).ShouldNot(t.BeTrue()) }) 63 | It("should not match other values", func(e Example) { e.Value(42).ShouldNot(t.BeTrue()) }) 64 | }) 65 | 66 | Describe("BeEqualTo", func() { 67 | It("should match numbers", func(e Example) { 68 | e.Value(1).Should(t.BeEqualTo(1)) 69 | e.Value(1.2).ShouldNot(t.BeEqualTo(2.1)) 70 | }) 71 | 72 | It("should match strings", func(e Example) { 73 | e.Value("foo").Should(t.BeEqualTo("foo")) 74 | e.Value("Doctor").ShouldNot(t.BeEqualTo("Donna")) 75 | }) 76 | 77 | It("should match things with EqualTo()", func(e Example) { e.Value([]byte{1, 2}).Should(t.BeEqualTo(bslice([]byte{1, 2}))) }) 78 | }) 79 | } 80 | 81 | type bslice []byte 82 | 83 | func (self bslice) EqualTo(value interface{}) bool { 84 | if other, ok := value.([]byte); ok { 85 | return bytes.Equal(([]byte)(self), other) 86 | } 87 | return false 88 | } 89 | -------------------------------------------------------------------------------- /src/specify.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package specify 23 | 24 | import ( 25 | "flag" 26 | "os" 27 | "strings" 28 | ) 29 | 30 | type AfterFunc func(Context) 31 | type BeforeBlock func(Example) 32 | type ExampleBlock func(Example) 33 | type ExampleGroupBlock func() 34 | 35 | type Runner interface { 36 | After(AfterFunc) 37 | Before(BeforeBlock) 38 | Describe(string, ExampleGroupBlock) 39 | It(string, ExampleBlock) 40 | Run(Reporter) 41 | } 42 | 43 | func NewRunner() Runner { return makeRunner() } 44 | 45 | type Location interface { 46 | String() string 47 | } 48 | 49 | type Report interface { 50 | Title() string 51 | Error() os.Error 52 | Location() Location 53 | } 54 | 55 | type Reporter interface { 56 | Error(Report) 57 | Fail(Report) 58 | Finish() 59 | Pass(Report) 60 | Pending(Report) 61 | } 62 | 63 | type Summary interface { 64 | ErrorCount() int 65 | FailingCount() int 66 | PassingCount() int 67 | PendingCount() int 68 | EachError() <-chan Report 69 | EachFailure() <-chan Report 70 | EachPending() <-chan Report 71 | } 72 | 73 | type ReporterSummary interface { 74 | Reporter 75 | Summary 76 | } 77 | 78 | func DotReporter() ReporterSummary { return makeDotReporter() } 79 | func SpecdocReporter() ReporterSummary { return makeSpecdocReporter() } 80 | 81 | type Context interface { 82 | Error(os.Error) 83 | GetField(string) interface{} 84 | SetField(string, interface{}) 85 | } 86 | 87 | type Example interface { 88 | Context 89 | Field(string) Assertion 90 | Value(interface{}) Assertion 91 | } 92 | 93 | type Assertion interface { 94 | Should(Matcher) 95 | ShouldNot(Matcher) 96 | } 97 | 98 | type Matcher interface { 99 | Should(interface{}) os.Error 100 | ShouldNot(interface{}) os.Error 101 | } 102 | 103 | func Be(value interface{}) Matcher { return makeBeMatcher(value) } 104 | func BeNil() Matcher { return Be(nil) } 105 | func BeFalse() Matcher { return Be(false) } 106 | func BeTrue() Matcher { return Be(true) } 107 | func BeEqualTo(value interface{}) Matcher { return newEqualityMatcher(value) } 108 | 109 | func mainReporter(format string) Reporter { 110 | switch format { 111 | case "dot": 112 | return DotReporter() 113 | case "specdoc": 114 | return SpecdocReporter() 115 | } 116 | panic("invalid reporter") 117 | } 118 | 119 | // Exported for the specify command 120 | func Main(runner Runner) { 121 | var format *string = flag.String("format", "dot", "output format, one of: dot, specdoc") 122 | flag.Parse() 123 | AdjustBlockDepth(1) 124 | runner.Run(mainReporter(strings.ToLower(*format))) 125 | } 126 | -------------------------------------------------------------------------------- /src/spec_matchers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2010 Samuel Tesla 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "os" 27 | "specify" 28 | "strings" 29 | 30 | t "../src/_test/specify" 31 | ) 32 | 33 | func HavePassing(expected interface{}) reporterMatcher { 34 | return reporterMatcher{expected, func(r t.ReporterSummary) interface{} { return r.PassingCount() }} 35 | } 36 | 37 | func HavePending(expected interface{}) reporterMatcher { 38 | return reporterMatcher{expected, func(r t.ReporterSummary) interface{} { return r.PendingCount() }} 39 | } 40 | 41 | func HaveFailing(expected interface{}) reporterMatcher { 42 | return reporterMatcher{expected, func(r t.ReporterSummary) interface{} { return r.FailingCount() }} 43 | } 44 | 45 | func HaveErrors(expected interface{}) reporterMatcher { 46 | return reporterMatcher{expected, func(r t.ReporterSummary) interface{} { return r.ErrorCount() }} 47 | } 48 | 49 | func HaveFailureIncluding(s string) eachMatcher { 50 | return eachMatcher{s, "failing example", eachFailure, matchTitle} 51 | } 52 | 53 | func HavePendingIncluding(s string) eachMatcher { 54 | return eachMatcher{s, "pending example", eachPending, matchTitle} 55 | } 56 | 57 | func HaveErrorIncluding(s string) eachMatcher { 58 | return eachMatcher{s, "error", eachError, matchTitle} 59 | } 60 | 61 | func HaveFailureAt(loc string) eachMatcher { 62 | return eachMatcher{loc, "failure", eachFailure, matchLocation} 63 | } 64 | 65 | func HavePendingAt(loc string) eachMatcher { 66 | return eachMatcher{loc, "pending example", eachPending, matchLocation} 67 | } 68 | 69 | func HaveErrorAt(loc string) eachMatcher { 70 | return eachMatcher{loc, "error", eachError, matchLocation} 71 | } 72 | 73 | func toReporterSummary(value interface{}) (reporter t.ReporterSummary, err os.Error) { 74 | var ok bool 75 | if reporter, ok = value.(t.ReporterSummary); !ok { 76 | err = os.NewError("Not a t.ReporterSummary") 77 | } 78 | return 79 | } 80 | 81 | type reporterMatcher struct { 82 | expected interface{} 83 | actualFunc func(t.ReporterSummary) interface{} 84 | } 85 | 86 | func (self reporterMatcher) Should(actual interface{}) (result os.Error) { 87 | if reporter, error := toReporterSummary(actual); error != nil { 88 | result = error 89 | } else { 90 | result = specify.Be(self.expected).Should(self.actualFunc(reporter)) 91 | } 92 | return 93 | } 94 | func (self reporterMatcher) ShouldNot(actual interface{}) (result os.Error) { 95 | if reporter, error := toReporterSummary(actual); error != nil { 96 | result = error 97 | } else { 98 | result = specify.Be(self.expected).ShouldNot(self.actualFunc(reporter)) 99 | } 100 | return 101 | } 102 | 103 | type eachMatcher struct { 104 | s, message string 105 | each func(t.ReporterSummary) <-chan t.Report 106 | f func(t.Report, string) bool 107 | } 108 | 109 | func (self eachMatcher) match(r t.ReporterSummary) bool { 110 | for report := range self.each(r) { 111 | if self.f(report, self.s) { 112 | return true 113 | } 114 | } 115 | return false 116 | } 117 | func (self eachMatcher) Should(val interface{}) os.Error { 118 | if reporter, error := toReporterSummary(val); error != nil { 119 | return error 120 | } else { 121 | if !self.match(reporter) { 122 | return os.NewError(fmt.Sprintf("expected %v including `%v`", self.message, self.s)) 123 | } 124 | } 125 | return nil 126 | } 127 | func (eachMatcher) ShouldNot(val interface{}) os.Error { 128 | return os.NewError("matcher not implemented") 129 | } 130 | 131 | func matchTitle(r t.Report, s string) bool { return strings.Count(r.Title(), s) > 0 } 132 | 133 | func matchLocation(r t.Report, s string) bool { 134 | return strings.HasSuffix(r.Location().String(), s) 135 | } 136 | 137 | func eachFailure(r t.ReporterSummary) <-chan t.Report { 138 | return r.EachFailure() 139 | } 140 | 141 | func eachPending(r t.ReporterSummary) <-chan t.Report { 142 | return r.EachPending() 143 | } 144 | 145 | func eachError(r t.ReporterSummary) <-chan t.Report { 146 | return r.EachError() 147 | } 148 | --------------------------------------------------------------------------------