generate - testing
This commit is contained in:
parent
9e25538b94
commit
6178b0c7a1
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
*.test
|
*.test
|
||||||
*.out
|
*.out
|
||||||
.coverprofile
|
.coverprofile
|
||||||
|
.coverprofile-cmd
|
||||||
codecov
|
codecov
|
||||||
cmd/treerack/treerack
|
cmd/treerack/treerack
|
||||||
|
14
Makefile
14
Makefile
@ -44,9 +44,11 @@ regenerate: $(SOURCES) $(PARSERS) head install
|
|||||||
|
|
||||||
check: imports build $(PARSERS)
|
check: imports build $(PARSERS)
|
||||||
go test -test.short -run ^Test
|
go test -test.short -run ^Test
|
||||||
|
go test ./cmd/treerack -test.short -run ^Test
|
||||||
|
|
||||||
checkall: imports build $(PARSERS)
|
checkall: imports build $(PARSERS)
|
||||||
go test
|
go test
|
||||||
|
go test ./cmd/treerack
|
||||||
|
|
||||||
.coverprofile: $(SOURCES) imports
|
.coverprofile: $(SOURCES) imports
|
||||||
go test -coverprofile .coverprofile
|
go test -coverprofile .coverprofile
|
||||||
@ -57,6 +59,16 @@ cover: .coverprofile
|
|||||||
showcover: .coverprofile
|
showcover: .coverprofile
|
||||||
go tool cover -html .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
|
publishcoverage: .coverprofile
|
||||||
curl -s https://codecov.io/bash -o codecov
|
curl -s https://codecov.io/bash -o codecov
|
||||||
bash codecov -Zf .coverprofile
|
bash codecov -Zf .coverprofile
|
||||||
@ -76,7 +88,7 @@ checkfmt: $(SOURCES)
|
|||||||
@if [ "$$(gofmt -s -d $(SOURCES))" != "" ]; then false; else true; fi
|
@if [ "$$(gofmt -s -d $(SOURCES))" != "" ]; then false; else true; fi
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet
|
go vet ./...
|
||||||
|
|
||||||
precommit: regenerate fmt vet build checkall
|
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 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 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:
|
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
|
export bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var isTest bool
|
|
||||||
|
|
||||||
func flagSet(o *generateOptions, output io.Writer) *flag.FlagSet {
|
func flagSet(o *generateOptions, output io.Writer) *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
fs.Usage = func() {}
|
fs.Usage = func() {}
|
||||||
@ -35,10 +33,12 @@ func helpGenerate() {
|
|||||||
stdout(generateUsage)
|
stdout(generateUsage)
|
||||||
stdout()
|
stdout()
|
||||||
stdout("Options:")
|
stdout("Options:")
|
||||||
fs := flagSet(&generateOptions{}, os.Stdout)
|
fs := flagSet(&generateOptions{}, wout)
|
||||||
fs.PrintDefaults()
|
fs.PrintDefaults()
|
||||||
stdout()
|
stdout()
|
||||||
stdout(generateExample)
|
stdout(generateExample)
|
||||||
|
stdout()
|
||||||
|
stdout(docRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagError(fs *flag.FlagSet) {
|
func flagError(fs *flag.FlagSet) {
|
||||||
@ -68,7 +68,7 @@ func generate(args []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var options generateOptions
|
var options generateOptions
|
||||||
fs := flagSet(&options, os.Stderr)
|
fs := flagSet(&options, werr)
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := fs.Parse(args); err != nil {
|
||||||
flagError(fs)
|
flagError(fs)
|
||||||
return -1
|
return -1
|
||||||
@ -81,8 +81,7 @@ func generate(args []string) int {
|
|||||||
|
|
||||||
var hasInput bool
|
var hasInput bool
|
||||||
if options.syntaxFile == "" && options.syntax == "" {
|
if options.syntaxFile == "" && options.syntax == "" {
|
||||||
fdint := int(os.Stdin.Fd())
|
hasInput = isTest && rin != nil || !isTest && !terminal.IsTerminal(0)
|
||||||
hasInput = !isTest && !terminal.IsTerminal(fdint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasInput && options.syntaxFile == "" && options.syntax == "" {
|
if !hasInput && options.syntaxFile == "" && options.syntax == "" {
|
||||||
@ -92,7 +91,7 @@ func generate(args []string) int {
|
|||||||
|
|
||||||
var input io.Reader
|
var input io.Reader
|
||||||
if hasInput {
|
if hasInput {
|
||||||
input = os.Stdin
|
input = rin
|
||||||
} else if options.syntaxFile != "" {
|
} else if options.syntaxFile != "" {
|
||||||
f, err := os.Open(options.syntaxFile)
|
f, err := os.Open(options.syntaxFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -116,7 +115,7 @@ func generate(args []string) int {
|
|||||||
goptions.PackageName = options.packageName
|
goptions.PackageName = options.packageName
|
||||||
goptions.Export = options.export
|
goptions.Export = options.export
|
||||||
|
|
||||||
if err := s.Generate(goptions, os.Stdout); err != nil {
|
if err := s.Generate(goptions, wout); err != nil {
|
||||||
stderr(err)
|
stderr(err)
|
||||||
return -1
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import "os"
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mainHelp() {
|
func mainHelp() {
|
||||||
stdout(summary)
|
stdout(summary)
|
||||||
@ -17,21 +15,22 @@ func main() {
|
|||||||
stderr("missing command")
|
stderr("missing command")
|
||||||
stderr()
|
stderr()
|
||||||
stderr(commandsHelp)
|
stderr(commandsHelp)
|
||||||
stdout()
|
stderr()
|
||||||
stdout(docRef)
|
stderr(docRef)
|
||||||
os.Exit(-1)
|
exit(-1)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "generate":
|
case "generate":
|
||||||
exit := generate(os.Args[2:])
|
code := generate(os.Args[2:])
|
||||||
os.Exit(exit)
|
exit(code)
|
||||||
case "help", "-help":
|
case "help", "-help":
|
||||||
mainHelp()
|
mainHelp()
|
||||||
default:
|
default:
|
||||||
stderr("invalid command")
|
stderr("invalid command")
|
||||||
stderr()
|
stderr()
|
||||||
stderr(commandsHelp)
|
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?
|
what was the bug with the large json from eskip?
|
||||||
|
|
||||||
[next]
|
[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:
|
missing tests, coverage:
|
||||||
- validation
|
- validation
|
||||||
- error cases
|
- error cases
|
||||||
@ -36,6 +40,8 @@ test error report on invalid flag
|
|||||||
report unused definitions
|
report unused definitions
|
||||||
simplify generated output
|
simplify generated output
|
||||||
parse hashed, storing only the results
|
parse hashed, storing only the results
|
||||||
|
allchars: can have char sequence
|
||||||
|
input name: may be just dropped because completely controlled by the client
|
||||||
|
|
||||||
[optimization]
|
[optimization]
|
||||||
try preallocate larger store chunks
|
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