2019-08-20 17:04:13 +02:00
package main
import (
"context"
"flag"
"fmt"
2019-12-21 19:01:47 +01:00
"os"
2019-09-29 18:44:59 +02:00
"path/filepath"
2019-12-23 11:48:48 +01:00
"regexp"
2019-08-20 17:04:13 +02:00
"time"
"github.com/fatih/color"
2019-09-29 18:44:59 +02:00
"github.com/pkg/errors"
2020-08-31 16:04:00 +02:00
2020-04-11 15:49:41 +02:00
"github.com/zrepl/zrepl/daemon/logging/trace"
2019-12-30 19:42:17 +01:00
2019-08-20 17:04:13 +02:00
"github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/daemon/logging"
"github.com/zrepl/zrepl/logger"
"github.com/zrepl/zrepl/platformtest"
"github.com/zrepl/zrepl/platformtest/tests"
)
2019-12-21 19:01:47 +01:00
var bold = color . New ( color . Bold )
var boldRed = color . New ( color . Bold , color . FgHiRed )
var boldGreen = color . New ( color . Bold , color . FgHiGreen )
2020-05-24 17:43:42 +02:00
const DefaultPoolImageSize = 200 * ( 1 << 20 )
2019-08-20 17:04:13 +02:00
2019-12-21 19:01:47 +01:00
func main ( ) {
2020-05-24 17:43:42 +02:00
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" , "" , "" )
2021-12-18 15:55:50 +01:00
flag . DurationVar ( & platformtest . ZpoolExportTimeout , "zfs.zpool-export-timeout" , platformtest . ZpoolExportTimeout , "" )
2020-05-24 17:43:42 +02:00
flag . Parse ( )
if err := HarnessRun ( args ) ; err != nil {
2019-12-21 19:01:47 +01:00
os . Exit ( 1 )
2019-08-20 17:04:13 +02:00
}
2019-12-21 19:01:47 +01:00
}
var exitWithErr = fmt . Errorf ( "exit with error" )
2020-05-24 17:43:42 +02:00
type HarnessArgs struct {
CreateArgs platformtest . ZpoolCreateArgs
StopAndKeepPoolOnFail bool
Run string
}
2019-12-21 19:01:47 +01:00
2020-05-24 17:43:42 +02:00
func HarnessRun ( args HarnessArgs ) error {
2019-08-20 17:04:13 +02:00
2020-05-24 17:43:42 +02:00
runRE := regexp . MustCompile ( args . Run )
2019-12-23 11:48:48 +01:00
2019-08-20 17:04:13 +02:00
outlets := logger . NewOutlets ( )
outlet , level , err := logging . ParseOutlet ( config . LoggingOutletEnum { Ret : & config . StdoutLoggingOutlet {
LoggingOutletCommon : config . LoggingOutletCommon {
Level : "debug" ,
Format : "human" ,
} ,
} } )
if err != nil {
panic ( err )
}
outlets . Add ( outlet , level )
logger := logger . NewLogger ( outlets , 1 * time . Second )
2020-05-24 17:43:42 +02:00
if err := args . CreateArgs . Validate ( ) ; err != nil {
2019-09-29 18:44:59 +02:00
logger . Error ( err . Error ( ) )
panic ( err )
2019-08-20 17:04:13 +02:00
}
2020-04-11 15:49:41 +02:00
ctx := context . Background ( )
defer trace . WithTaskFromStackUpdateCtx ( & ctx ) ( )
ctx = logging . WithLoggers ( ctx , logging . SubsystemLoggersWithUniversalLogger ( logger ) )
2019-09-29 18:44:59 +02:00
ex := platformtest . NewEx ( logger )
2019-08-20 17:04:13 +02:00
2019-12-23 11:48:48 +01:00
type invocation struct {
runFunc tests . Case
result * testCaseResult
}
invocations := make ( [ ] * invocation , 0 , len ( tests . Cases ) )
2019-08-20 17:04:13 +02:00
for _ , c := range tests . Cases {
2020-05-24 17:43:42 +02:00
if runRE . MatchString ( c . String ( ) ) {
2019-12-23 11:48:48 +01:00
invocations = append ( invocations , & invocation { runFunc : c } )
}
}
2019-12-21 19:01:47 +01:00
2019-12-23 11:48:48 +01:00
for _ , inv := range invocations {
bold . Printf ( "BEGIN TEST CASE %s\n" , inv . runFunc . String ( ) )
2019-12-21 19:01:47 +01:00
2020-05-24 17:43:42 +02:00
pool , err := platformtest . CreateOrReplaceZpool ( ctx , ex , args . CreateArgs )
2019-09-29 18:44:59 +02:00
if err != nil {
2019-12-21 19:01:47 +01:00
panic ( errors . Wrap ( err , "create test pool" ) )
2019-09-29 18:44:59 +02:00
}
2019-12-21 19:01:47 +01:00
ctx := & platformtest . Context {
Context : ctx ,
RootDataset : filepath . Join ( pool . Name ( ) , "rootds" ) ,
}
2019-12-23 11:48:48 +01:00
res := runTestCase ( ctx , ex , inv . runFunc )
inv . result = res
2019-12-21 19:01:47 +01:00
if res . failed {
fmt . Printf ( "%+v\n" , res . failedStack ) // print with stack trace
}
2020-05-24 17:43:42 +02:00
if res . failed && args . StopAndKeepPoolOnFail {
2019-12-21 19:01:47 +01:00
boldRed . Printf ( "STOPPING TEST RUN AT FAILING TEST PER USER REQUEST\n" )
return exitWithErr
}
if err := pool . Destroy ( ctx , ex ) ; err != nil {
panic ( fmt . Sprintf ( "error destroying test pool: %s" , err ) )
}
if res . failed {
boldRed . Printf ( "TEST FAILED\n" )
} else if res . skipped {
bold . Printf ( "TEST SKIPPED\n" )
} else if res . succeeded {
boldGreen . Printf ( "TEST PASSED\n" )
2019-12-23 11:48:48 +01:00
} else {
panic ( "unreachable" )
2019-12-21 19:01:47 +01:00
}
2019-10-14 17:32:58 +02:00
fmt . Println ( )
2019-08-20 17:04:13 +02:00
}
2019-12-23 11:48:48 +01:00
var summary struct {
succ , fail , skip [ ] * invocation
}
for _ , inv := range invocations {
var bucket * [ ] * invocation
if inv . result . failed {
bucket = & summary . fail
} else if inv . result . skipped {
bucket = & summary . skip
} else if inv . result . succeeded {
bucket = & summary . succ
} else {
panic ( "unreachable" )
}
* bucket = append ( * bucket , inv )
}
printBucket := func ( bucketName string , c * color . Color , bucket [ ] * invocation ) {
c . Printf ( "%s:" , bucketName )
if len ( bucket ) == 0 {
fmt . Printf ( " []\n" )
return
}
fmt . Printf ( "\n" )
for _ , inv := range bucket {
fmt . Printf ( " %s\n" , inv . runFunc . String ( ) )
}
}
printBucket ( "PASSING TESTS" , boldGreen , summary . succ )
printBucket ( "SKIPPED TESTS" , bold , summary . skip )
printBucket ( "FAILED TESTS" , boldRed , summary . fail )
if len ( summary . fail ) > 0 {
return errors . New ( "at least one test failed" )
}
2019-12-21 19:01:47 +01:00
return nil
}
type testCaseResult struct {
// oneof
failed , skipped , succeeded bool
failedStack error // has stack inside, valid if failed=true
}
2019-12-23 11:48:48 +01:00
func runTestCase ( ctx * platformtest . Context , ex platformtest . Execer , c tests . Case ) * testCaseResult {
2019-12-21 19:01:47 +01:00
// run case
2020-02-23 23:24:12 +01:00
var panicked = false
2019-12-21 19:01:47 +01:00
var panicValue interface { } = nil
var panicStack error
func ( ) {
defer func ( ) {
if item := recover ( ) ; item != nil {
panicValue = item
2020-02-23 23:24:12 +01:00
panicked = true
2019-12-21 19:01:47 +01:00
panicStack = errors . Errorf ( "panic while running test: %v" , panicValue )
}
} ( )
c ( ctx )
} ( )
2020-02-23 23:24:12 +01:00
if panicked {
2019-12-21 19:01:47 +01:00
switch panicValue {
case platformtest . SkipNowSentinel :
2019-12-23 11:48:48 +01:00
return & testCaseResult { skipped : true }
2019-12-21 19:01:47 +01:00
case platformtest . FailNowSentinel :
2019-12-23 11:48:48 +01:00
return & testCaseResult { failed : true , failedStack : panicStack }
2019-12-21 19:01:47 +01:00
default :
2019-12-23 11:48:48 +01:00
return & testCaseResult { failed : true , failedStack : panicStack }
2019-12-21 19:01:47 +01:00
}
} else {
2019-12-23 11:48:48 +01:00
return & testCaseResult { succeeded : true }
2019-12-21 19:01:47 +01:00
}
2019-08-20 17:04:13 +02:00
}