github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/commands/run.go (about) 1 package commands 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "runtime" 10 "strings" 11 "time" 12 13 "github.com/fatih/color" 14 "github.com/pkg/errors" 15 "github.com/spf13/cobra" 16 "github.com/spf13/pflag" 17 18 "github.com/golangci/golangci-lint/pkg/config" 19 "github.com/golangci/golangci-lint/pkg/exitcodes" 20 "github.com/golangci/golangci-lint/pkg/lint" 21 "github.com/golangci/golangci-lint/pkg/lint/lintersdb" 22 "github.com/golangci/golangci-lint/pkg/logutils" 23 "github.com/golangci/golangci-lint/pkg/packages" 24 "github.com/golangci/golangci-lint/pkg/printers" 25 "github.com/golangci/golangci-lint/pkg/result" 26 "github.com/golangci/golangci-lint/pkg/result/processors" 27 ) 28 29 const defaultFileMode = 0644 30 31 const ( 32 // envFailOnWarnings value: "1" 33 envFailOnWarnings = "FAIL_ON_WARNINGS" 34 // envMemLogEvery value: "1" 35 envMemLogEvery = "GL_MEM_LOG_EVERY" 36 ) 37 38 func getDefaultIssueExcludeHelp() string { 39 parts := []string{"Use or not use default excludes:"} 40 for _, ep := range config.DefaultExcludePatterns { 41 parts = append(parts, 42 fmt.Sprintf(" # %s %s: %s", ep.ID, ep.Linter, ep.Why), 43 fmt.Sprintf(" - %s", color.YellowString(ep.Pattern)), 44 "", 45 ) 46 } 47 return strings.Join(parts, "\n") 48 } 49 50 func getDefaultDirectoryExcludeHelp() string { 51 parts := []string{"Use or not use default excluded directories:"} 52 for _, dir := range packages.StdExcludeDirRegexps { 53 parts = append(parts, fmt.Sprintf(" - %s", color.YellowString(dir))) 54 } 55 parts = append(parts, "") 56 return strings.Join(parts, "\n") 57 } 58 59 func wh(text string) string { 60 return color.GreenString(text) 61 } 62 63 const defaultTimeout = time.Minute 64 65 //nolint:funlen,gomnd 66 func initFlagSet(fs *pflag.FlagSet, cfg *config.Config, m *lintersdb.Manager, isFinalInit bool) { 67 hideFlag := func(name string) { 68 if err := fs.MarkHidden(name); err != nil { 69 panic(err) 70 } 71 72 // we run initFlagSet multiple times, but we wouldn't like to see deprecation message multiple times 73 if isFinalInit { 74 const deprecateMessage = "flag will be removed soon, please, use .golangci.yml config" 75 if err := fs.MarkDeprecated(name, deprecateMessage); err != nil { 76 panic(err) 77 } 78 } 79 } 80 81 // Output config 82 oc := &cfg.Output 83 fs.StringVar(&oc.Format, "out-format", 84 config.OutFormatColoredLineNumber, 85 wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|")))) 86 fs.BoolVar(&oc.PrintIssuedLine, "print-issued-lines", true, wh("Print lines of code with issue")) 87 fs.BoolVar(&oc.PrintLinterName, "print-linter-name", true, wh("Print linter name in issue line")) 88 fs.BoolVar(&oc.UniqByLine, "uniq-by-line", true, wh("Make issues output unique by line")) 89 fs.BoolVar(&oc.SortResults, "sort-results", false, wh("Sort linter results")) 90 fs.BoolVar(&oc.PrintWelcomeMessage, "print-welcome", false, wh("Print welcome message")) 91 fs.StringVar(&oc.PathPrefix, "path-prefix", "", wh("Path prefix to add to output")) 92 hideFlag("print-welcome") // no longer used 93 94 fs.BoolVar(&cfg.InternalCmdTest, "internal-cmd-test", false, wh("Option is used only for testing golangci-lint command, don't use it")) 95 if err := fs.MarkHidden("internal-cmd-test"); err != nil { 96 panic(err) 97 } 98 99 // Run config 100 rc := &cfg.Run 101 fs.StringVar(&rc.ModulesDownloadMode, "modules-download-mode", "", 102 "Modules download mode. If not empty, passed as -mod=<mode> to go tools") 103 fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code", 104 exitcodes.IssuesFound, wh("Exit code when issues were found")) 105 fs.StringVar(&rc.Go, "go", "", wh("Targeted Go version")) 106 fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags")) 107 108 fs.DurationVar(&rc.Timeout, "deadline", defaultTimeout, wh("Deadline for total work")) 109 if err := fs.MarkHidden("deadline"); err != nil { 110 panic(err) 111 } 112 fs.DurationVar(&rc.Timeout, "timeout", defaultTimeout, wh("Timeout for total work")) 113 114 fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)")) 115 fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, 116 wh("Print avg and max memory usage of golangci-lint and total time")) 117 fs.StringVarP(&rc.Config, "config", "c", "", wh("Read config from file path `PATH`")) 118 fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config")) 119 fs.StringSliceVar(&rc.SkipDirs, "skip-dirs", nil, wh("Regexps of directories to skip")) 120 fs.BoolVar(&rc.UseDefaultSkipDirs, "skip-dirs-use-default", true, getDefaultDirectoryExcludeHelp()) 121 fs.StringSliceVar(&rc.SkipFiles, "skip-files", nil, wh("Regexps of files to skip")) 122 123 const allowParallelDesc = "Allow multiple parallel golangci-lint instances running. " + 124 "If false (default) - golangci-lint acquires file lock on start." 125 fs.BoolVar(&rc.AllowParallelRunners, "allow-parallel-runners", false, wh(allowParallelDesc)) 126 const allowSerialDesc = "Allow multiple golangci-lint instances running, but serialize them around a lock. " + 127 "If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start." 128 fs.BoolVar(&rc.AllowSerialRunners, "allow-serial-runners", false, wh(allowSerialDesc)) 129 130 // Linters settings config 131 lsc := &cfg.LintersSettings 132 133 // Hide all linters settings flags: they were initially visible, 134 // but when number of linters started to grow it became obvious that 135 // we can't fill 90% of flags by linters settings: common flags became hard to find. 136 // New linters settings should be done only through config file. 137 fs.BoolVar(&lsc.Errcheck.CheckTypeAssertions, "errcheck.check-type-assertions", 138 false, "Errcheck: check for ignored type assertion results") 139 hideFlag("errcheck.check-type-assertions") 140 fs.BoolVar(&lsc.Errcheck.CheckAssignToBlank, "errcheck.check-blank", false, 141 "Errcheck: check for errors assigned to blank identifier: _ = errFunc()") 142 hideFlag("errcheck.check-blank") 143 fs.StringVar(&lsc.Errcheck.Exclude, "errcheck.exclude", "", 144 "Path to a file containing a list of functions to exclude from checking") 145 hideFlag("errcheck.exclude") 146 fs.StringVar(&lsc.Errcheck.Ignore, "errcheck.ignore", "fmt:.*", 147 `Comma-separated list of pairs of the form pkg:regex. The regex is used to ignore names within pkg`) 148 hideFlag("errcheck.ignore") 149 150 fs.BoolVar(&lsc.Govet.CheckShadowing, "govet.check-shadowing", false, 151 "Govet: check for shadowed variables") 152 hideFlag("govet.check-shadowing") 153 154 fs.Float64Var(&lsc.Golint.MinConfidence, "golint.min-confidence", 0.8, 155 "Golint: minimum confidence of a problem to print it") 156 hideFlag("golint.min-confidence") 157 158 fs.BoolVar(&lsc.Gofmt.Simplify, "gofmt.simplify", true, "Gofmt: simplify code") 159 hideFlag("gofmt.simplify") 160 161 fs.IntVar(&lsc.Gocyclo.MinComplexity, "gocyclo.min-complexity", 162 30, "Minimal complexity of function to report it") 163 hideFlag("gocyclo.min-complexity") 164 165 fs.BoolVar(&lsc.Maligned.SuggestNewOrder, "maligned.suggest-new", false, 166 "Maligned: print suggested more optimal struct fields ordering") 167 hideFlag("maligned.suggest-new") 168 169 fs.IntVar(&lsc.Dupl.Threshold, "dupl.threshold", 170 150, "Dupl: Minimal threshold to detect copy-paste") 171 hideFlag("dupl.threshold") 172 173 fs.BoolVar(&lsc.Goconst.MatchWithConstants, "goconst.match-constant", 174 true, "Goconst: look for existing constants matching the values") 175 hideFlag("goconst.match-constant") 176 fs.IntVar(&lsc.Goconst.MinStringLen, "goconst.min-len", 177 3, "Goconst: minimum constant string length") 178 hideFlag("goconst.min-len") 179 fs.IntVar(&lsc.Goconst.MinOccurrencesCount, "goconst.min-occurrences", 180 3, "Goconst: minimum occurrences of constant string count to trigger issue") 181 hideFlag("goconst.min-occurrences") 182 fs.BoolVar(&lsc.Goconst.ParseNumbers, "goconst.numbers", 183 false, "Goconst: search also for duplicated numbers") 184 hideFlag("goconst.numbers") 185 fs.IntVar(&lsc.Goconst.NumberMin, "goconst.min", 186 3, "minimum value, only works with goconst.numbers") 187 hideFlag("goconst.min") 188 fs.IntVar(&lsc.Goconst.NumberMax, "goconst.max", 189 3, "maximum value, only works with goconst.numbers") 190 hideFlag("goconst.max") 191 fs.BoolVar(&lsc.Goconst.IgnoreCalls, "goconst.ignore-calls", 192 true, "Goconst: ignore when constant is not used as function argument") 193 hideFlag("goconst.ignore-calls") 194 195 // (@dixonwille) These flag is only used for testing purposes. 196 fs.StringSliceVar(&lsc.Depguard.Packages, "depguard.packages", nil, 197 "Depguard: packages to add to the list") 198 hideFlag("depguard.packages") 199 200 fs.BoolVar(&lsc.Depguard.IncludeGoRoot, "depguard.include-go-root", false, 201 "Depguard: check list against standard lib") 202 hideFlag("depguard.include-go-root") 203 204 fs.IntVar(&lsc.Lll.TabWidth, "lll.tab-width", 1, 205 "Lll: tab width in spaces") 206 hideFlag("lll.tab-width") 207 208 // Linters config 209 lc := &cfg.Linters 210 fs.StringSliceVarP(&lc.Enable, "enable", "E", nil, wh("Enable specific linter")) 211 fs.StringSliceVarP(&lc.Disable, "disable", "D", nil, wh("Disable specific linter")) 212 fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters")) 213 214 fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters")) 215 fs.StringSliceVarP(&lc.Presets, "presets", "p", nil, 216 wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see "+ 217 "them. This option implies option --disable-all", strings.Join(m.AllPresets(), "|")))) 218 fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set (first run won't be fast)")) 219 220 // Issues config 221 ic := &cfg.Issues 222 fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp")) 223 fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultIssueExcludeHelp()) 224 fs.BoolVar(&ic.ExcludeCaseSensitive, "exclude-case-sensitive", false, wh("If set to true exclude "+ 225 "and exclude rules regular expressions are case sensitive")) 226 227 fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50, 228 wh("Maximum issues count per one linter. Set to 0 to disable")) 229 fs.IntVar(&ic.MaxSameIssues, "max-same-issues", 3, 230 wh("Maximum count of issues with the same text. Set to 0 to disable")) 231 232 fs.BoolVarP(&ic.Diff, "new", "n", false, 233 wh("Show only new issues: if there are unstaged changes or untracked files, only those changes "+ 234 "are analyzed, else only changes in HEAD~ are analyzed.\nIt's a super-useful option for integration "+ 235 "of golangci-lint into existing large codebase.\nIt's not practical to fix all existing issues at "+ 236 "the moment of integration: much better to not allow issues in new code.\nFor CI setups, prefer "+ 237 "--new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate "+ 238 "unstaged files before golangci-lint runs.")) 239 fs.StringVar(&ic.DiffFromRevision, "new-from-rev", "", 240 wh("Show only new issues created after git revision `REV`")) 241 fs.StringVar(&ic.DiffPatchFilePath, "new-from-patch", "", 242 wh("Show only new issues created in git patch with file path `PATH`")) 243 fs.BoolVar(&ic.WholeFiles, "whole-files", false, 244 wh("Show issues in any part of update files (requires new-from-rev or new-from-patch)")) 245 fs.BoolVar(&ic.NeedFix, "fix", false, "Fix found issues (if it's supported by the linter)") 246 } 247 248 func (e *Executor) initRunConfiguration(cmd *cobra.Command) { 249 fs := cmd.Flags() 250 fs.SortFlags = false // sort them as they are defined here 251 initFlagSet(fs, e.cfg, e.DBManager, true) 252 } 253 254 func (e *Executor) getConfigForCommandLine() (*config.Config, error) { 255 // We use another pflag.FlagSet here to not set `changed` flag 256 // on cmd.Flags() options. Otherwise, string slice options will be duplicated. 257 fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError) 258 259 var cfg config.Config 260 // Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations: 261 // `changed` variable inside string slice vars will be shared. 262 // Use another config variable here, not e.cfg, to not 263 // affect main parsing by this parsing of only config option. 264 initFlagSet(fs, &cfg, e.DBManager, false) 265 initVersionFlagSet(fs, &cfg) 266 267 // Parse max options, even force version option: don't want 268 // to get access to Executor here: it's error-prone to use 269 // cfg vs e.cfg. 270 initRootFlagSet(fs, &cfg, true) 271 272 fs.Usage = func() {} // otherwise, help text will be printed twice 273 if err := fs.Parse(os.Args); err != nil { 274 if err == pflag.ErrHelp { 275 return nil, err 276 } 277 278 return nil, fmt.Errorf("can't parse args: %s", err) 279 } 280 281 return &cfg, nil 282 } 283 284 func (e *Executor) initRun() { 285 e.runCmd = &cobra.Command{ 286 Use: "run", 287 Short: "Run the linters", 288 Run: e.executeRun, 289 PreRunE: func(_ *cobra.Command, _ []string) error { 290 if ok := e.acquireFileLock(); !ok { 291 return errors.New("parallel golangci-lint is running") 292 } 293 return nil 294 }, 295 PostRun: func(_ *cobra.Command, _ []string) { 296 e.releaseFileLock() 297 }, 298 } 299 e.rootCmd.AddCommand(e.runCmd) 300 301 e.runCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals 302 e.runCmd.SetErr(logutils.StdErr) 303 304 e.initRunConfiguration(e.runCmd) 305 } 306 307 func fixSlicesFlags(fs *pflag.FlagSet) { 308 // It's a dirty hack to set flag.Changed to true for every string slice flag. 309 // It's necessary to merge config and command-line slices: otherwise command-line 310 // flags will always overwrite ones from the config. 311 fs.VisitAll(func(f *pflag.Flag) { 312 if f.Value.Type() != "stringSlice" { 313 return 314 } 315 316 s, err := fs.GetStringSlice(f.Name) 317 if err != nil { 318 return 319 } 320 321 if s == nil { // assume that every string slice flag has nil as the default 322 return 323 } 324 325 var safe []string 326 for _, v := range s { 327 // add quotes to escape comma because spf13/pflag use a CSV parser: 328 // https://github.com/spf13/pflag/blob/85dd5c8bc61cfa382fecd072378089d4e856579d/string_slice.go#L43 329 safe = append(safe, `"`+v+`"`) 330 } 331 332 // calling Set sets Changed to true: next Set calls will append, not overwrite 333 _ = f.Value.Set(strings.Join(safe, ",")) 334 }) 335 } 336 337 // runAnalysis executes the linters that have been enabled in the configuration. 338 func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) { 339 e.cfg.Run.Args = args 340 341 lintersToRun, err := e.EnabledLintersSet.GetOptimizedLinters() 342 if err != nil { 343 return nil, err 344 } 345 346 enabledLintersMap, err := e.EnabledLintersSet.GetEnabledLintersMap() 347 if err != nil { 348 return nil, err 349 } 350 351 for _, lc := range e.DBManager.GetAllSupportedLinterConfigs() { 352 isEnabled := enabledLintersMap[lc.Name()] != nil 353 e.reportData.AddLinter(lc.Name(), isEnabled, lc.EnabledByDefault) 354 } 355 356 lintCtx, err := e.contextLoader.Load(ctx, lintersToRun) 357 if err != nil { 358 return nil, errors.Wrap(err, "context loading failed") 359 } 360 lintCtx.Log = e.log.Child(logutils.DebugKeyLintersContext) 361 362 runner, err := lint.NewRunner(e.cfg, e.log.Child(logutils.DebugKeyRunner), 363 e.goenv, e.EnabledLintersSet, e.lineCache, e.DBManager, lintCtx.Packages) 364 if err != nil { 365 return nil, err 366 } 367 368 issues, err := runner.Run(ctx, lintersToRun, lintCtx) 369 if err != nil { 370 return nil, err 371 } 372 373 fixer := processors.NewFixer(e.cfg, e.log, e.fileCache) 374 return fixer.Process(issues), nil 375 } 376 377 func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) { 378 savedStdout, savedStderr = os.Stdout, os.Stderr 379 devNull, err := os.Open(os.DevNull) 380 if err != nil { 381 e.log.Warnf("Can't open null device %q: %s", os.DevNull, err) 382 return 383 } 384 385 os.Stdout, os.Stderr = devNull, devNull 386 return 387 } 388 389 func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) { 390 if len(issues) != 0 { 391 e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound 392 } 393 } 394 395 func (e *Executor) runAndPrint(ctx context.Context, args []string) error { 396 if err := e.goenv.Discover(ctx); err != nil { 397 e.log.Warnf("Failed to discover go env: %s", err) 398 } 399 400 if !logutils.HaveDebugTag(logutils.DebugKeyLintersOutput) { 401 // Don't allow linters and loader to print anything 402 log.SetOutput(io.Discard) 403 savedStdout, savedStderr := e.setOutputToDevNull() 404 defer func() { 405 os.Stdout, os.Stderr = savedStdout, savedStderr 406 }() 407 } 408 409 issues, err := e.runAnalysis(ctx, args) 410 if err != nil { 411 return err // XXX: don't loose type 412 } 413 414 formats := strings.Split(e.cfg.Output.Format, ",") 415 for _, format := range formats { 416 out := strings.SplitN(format, ":", 2) 417 if len(out) < 2 { 418 out = append(out, "") 419 } 420 421 err := e.printReports(ctx, issues, out[1], out[0]) 422 if err != nil { 423 return err 424 } 425 } 426 427 e.setExitCodeIfIssuesFound(issues) 428 429 e.fileCache.PrintStats(e.log) 430 431 return nil 432 } 433 434 func (e *Executor) printReports(ctx context.Context, issues []result.Issue, path, format string) error { 435 w, shouldClose, err := e.createWriter(path) 436 if err != nil { 437 return fmt.Errorf("can't create output for %s: %w", path, err) 438 } 439 440 p, err := e.createPrinter(format, w) 441 if err != nil { 442 if file, ok := w.(io.Closer); shouldClose && ok { 443 _ = file.Close() 444 } 445 return err 446 } 447 448 if err = p.Print(ctx, issues); err != nil { 449 if file, ok := w.(io.Closer); shouldClose && ok { 450 _ = file.Close() 451 } 452 return fmt.Errorf("can't print %d issues: %s", len(issues), err) 453 } 454 455 if file, ok := w.(io.Closer); shouldClose && ok { 456 _ = file.Close() 457 } 458 459 return nil 460 } 461 462 func (e *Executor) createWriter(path string) (io.Writer, bool, error) { 463 if path == "" || path == "stdout" { 464 return logutils.StdOut, false, nil 465 } 466 if path == "stderr" { 467 return logutils.StdErr, false, nil 468 } 469 f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFileMode) 470 if err != nil { 471 return nil, false, err 472 } 473 return f, true, nil 474 } 475 476 func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer, error) { 477 var p printers.Printer 478 switch format { 479 case config.OutFormatJSON: 480 p = printers.NewJSON(&e.reportData, w) 481 case config.OutFormatColoredLineNumber, config.OutFormatLineNumber: 482 p = printers.NewText(e.cfg.Output.PrintIssuedLine, 483 format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName, 484 e.log.Child(logutils.DebugKeyTextPrinter), w) 485 case config.OutFormatTab: 486 p = printers.NewTab(e.cfg.Output.PrintLinterName, e.log.Child(logutils.DebugKeyTabPrinter), w) 487 case config.OutFormatCheckstyle: 488 p = printers.NewCheckstyle(w) 489 case config.OutFormatCodeClimate: 490 p = printers.NewCodeClimate(w) 491 case config.OutFormatHTML: 492 p = printers.NewHTML(w) 493 case config.OutFormatJunitXML: 494 p = printers.NewJunitXML(w) 495 case config.OutFormatGithubActions: 496 p = printers.NewGithub(w) 497 default: 498 return nil, fmt.Errorf("unknown output format %s", format) 499 } 500 501 return p, nil 502 } 503 504 // executeRun executes the 'run' CLI command, which runs the linters. 505 func (e *Executor) executeRun(_ *cobra.Command, args []string) { 506 needTrackResources := e.cfg.Run.IsVerbose || e.cfg.Run.PrintResourcesUsage 507 trackResourcesEndCh := make(chan struct{}) 508 defer func() { // XXX: this defer must be before ctx.cancel defer 509 if needTrackResources { // wait until resource tracking finished to print properly 510 <-trackResourcesEndCh 511 } 512 }() 513 514 e.setTimeoutToDeadlineIfOnlyDeadlineIsSet() 515 ctx, cancel := context.WithTimeout(context.Background(), e.cfg.Run.Timeout) 516 defer cancel() 517 518 if needTrackResources { 519 go watchResources(ctx, trackResourcesEndCh, e.log, e.debugf) 520 } 521 522 if err := e.runAndPrint(ctx, args); err != nil { 523 e.log.Errorf("Running error: %s", err) 524 if e.exitCode == exitcodes.Success { 525 if exitErr, ok := errors.Cause(err).(*exitcodes.ExitError); ok { 526 e.exitCode = exitErr.Code 527 } else { 528 e.exitCode = exitcodes.Failure 529 } 530 } 531 } 532 533 e.setupExitCode(ctx) 534 } 535 536 // to be removed when deadline is finally decommissioned 537 func (e *Executor) setTimeoutToDeadlineIfOnlyDeadlineIsSet() { 538 deadlineValue := e.cfg.Run.Deadline 539 if deadlineValue != 0 && e.cfg.Run.Timeout == defaultTimeout { 540 e.cfg.Run.Timeout = deadlineValue 541 } 542 } 543 544 func (e *Executor) setupExitCode(ctx context.Context) { 545 if ctx.Err() != nil { 546 e.exitCode = exitcodes.Timeout 547 e.log.Errorf("Timeout exceeded: try increasing it by passing --timeout option") 548 return 549 } 550 551 if e.exitCode != exitcodes.Success { 552 return 553 } 554 555 needFailOnWarnings := os.Getenv(lintersdb.EnvTestRun) == "1" || os.Getenv(envFailOnWarnings) == "1" 556 if needFailOnWarnings && len(e.reportData.Warnings) != 0 { 557 e.exitCode = exitcodes.WarningInTest 558 return 559 } 560 561 if e.reportData.Error != "" { 562 // it's a case e.g. when typecheck linter couldn't parse and error and just logged it 563 e.exitCode = exitcodes.ErrorWasLogged 564 return 565 } 566 } 567 568 func watchResources(ctx context.Context, done chan struct{}, logger logutils.Log, debugf logutils.DebugFunc) { 569 startedAt := time.Now() 570 debugf("Started tracking time") 571 572 var maxRSSMB, totalRSSMB float64 573 var iterationsCount int 574 575 const intervalMS = 100 576 ticker := time.NewTicker(intervalMS * time.Millisecond) 577 defer ticker.Stop() 578 579 logEveryRecord := os.Getenv(envMemLogEvery) == "1" 580 const MB = 1024 * 1024 581 582 track := func() { 583 var m runtime.MemStats 584 runtime.ReadMemStats(&m) 585 586 if logEveryRecord { 587 debugf("Stopping memory tracing iteration, printing ...") 588 printMemStats(&m, logger) 589 } 590 591 rssMB := float64(m.Sys) / MB 592 if rssMB > maxRSSMB { 593 maxRSSMB = rssMB 594 } 595 totalRSSMB += rssMB 596 iterationsCount++ 597 } 598 599 for { 600 track() 601 602 stop := false 603 select { 604 case <-ctx.Done(): 605 stop = true 606 debugf("Stopped resources tracking") 607 case <-ticker.C: 608 } 609 610 if stop { 611 break 612 } 613 } 614 track() 615 616 avgRSSMB := totalRSSMB / float64(iterationsCount) 617 618 logger.Infof("Memory: %d samples, avg is %.1fMB, max is %.1fMB", 619 iterationsCount, avgRSSMB, maxRSSMB) 620 logger.Infof("Execution took %s", time.Since(startedAt)) 621 close(done) 622 }