generate - testing
This commit is contained in:
parent
9e25538b94
commit
6178b0c7a1
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
*.test
|
||||
*.out
|
||||
.coverprofile
|
||||
.coverprofile-cmd
|
||||
codecov
|
||||
cmd/treerack/treerack
|
||||
|
14
Makefile
14
Makefile
@ -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
|
||||
|
||||
|
@ -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`
|
||||
|
1
cmd/treerack/foo_test.treerack
Normal file
1
cmd/treerack/foo_test.treerack
Normal file
@ -0,0 +1 @@
|
||||
foo = "bar"
|
@ -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
|
||||
}
|
||||
|
132
cmd/treerack/generate_test.go
Normal file
132
cmd/treerack/generate_test.go
Normal 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
29
cmd/treerack/io.go
Normal 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...)
|
||||
}
|
@ -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
205
cmd/treerack/main_test.go
Normal 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,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
@ -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...)
|
||||
}
|
@ -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
|
||||
|
1150
self/self.go
1150
self/self.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user