diff --git a/action/io.go b/action/io.go index df88a523..3e809804 100644 --- a/action/io.go +++ b/action/io.go @@ -62,8 +62,6 @@ func Expect(action *recipe.Action, output *OutputContainer) error { } } - output.Purge() - return fmt.Errorf("Timeout (%g sec) reached", timeout) } diff --git a/cli/cli.go b/cli/cli.go index b0bddb6f..8c773f18 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -18,6 +18,7 @@ import ( "github.com/essentialkaos/ek/v12/fmtutil/panel" "github.com/essentialkaos/ek/v12/fmtutil/table" "github.com/essentialkaos/ek/v12/fsutil" + "github.com/essentialkaos/ek/v12/mathutil" "github.com/essentialkaos/ek/v12/options" "github.com/essentialkaos/ek/v12/req" "github.com/essentialkaos/ek/v12/strutil" @@ -40,7 +41,7 @@ import ( // Application info const ( APP = "bibop" - VER = "7.4.1" + VER = "7.5.0" DESC = "Utility for testing command-line tools" ) @@ -80,7 +81,7 @@ var optMap = options.Map{ OPT_LIST_PACKAGES_FLAT: {Type: options.BOOL}, OPT_VARIABLES: {Type: options.BOOL}, OPT_BARCODE: {Type: options.BOOL}, - OPT_EXTRA: {Type: options.BOOL}, + OPT_EXTRA: {Type: options.MIXED}, OPT_TIME: {Type: options.BOOL}, OPT_FORMAT: {}, OPT_DIR: {}, @@ -223,26 +224,20 @@ func validateOptions() { errsDir := options.GetS(OPT_ERROR_DIR) if errsDir != "" { - switch { - case !fsutil.IsExist(errsDir): - printErrorAndExit("Directory %s doesn't exist", errsDir) - - case !fsutil.IsDir(errsDir): - printErrorAndExit("Object %s is not a directory", errsDir) + err := fsutil.ValidatePerms("DW", errsDir) - case !fsutil.IsWritable(errsDir): - printErrorAndExit("Directory %s is not writable", errsDir) + if err != nil { + printErrorAndExit(err.Error()) } } wrkDir := options.GetS(OPT_DIR) if wrkDir != "" { - switch { - case !fsutil.IsExist(wrkDir): - printErrorAndExit("Directory %s doesn't exist", wrkDir) - case !fsutil.IsDir(wrkDir): - printErrorAndExit("Object %s is not a directory", wrkDir) + err := fsutil.ValidatePerms("DR", wrkDir) + + if err != nil { + printErrorAndExit(err.Error()) } } } @@ -281,12 +276,15 @@ func process(file string) { } cfg := &executor.Config{ - Debug: options.GetB(OPT_EXTRA), Quiet: options.GetB(OPT_QUIET), DisableCleanup: options.GetB(OPT_NO_CLEANUP), ErrsDir: errDir, } + if options.GetB(OPT_EXTRA) { + cfg.DebugLines = mathutil.Max(10, options.GetI(OPT_EXTRA)) + } + e := executor.NewExecutor(cfg) tags := strutil.Fields(options.GetS(OPT_TAG)) @@ -461,7 +459,7 @@ func genUsage() *usage.Info { info.AppNameColorTag = "{*}" + colorTagApp info.AddOption(OPT_DRY_RUN, "Parse and validate recipe") - info.AddOption(OPT_EXTRA, "Print the last lines from command output if action was failed") + info.AddOption(OPT_EXTRA, "Print the last lines from command output if action was failed", "?lines") info.AddOption(OPT_LIST_PACKAGES, "List required packages") info.AddOption(OPT_LIST_PACKAGES_FLAT, "List required packages in one line {s-}(useful for scripts){!}") info.AddOption(OPT_VARIABLES, "List recipe variables") @@ -494,6 +492,16 @@ func genUsage() *usage.Info { "Run tests from app.recipe and execute commands with tags init and service", ) + info.AddExample( + "app.recipe --extra", + "Run tests from app.recipe and print the last 10 lines from command output if action was failed", + ) + + info.AddExample( + "app.recipe --extra=50", + "Run tests from app.recipe and print the last 50 lines from command output if action was failed", + ) + info.AddExample( "app.recipe --format json 1> ~/results/app.json", "Run tests from app.recipe and save result in JSON format", diff --git a/cli/executor/executor.go b/cli/executor/executor.go index 26fb500e..ffa34ff2 100644 --- a/cli/executor/executor.go +++ b/cli/executor/executor.go @@ -55,8 +55,8 @@ type Executor struct { // ExecutorConfig contains executor configuration type Config struct { ErrsDir string + DebugLines int Quiet bool - Debug bool DisableCleanup bool } @@ -290,11 +290,12 @@ func runCommand(e *Executor, rr render.Renderer, c *recipe.Command) bool { } if err != nil { - if !e.config.Quiet && e.config.Debug && cmdEnv.output != nil && !cmdEnv.output.IsEmpty() { + if !e.config.Quiet && e.config.DebugLines > 0 && cmdEnv != nil && !cmdEnv.output.IsEmpty() { fmtc.NewLine() panel.Panel( - "☴ OUTPUT", "{y}", "The last 10 lines from command output", - cmdEnv.output.Tail(10), panel.BOTTOM_LINE, + "☴ OUTPUT", "{y}", + fmt.Sprintf("The last %d lines from command output", e.config.DebugLines), + cmdEnv.output.Tail(e.config.DebugLines), panel.BOTTOM_LINE, ) } @@ -390,6 +391,17 @@ func runAction(a *recipe.Action, cmdEnv *CommandEnv) error { time.Sleep(25 * time.Millisecond) } + switch a.Name { + case recipe.ACTION_EXIT, recipe.ACTION_EXPECT, recipe.ACTION_PRINT, + recipe.ACTION_WAIT_OUTPUT, recipe.ACTION_OUTPUT_CONTAINS, + recipe.ACTION_OUTPUT_EMPTY, recipe.ACTION_OUTPUT_MATCH, + recipe.ACTION_OUTPUT_TRIM, recipe.ACTION_SIGNAL: + + if cmdEnv == nil { + return fmt.Errorf("Action %q doesn't support hollow commands (without executing binary)", a.Name) + } + } + switch a.Name { case recipe.ACTION_EXIT: return action.Exit(a, cmdEnv.cmd) @@ -418,7 +430,7 @@ func runAction(a *recipe.Action, cmdEnv *CommandEnv) error { handler, ok := handlers[a.Name] if !ok { - return fmt.Errorf("Can't find handler for action %s", a.Name) + return fmt.Errorf("Can't find handler for action %q", a.Name) } return handler(a) diff --git a/parser/parser.go b/parser/parser.go index 88b2eda4..6fd04967 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -41,7 +41,7 @@ var tagRegex = regexp.MustCompile(`^\+?command:([a-zA-Z_0-9_-]+)`) // Parse parse bibop suite func Parse(file string) (*recipe.Recipe, error) { - err := checkRecipeFile(file) + err := fsutil.ValidatePerms("FRS", file) if err != nil { return nil, err @@ -52,19 +52,6 @@ func Parse(file string) (*recipe.Recipe, error) { // ////////////////////////////////////////////////////////////////////////////////// // -// checkRecipeFile check recipe file -func checkRecipeFile(file string) error { - if !fsutil.CheckPerms("FR", file) { - return fmt.Errorf("File %s doesn't exist or not readable", file) - } - - if !fsutil.IsNonEmpty(file) { - return fmt.Errorf("File %s is empty", file) - } - - return nil -} - // parseRecipeFile parce recipe file func parseRecipeFile(file string) (*recipe.Recipe, error) { fd, err := os.Open(file) diff --git a/parser/parser_test.go b/parser/parser_test.go index 7be1567b..f7ac12d5 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -31,7 +31,7 @@ var _ = Suite(&ParseSuite{}) func (s *ParseSuite) TestGlobalErrors(c *C) { recipe, err := Parse("../testdata/test0.recipe") - c.Assert(err, DeepEquals, errors.New("File ../testdata/test0.recipe doesn't exist or not readable")) + c.Assert(err, DeepEquals, errors.New("File ../testdata/test0.recipe doesn't exist or not accessible")) c.Assert(recipe, IsNil) recipe, err = Parse("../testdata/test2.recipe") diff --git a/scripts/bibop-massive b/scripts/bibop-massive index 49c2e639..b1d0b73c 100755 --- a/scripts/bibop-massive +++ b/scripts/bibop-massive @@ -3,7 +3,7 @@ ################################################################################ APP="bibop-massive" -VER="1.10.1" +VER="1.11.0" DESC="Utility for mass package testing" ################################################################################ @@ -78,19 +78,31 @@ CACHE_DIR="/var/tmp/bibop-massive-cache" ################################################################################ +# Path to log file log="" + +# Name of OS distribution dist="" +# List of recipes recipes="" + +# Current recipe name cur_recipe="" +# Counters of passes and fails pass_count=0 fail_count=0 -max_recipe_size=0 +# Max length of recipe name +max_recipe_len=0 +# Path to package manager log file pm_log="" +# Canceled flag +is_canceled="" + ################################################################################ # Main function @@ -292,8 +304,8 @@ filterRecipes() { recipe_name=$(basename "$recipe" | sed 's/.recipe//') - if [[ ${#recipe_name} -gt $max_recipe_size ]] ; then - max_recipe_size=${#recipe_name} + if [[ ${#recipe_name} -gt $max_recipe_len ]] ; then + max_recipe_len=${#recipe_name} fi recipes="$recipes $recipe" @@ -363,14 +375,22 @@ runTests() { separator + trap cancelTrap SIGINT SIGQUIT SIGTERM + for recipe in $recipes ; do runTest "$recipe" if [[ -n "$interrupt" && $fail_count -ne 0 ]] ; then break fi + + if [[ -n "$is_canceled" ]] ; then + break + fi done + trap "" SIGINT SIGQUIT SIGTERM + separator end_ts=$(now) @@ -405,7 +425,7 @@ runTest() { return fi - printf " %${max_recipe_size}s: " "$cur_recipe" + printf " %${max_recipe_len}s: " "$cur_recipe" if [[ -n "$require_install" ]] ; then log "Checking files availability…" @@ -577,7 +597,7 @@ validateRecipe() { cur_recipe=$(basename "$recipe" | sed 's/.recipe//') - printf " %${max_recipe_size}s: " "$cur_recipe" + printf " %${max_recipe_len}s: " "$cur_recipe" if bibop -q -D -ip "$recipe" &>/dev/null ; then show "✔ " $GREEN @@ -1027,6 +1047,14 @@ printLegend() { show "\n${CL_BOLD}Legend:${CL_NORM} ${CL_DARK}•${CL_NORM} - skipped ${CL_DARK}|${CL_NORM} ${CL_GREEN}•${CL_NORM} - passed ${CL_DARK}|${CL_NORM} ${CL_YELLOW}•${CL_NORM} - warning ${CL_DARK}|${CL_NORM} ${CL_RED}•${CL_NORM} - error" } +# Signal trap for INT/TERM/QUIT +# +# Code: No +# Echo: No +cancelTrap() { + is_canceled=true +} + ################################################################################ # Check if some app is installed