generate - testing

This commit is contained in:
Arpad Ryszka 2018-01-08 16:49:04 +01:00
parent 9e25538b94
commit 6178b0c7a1
12 changed files with 980 additions and 610 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.test
*.out
.coverprofile
.coverprofile-cmd
codecov
cmd/treerack/treerack

View File

@ -44,9 +44,11 @@ regenerate: $(SOURCES) $(PARSERS) head install
check: imports build $(PARSERS)
go test -test.short -run ^Test
go test ./cmd/treerack -test.short -run ^Test
checkall: imports build $(PARSERS)
go test
go test ./cmd/treerack
.coverprofile: $(SOURCES) imports
go test -coverprofile .coverprofile
@ -57,6 +59,16 @@ cover: .coverprofile
showcover: .coverprofile
go tool cover -html .coverprofile
.coverprofile-cmd: $(SOURCES) imports
go test ./cmd/treerack -coverprofile .coverprofile-cmd
cover-cmd: .coverprofile-cmd
go tool cover -func .coverprofile-cmd
showcover-cmd: .coverprofile-cmd
go tool cover -html .coverprofile-cmd
# command line interface not included
publishcoverage: .coverprofile
curl -s https://codecov.io/bash -o codecov
bash codecov -Zf .coverprofile
@ -76,7 +88,7 @@ checkfmt: $(SOURCES)
@if [ "$$(gofmt -s -d $(SOURCES))" != "" ]; then false; else true; fi
vet:
go vet
go vet ./...
precommit: regenerate fmt vet build checkall

View File

@ -15,11 +15,11 @@ const syntaxFileUsage = "path to the syntax file in treerack format"
const syntaxStringUsage = "inline syntax in treerack format"
const packageNameUsage = `package name of the generated code`
const packageNameUsage = `package name of the generated Go code`
const exportUsage = `when the export flag is set, the generated code will have exported symbols to allow using it as a separate package`
const generateUsage = `treerack generate takes a syntax description from the standard input, or a file, or inline string, and generates code implementing a parser. It prints the parser code to the standard output.`
const generateUsage = `treerack generate takes a syntax description from the standard input, or a file, or inline string, and generates parser code implementing the described syntax. It prints the parser code to the standard output.`
const generateExample = `Example:
treerack generate < syntax.treerack > parser.go`
treerack generate -syntax syntax.treerack > parser.go`

View File

@ -0,0 +1 @@
foo = "bar"

View File

@ -18,8 +18,6 @@ type generateOptions struct {
export bool
}
var isTest bool
func flagSet(o *generateOptions, output io.Writer) *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.Usage = func() {}
@ -35,10 +33,12 @@ func helpGenerate() {
stdout(generateUsage)
stdout()
stdout("Options:")
fs := flagSet(&generateOptions{}, os.Stdout)
fs := flagSet(&generateOptions{}, wout)
fs.PrintDefaults()
stdout()
stdout(generateExample)
stdout()
stdout(docRef)
}
func flagError(fs *flag.FlagSet) {
@ -68,7 +68,7 @@ func generate(args []string) int {
}
var options generateOptions
fs := flagSet(&options, os.Stderr)
fs := flagSet(&options, werr)
if err := fs.Parse(args); err != nil {
flagError(fs)
return -1
@ -81,8 +81,7 @@ func generate(args []string) int {
var hasInput bool
if options.syntaxFile == "" && options.syntax == "" {
fdint := int(os.Stdin.Fd())
hasInput = !isTest && !terminal.IsTerminal(fdint)
hasInput = isTest && rin != nil || !isTest && !terminal.IsTerminal(0)
}
if !hasInput && options.syntaxFile == "" && options.syntax == "" {
@ -92,7 +91,7 @@ func generate(args []string) int {
var input io.Reader
if hasInput {
input = os.Stdin
input = rin
} else if options.syntaxFile != "" {
f, err := os.Open(options.syntaxFile)
if err != nil {
@ -116,7 +115,7 @@ func generate(args []string) int {
goptions.PackageName = options.packageName
goptions.Export = options.export
if err := s.Generate(goptions, os.Stdout); err != nil {
if err := s.Generate(goptions, wout); err != nil {
stderr(err)
return -1
}

View File

@ -0,0 +1,132 @@
package main
import "testing"
func TestGenerate(t *testing.T) {
runMainTest(t,
mainTest{
title: "help",
args: []string{
"treerack", "generate", "-help",
},
stdout: []string{
generateUsage,
"-export",
"-package-name",
"-syntax",
"-syntax-string",
generateExample,
docRef,
},
},
mainTest{
title: "invalid flag",
args: []string{
"treerack", "generate", "-foo",
},
exit: -1,
stderr: []string{
"-export",
"-package-name",
"-syntax",
"-syntax-string",
},
},
mainTest{
title: "multiple inputs",
args: []string{
"treerack", "generate", "-syntax", "foo.treerack", "-syntax-string", `foo = "bar"`,
},
exit: -1,
stderr: []string{
"only one",
"-export",
"-package-name",
"-syntax",
"-syntax-string",
},
},
mainTest{
title: "no input",
args: []string{
"treerack", "generate",
},
exit: -1,
stderr: []string{
"missing syntax input",
"-export",
"-package-name",
"-syntax",
"-syntax-string",
},
},
mainTest{
title: "invalid input",
args: []string{
"treerack", "generate", "-syntax-string", "foo",
},
exit: -1,
stderr: []string{
"parse failed",
},
},
mainTest{
title: "file open fails",
args: []string{
"treerack", "generate", "-syntax", "noexist.treerack",
},
exit: -1,
stderr: []string{
"file",
},
},
mainTest{
title: "failing output",
args: []string{
"treerack", "generate", "-syntax-string", `foo = "bar"`,
},
failingOutput: true,
exit: -1,
},
mainTest{
title: "syntax as stdin",
args: []string{
"treerack", "generate", "-export", "-package-name", "foo",
},
stdin: `foo = "bar"`,
stdout: []string{
"package foo",
"func Parse",
},
},
mainTest{
title: "syntax as file",
args: []string{
"treerack", "generate", "-export", "-package-name", "foo", "-syntax", "foo_test.treerack",
},
stdout: []string{
"package foo",
"func Parse",
},
},
mainTest{
title: "syntax as string",
args: []string{
"treerack", "generate", "-export", "-package-name", "foo", "-syntax-string", `foo = "bar"`,
},
stdout: []string{
"package foo",
"func Parse",
},
},
)
}

29
cmd/treerack/io.go Normal file
View File

@ -0,0 +1,29 @@
package main
import (
"fmt"
"io"
"os"
)
type exitFunc func(int)
var (
isTest bool
rin io.Reader = os.Stdin
wout io.Writer = os.Stdout
werr io.Writer = os.Stderr
exit exitFunc = func(code int) {
os.Exit(code)
}
)
func stdout(a ...interface{}) {
fmt.Fprintln(wout, a...)
}
func stderr(a ...interface{}) {
fmt.Fprintln(werr, a...)
}

View File

@ -1,8 +1,6 @@
package main
import (
"os"
)
import "os"
func mainHelp() {
stdout(summary)
@ -17,21 +15,22 @@ func main() {
stderr("missing command")
stderr()
stderr(commandsHelp)
stdout()
stdout(docRef)
os.Exit(-1)
stderr()
stderr(docRef)
exit(-1)
return
}
switch os.Args[1] {
case "generate":
exit := generate(os.Args[2:])
os.Exit(exit)
code := generate(os.Args[2:])
exit(code)
case "help", "-help":
mainHelp()
default:
stderr("invalid command")
stderr()
stderr(commandsHelp)
os.Exit(-1)
exit(-1)
}
}

205
cmd/treerack/main_test.go Normal file
View File

@ -0,0 +1,205 @@
package main
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"strings"
"testing"
)
type mainTest struct {
title string
args []string
failingOutput bool
exit int
stdin string
stdout []string
stderr []string
}
type failingWriter struct{}
var errWriteFailed = errors.New("write failed")
func (w failingWriter) Write([]byte) (int, error) {
return 0, errWriteFailed
}
func init() {
isTest = true
}
func mockArgs(args ...string) (reset func()) {
original := os.Args
os.Args = args
reset = func() {
os.Args = original
}
return
}
func mockStdin(in string) (reset func()) {
original := rin
if in == "" {
rin = nil
} else {
rin = bytes.NewBufferString(in)
}
reset = func() {
rin = original
}
return
}
func mockOutput(w *io.Writer, failing bool) (out fmt.Stringer, reset func()) {
original := *w
reset = func() { *w = original }
if failing {
*w = failingWriter{}
return
}
var buf bytes.Buffer
*w = &buf
out = &buf
return
}
func mockStdout() (out fmt.Stringer, reset func()) {
return mockOutput(&wout, false)
}
func mockStderr() (out fmt.Stringer, reset func()) {
return mockOutput(&werr, false)
}
func mockFailingOutput() (reset func()) {
_, reset = mockOutput(&wout, true)
return
}
func mockExit() (code *int, reset func()) {
var exitCode int
code = &exitCode
original := exit
exit = func(c int) { exitCode = c }
reset = func() { exit = original }
return
}
func (mt mainTest) run(t *testing.T) {
test := func(t *testing.T) {
defer mockArgs(mt.args...)()
defer mockStdin(mt.stdin)()
var stdout fmt.Stringer
if mt.failingOutput {
defer mockFailingOutput()()
} else {
var reset func()
stdout, reset = mockStdout()
defer reset()
}
stderr, resetStderr := mockStderr()
defer resetStderr()
code, resetExit := mockExit()
defer resetExit()
main()
if *code != mt.exit {
t.Error("invalid exit code")
}
if stdout != nil {
for i := range mt.stdout {
if !strings.Contains(stdout.String(), mt.stdout[i]) {
t.Error("invalid output")
t.Log(stdout.String())
}
}
}
for i := range mt.stderr {
if !strings.Contains(stderr.String(), mt.stderr[i]) {
t.Error("invalid error output")
t.Log(stderr.String())
}
}
}
if mt.title == "" {
test(t)
} else {
t.Run(mt.title, test)
}
}
func runMainTest(t *testing.T, mt ...mainTest) {
for i := range mt {
mt[i].run(t)
}
}
func TestMissingCommand(t *testing.T) {
runMainTest(t,
mainTest{
args: []string{"treerack"},
exit: -1,
stderr: []string{
"missing command",
commandsHelp,
docRef,
},
},
)
}
func TestInvalidCommand(t *testing.T) {
runMainTest(t,
mainTest{
args: []string{
"treerack", "foo",
},
exit: -1,
stderr: []string{
"invalid command",
commandsHelp,
},
},
)
}
func TestHelp(t *testing.T) {
runMainTest(t,
mainTest{
title: "without dash",
args: []string{
"treerack", "help",
},
stdout: []string{
summary, commandsHelp, docRef,
},
},
mainTest{
title: "with dash",
args: []string{
"treerack", "-help",
},
stdout: []string{
summary, commandsHelp, docRef,
},
},
)
}

View File

@ -1,14 +0,0 @@
package main
import (
"fmt"
"os"
)
func stderr(a ...interface{}) {
fmt.Fprintln(os.Stderr, a...)
}
func stdout(a ...interface{}) {
fmt.Fprintln(os.Stderr, a...)
}

View File

@ -9,6 +9,10 @@ read, with error reporting
what was the bug with the large json from eskip?
[next]
error reports
- take the last
simplify generator output
make generator output non-random (track parsers in a list in definition order)
missing tests, coverage:
- validation
- error cases
@ -36,6 +40,8 @@ test error report on invalid flag
report unused definitions
simplify generated output
parse hashed, storing only the results
allchars: can have char sequence
input name: may be just dropped because completely controlled by the client
[optimization]
try preallocate larger store chunks

File diff suppressed because it is too large Load Diff