[#321] platformtest: generate test case list + coverage tooling

This commit is contained in:
Christian Schwarz
2020-05-24 17:43:42 +02:00
parent 474652ea51
commit 301f163a44
15 changed files with 284 additions and 67 deletions
+24 -21
View File
@@ -24,33 +24,36 @@ var bold = color.New(color.Bold)
var boldRed = color.New(color.Bold, color.FgHiRed)
var boldGreen = color.New(color.Bold, color.FgHiGreen)
var args struct {
createArgs platformtest.ZpoolCreateArgs
stopAndKeepPoolOnFail bool
run string
runRE *regexp.Regexp
}
const DefaultPoolImageSize = 200 * (1 << 20)
func main() {
if err := doMain(); err != nil {
var args HarnessArgs
flag.StringVar(&args.CreateArgs.PoolName, "poolname", "", "")
flag.StringVar(&args.CreateArgs.ImagePath, "imagepath", "", "")
flag.Int64Var(&args.CreateArgs.ImageSize, "imagesize", DefaultPoolImageSize, "")
flag.StringVar(&args.CreateArgs.Mountpoint, "mountpoint", "", "")
flag.BoolVar(&args.StopAndKeepPoolOnFail, "failure.stop-and-keep-pool", false, "if a test case fails, stop test execution and keep pool as it was when the test failed")
flag.StringVar(&args.Run, "run", "", "")
flag.Parse()
if err := HarnessRun(args); err != nil {
os.Exit(1)
}
}
var exitWithErr = fmt.Errorf("exit with error")
func doMain() error {
type HarnessArgs struct {
CreateArgs platformtest.ZpoolCreateArgs
StopAndKeepPoolOnFail bool
Run string
}
flag.StringVar(&args.createArgs.PoolName, "poolname", "", "")
flag.StringVar(&args.createArgs.ImagePath, "imagepath", "", "")
flag.Int64Var(&args.createArgs.ImageSize, "imagesize", 200*(1<<20), "")
flag.StringVar(&args.createArgs.Mountpoint, "mountpoint", "", "")
flag.BoolVar(&args.stopAndKeepPoolOnFail, "failure.stop-and-keep-pool", false, "if a test case fails, stop test execution and keep pool as it was when the test failed")
flag.StringVar(&args.run, "run", "", "")
flag.Parse()
func HarnessRun(args HarnessArgs) error {
args.runRE = regexp.MustCompile(args.run)
runRE := regexp.MustCompile(args.Run)
outlets := logger.NewOutlets()
outlet, level, err := logging.ParseOutlet(config.LoggingOutletEnum{Ret: &config.StdoutLoggingOutlet{
@@ -65,7 +68,7 @@ func doMain() error {
outlets.Add(outlet, level)
logger := logger.NewLogger(outlets, 1*time.Second)
if err := args.createArgs.Validate(); err != nil {
if err := args.CreateArgs.Validate(); err != nil {
logger.Error(err.Error())
panic(err)
}
@@ -81,7 +84,7 @@ func doMain() error {
invocations := make([]*invocation, 0, len(tests.Cases))
for _, c := range tests.Cases {
if args.runRE.MatchString(c.String()) {
if runRE.MatchString(c.String()) {
invocations = append(invocations, &invocation{runFunc: c})
}
}
@@ -90,7 +93,7 @@ func doMain() error {
bold.Printf("BEGIN TEST CASE %s\n", inv.runFunc.String())
pool, err := platformtest.CreateOrReplaceZpool(ctx, ex, args.createArgs)
pool, err := platformtest.CreateOrReplaceZpool(ctx, ex, args.CreateArgs)
if err != nil {
panic(errors.Wrap(err, "create test pool"))
}
@@ -106,7 +109,7 @@ func doMain() error {
fmt.Printf("%+v\n", res.failedStack) // print with stack trace
}
if res.failed && args.stopAndKeepPoolOnFail {
if res.failed && args.StopAndKeepPoolOnFail {
boldRed.Printf("STOPPING TEST RUN AT FAILING TEST PER USER REQUEST\n")
return exitWithErr
}
+12 -4
View File
@@ -1,6 +1,7 @@
package main
import (
"fmt"
"os"
"strings"
"testing"
@@ -21,22 +22,29 @@ import (
// https://github.com/wadey/gocovmerge
func TestMain(t *testing.T) {
fmt.Println("incoming args: ", os.Args)
var (
args []string
run bool
args []string
run bool
startCaptureArgs bool
)
for _, arg := range os.Args {
for i, arg := range os.Args {
switch {
case arg == "__DEVEL--i-heard-you-like-tests":
run = true
startCaptureArgs = true
case strings.HasPrefix(arg, "-test"):
case strings.HasPrefix(arg, "__DEVEL"):
default:
case i == 0:
args = append(args, arg)
case startCaptureArgs:
args = append(args, arg)
}
}
os.Args = args
fmt.Println("using args: ", os.Args)
if run {
main()
+12
View File
@@ -58,6 +58,14 @@ func CreateOrReplaceZpool(ctx context.Context, e Execer, args ZpoolCreateArgs) (
}
}
// clear the mountpoint dir
if err := os.RemoveAll(args.Mountpoint); err != nil {
return nil, errors.Wrapf(err, "remove mountpoint dir %q", args.Mountpoint)
}
if err := os.Mkdir(args.Mountpoint, 0700); err != nil {
return nil, errors.Wrapf(err, "create mountpoint dir %q", args.Mountpoint)
}
// idempotently (re)create the pool image
image, err := os.OpenFile(args.ImagePath, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
@@ -93,5 +101,9 @@ func (p *Zpool) Destroy(ctx context.Context, e Execer) error {
return errors.Wrapf(err, "remove pool image")
}
if err := os.RemoveAll(p.args.Mountpoint); err != nil {
return errors.Wrapf(err, "remove mountpoint dir %q", p.args.Mountpoint)
}
return nil
}
+140
View File
@@ -0,0 +1,140 @@
package main
import (
"bytes"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io/ioutil"
"os"
"sort"
"strings"
"text/template"
"golang.org/x/tools/go/packages"
)
func check(err error) {
if err != nil {
panic(err)
}
}
type platformtestFuncDeclFinder struct {
pkg *packages.Package
testFuncs []*ast.FuncDecl
}
func isPlatformtestFunc(n *ast.FuncDecl) bool {
if !n.Name.IsExported() {
return false
}
if n.Recv != nil {
return false
}
if n.Type.Results.NumFields() != 0 {
return false
}
if n.Type.Params.NumFields() != 1 {
return false
}
se, ok := n.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
sel, ok := se.X.(*ast.SelectorExpr)
if !ok {
return false
}
x, ok := sel.X.(*ast.Ident)
if !ok {
return false
}
if x.Name != "platformtest" || sel.Sel.Name != "Context" {
return false
}
return true
}
func (e *platformtestFuncDeclFinder) Visit(n2 ast.Node) ast.Visitor {
switch n := n2.(type) {
case *ast.File:
return e
case *ast.FuncDecl:
if isPlatformtestFunc(n) {
e.testFuncs = append(e.testFuncs, n)
}
return nil
default:
return nil
}
}
func main() {
// TODO safeguards that prevent us from deleting non-generated generated_cases.go
os.Remove("generated_cases.go")
// (no error handling to easily cover the case where the file doesn't exist)
pkgs, err := packages.Load(
&packages.Config{
Mode: packages.LoadFiles,
Tests: false,
},
os.Args[1],
)
check(err)
if len(pkgs) != 1 {
panic(pkgs)
}
p := pkgs[0]
var tests []*ast.FuncDecl
for _, f := range p.GoFiles {
s := token.NewFileSet()
a, err := parser.ParseFile(s, f, nil, parser.AllErrors)
check(err)
finder := &platformtestFuncDeclFinder{
pkg: p,
}
ast.Walk(finder, a)
tests = append(tests, finder.testFuncs...)
}
sort.Slice(tests, func(i, j int) bool {
return strings.Compare(tests[i].Name.Name, tests[j].Name.Name) < 0
})
{
casesTemplate := `
// Code generated by zrepl tooling; DO NOT EDIT.
package tests
var Cases = []Case {
{{- range . -}}
{{ .Name }},
{{ end -}}
}
`
t, err := template.New("CaseFunc").Parse(casesTemplate)
check(err)
var buf bytes.Buffer
err = t.Execute(&buf, tests)
check(err)
formatted, err := format.Source(buf.Bytes())
check(err)
err = ioutil.WriteFile("generated_cases.go", formatted, 0664)
check(err)
}
}
+22
View File
@@ -0,0 +1,22 @@
// Code generated by zrepl tooling; DO NOT EDIT.
package tests
var Cases = []Case{BatchDestroy,
CreateReplicationCursor,
GetNonexistent,
IdempotentBookmark,
IdempotentDestroy,
IdempotentHold,
ListFilesystemVersionsFilesystemNotExist,
ListFilesystemVersionsTypeFilteringAndPrefix,
ListFilesystemVersionsUserrefs,
ListFilesystemVersionsZeroExistIsNotAnError,
ListFilesystemsNoFilter,
ResumableRecvAndTokenHandling,
ResumeTokenParsing,
SendArgsValidationEncryptedSendOfUnencryptedDatasetForbidden,
SendArgsValidationResumeTokenDifferentFilesystemForbidden,
SendArgsValidationResumeTokenEncryptionMismatchForbidden,
UndestroyableSnapshotParsing,
}
+1 -19
View File
@@ -13,22 +13,4 @@ func (c Case) String() string {
return runtime.FuncForPC(reflect.ValueOf(c).Pointer()).Name()
}
var Cases = []Case{
BatchDestroy,
UndestroyableSnapshotParsing,
GetNonexistent,
CreateReplicationCursor,
IdempotentHold,
IdempotentBookmark,
IdempotentDestroy,
ResumeTokenParsing,
ResumableRecvAndTokenHandling,
SendArgsValidationEncryptedSendOfUnencryptedDatasetForbidden,
SendArgsValidationResumeTokenEncryptionMismatchForbidden,
SendArgsValidationResumeTokenDifferentFilesystemForbidden,
ListFilesystemVersionsTypeFilteringAndPrefix,
ListFilesystemVersionsFilesystemNotExist,
ListFilesystemVersionsFilesystemNotExist,
ListFilesystemVersionsUserrefs,
ListFilesystemsNoFilter,
}
//go:generate ../../artifacts/generate-platform-test-list github.com/zrepl/zrepl/platformtest/tests