diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9011299..ab0fc69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,13 +147,12 @@ jobs: needs: Go - continue-on-error: true - steps: - name: Checkout uses: actions/checkout@v3 - name: Check spelling + continue-on-error: true uses: crate-ci/typos@master DockerBuild: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bf5ecc9..2081d1c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,6 +13,10 @@ permissions: actions: read contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyse: name: Analyse diff --git a/check/check.go b/check/check.go index 9132d10..50416a8 100644 --- a/check/check.go +++ b/check/check.go @@ -9,9 +9,12 @@ package check import ( "sort" + "strings" "github.com/essentialkaos/ek/v12/sliceutil" "github.com/essentialkaos/ek/v12/sortutil" + "github.com/essentialkaos/ek/v12/strutil" + "github.com/essentialkaos/ek/v12/system" "github.com/essentialkaos/perfecto/spec" ) @@ -30,19 +33,23 @@ const ( // Report contains info about all alerts type Report struct { - Notices Alerts `json:"notices,omitempty"` - Warnings Alerts `json:"warnings,omitempty"` - Errors Alerts `json:"errors,omitempty"` - Criticals Alerts `json:"criticals,omitempty"` + Notices Alerts `json:"notices,omitempty"` + Warnings Alerts `json:"warnings,omitempty"` + Errors Alerts `json:"errors,omitempty"` + Criticals Alerts `json:"criticals,omitempty"` + IgnoredChecks []string `json:"ignored_checks,omitempty"` + NoLint bool `json:"no_lint"` + IsPerfect bool `json:"is_perfect"` + IsSkipped bool `json:"is_skipped"` } // Alert contains basic alert info type Alert struct { - ID string `json:"id"` - Level uint8 `json:"level"` - Info string `json:"info"` - Line spec.Line `json:"line"` - Ignored bool `json:"ignored"` + ID string `json:"id"` + Level uint8 `json:"level"` + Info string `json:"info"` + Line spec.Line `json:"line"` + IsIgnored bool `json:"is_ignored"` } // ////////////////////////////////////////////////////////////////////////////////// // @@ -56,6 +63,10 @@ func (s Alerts) Less(i, j int) bool { return s[i].Line.Index < s[j].Line.Index } // ////////////////////////////////////////////////////////////////////////////////// // +var osInfoFunc = system.GetOSInfo + +// ////////////////////////////////////////////////////////////////////////////////// // + // NewAlert creates new alert func NewAlert(id string, level uint8, info string, line spec.Line) Alert { return Alert{id, level, info, line, false} @@ -63,11 +74,6 @@ func NewAlert(id string, level uint8, info string, line spec.Line) Alert { // ////////////////////////////////////////////////////////////////////////////////// // -// IsPerfect returns true if report doesn't have any alerts -func (r *Report) IsPerfect() bool { - return r.Total() == 0 -} - // Total returns total number of alerts (including ignored) func (r *Report) Total() int { return r.Notices.Total() + @@ -128,7 +134,7 @@ func (r *Report) IDs() []string { // HasAlerts returns true if alerts contains at least one non-ignored alert func (a Alerts) HasAlerts() bool { for _, alert := range a { - if alert.Ignored { + if alert.IsIgnored { continue } @@ -148,7 +154,7 @@ func (a Alerts) Ignored() int { var counter int for _, alert := range a { - if alert.Ignored { + if alert.IsIgnored { counter++ } } @@ -158,12 +164,18 @@ func (a Alerts) Ignored() int { // ////////////////////////////////////////////////////////////////////////////////// // -// Check check spec +// Check executes different checks over given spec func Check(s *spec.Spec, lint bool, linterConfig string, ignored []string) *Report { - report := &Report{} + report := &Report{NoLint: !lint, IgnoredChecks: ignored} + + if !isApplicableTarget(s) { + report.IsSkipped = true + return report + } + checkers := getCheckers() - if lint { + if lint && !sliceutil.Contains(ignored, RPMLINT_CHECK_ID) { alerts := Lint(s, linterConfig) appendLinterAlerts(report, alerts) } @@ -178,8 +190,8 @@ func Check(s *spec.Spec, lint bool, linterConfig string, ignored []string) *Repo ignore := sliceutil.Contains(ignored, id) for _, alert := range alerts { - if ignore || alert.Line.Skip { - alert.Ignored = true + if ignore || alert.Line.Ignore { + alert.IsIgnored = true } switch alert.Level { @@ -200,11 +212,61 @@ func Check(s *spec.Spec, lint bool, linterConfig string, ignored []string) *Repo sort.Sort(Alerts(report.Errors)) sort.Sort(Alerts(report.Criticals)) + report.IsPerfect = report.Total()-report.Ignored() == 0 + return report } // ////////////////////////////////////////////////////////////////////////////////// // +// isApplicableTarget checks if current system is applicable for tests +func isApplicableTarget(s *spec.Spec) bool { + if len(s.Targets) == 0 { + return true + } + + osInfo, err := osInfoFunc() + + if err != nil { + return false + } + + for _, target := range s.Targets { + if isTargetFit(osInfo, target) { + return true + } + } + + return false +} + +// isTargetFit returns true if current system is applicable for tests +func isTargetFit(osInfo *system.OSInfo, target string) bool { + if osInfo.ID == target { + return true + } + + majorVersion, _, _ := strings.Cut(osInfo.VersionID, ".") + + if osInfo.ID+majorVersion == target { + return true + } + + _, platform, _ := strings.Cut(osInfo.PlatformID, ":") + + if target == platform { + return true + } + + for _, id := range strutil.Fields(osInfo.IDLike) { + if "@"+id == target { + return true + } + } + + return false +} + // appendLinterAlerts append rpmlint alerts to report func appendLinterAlerts(r *Report, alerts []Alert) { if len(alerts) == 0 { @@ -212,7 +274,7 @@ func appendLinterAlerts(r *Report, alerts []Alert) { } for _, alert := range alerts { - if alert.Line.Skip { + if alert.Line.Ignore { continue } diff --git a/check/checkers.go b/check/checkers.go index 5a712f8..77900fb 100644 --- a/check/checkers.go +++ b/check/checkers.go @@ -117,12 +117,12 @@ func checkForUselessSpaces(id string, s *spec.Spec) []Alert { for _, line := range s.Data { if contains(line, " ") { if strings.TrimSpace(line.Text) == "" { - impLine := spec.Line{line.Index, strings.Replace(line.Text, " ", "▒", -1), line.Skip} + impLine := spec.Line{line.Index, strings.Replace(line.Text, " ", "░", -1), line.Ignore} result = append(result, NewAlert(id, LEVEL_NOTICE, "Line contains useless spaces", impLine)) } else if strings.TrimRight(line.Text, " ") != line.Text { cleanLine := strings.TrimRight(line.Text, " ") spaces := len(line.Text) - len(cleanLine) - impLine := spec.Line{line.Index, cleanLine + strings.Repeat("▒", spaces), line.Skip} + impLine := spec.Line{line.Index, cleanLine + strings.Repeat("░", spaces), line.Ignore} result = append(result, NewAlert(id, LEVEL_NOTICE, "Line contains spaces at the end of line", impLine)) } } diff --git a/check/checkers_test.go b/check/checkers_test.go index eede0c3..8110561 100644 --- a/check/checkers_test.go +++ b/check/checkers_test.go @@ -8,8 +8,11 @@ package check // ////////////////////////////////////////////////////////////////////////////////// // import ( + "fmt" "testing" + "github.com/essentialkaos/ek/v12/system" + "github.com/essentialkaos/perfecto/spec" chk "github.com/essentialkaos/check" @@ -37,7 +40,7 @@ func (sc *CheckSuite) TestCheckForUselessSpaces(c *chk.C) { c.Assert(alerts, chk.HasLen, 2) c.Assert(alerts[0].Info, chk.Equals, "Line contains spaces at the end of line") - c.Assert(alerts[0].Line.Text, chk.Equals, "License: MIT▒") + c.Assert(alerts[0].Line.Text, chk.Equals, "License: MIT░") c.Assert(alerts[1].Info, chk.Equals, "Line contains useless spaces") c.Assert(alerts[1].Line.Index, chk.Equals, 10) } @@ -366,7 +369,7 @@ func (sc *CheckSuite) TestCheckURLForHTTPS(c *chk.C) { alerts := checkURLForHTTPS("", s) - c.Assert(alerts, chk.HasLen, 2) + c.Assert(alerts, chk.HasLen, 3) c.Assert(alerts[0].Info, chk.Equals, "Domain kaos.st supports HTTPS. Replace http by https in URL.") c.Assert(alerts[0].Line.Index, chk.Equals, 13) } @@ -506,7 +509,8 @@ func (sc *CheckSuite) TestRPMLint(c *chk.C) { r := Check(s, true, "", nil) c.Assert(r, chk.NotNil) - c.Assert(r.IsPerfect(), chk.Equals, true) + c.Assert(r.Total(), chk.Equals, 0) + c.Assert(r.IsPerfect, chk.Equals, true) c.Assert(r.IDs(), chk.HasLen, 0) s, err = spec.Read("../testdata/test_7.spec") @@ -517,7 +521,7 @@ func (sc *CheckSuite) TestRPMLint(c *chk.C) { r = Check(s, true, "", nil) c.Assert(r, chk.NotNil) - c.Assert(r.IsPerfect(), chk.Equals, false) + c.Assert(r.IsPerfect, chk.Equals, false) s, err = spec.Read("../testdata/test_11.spec") @@ -527,8 +531,8 @@ func (sc *CheckSuite) TestRPMLint(c *chk.C) { r = Check(s, true, "", []string{"PF20", "PF21"}) c.Assert(r, chk.NotNil) - c.Assert(r.Warnings, chk.HasLen, 3) - c.Assert(r.Warnings[0].Ignored, chk.Equals, true) + c.Assert(r.Warnings, chk.HasLen, 4) + c.Assert(r.Warnings[0].IsIgnored, chk.Equals, true) rpmLintBin = "echo" s = &spec.Spec{File: ""} @@ -544,6 +548,38 @@ func (sc *CheckSuite) TestRPMLint(c *chk.C) { rpmLintBin = "rpmlint" } +func (sc *CheckSuite) TestTargetCheck(c *chk.C) { + s, err := spec.Read("../testdata/test_18.spec") + + c.Assert(err, chk.IsNil) + c.Assert(s, chk.NotNil) + c.Assert(s.Targets, chk.DeepEquals, []string{"mysuppaos"}) + + r := Check(s, false, "", nil) + c.Assert(r, chk.NotNil) + + osInfo := &system.OSInfo{ + ID: "almalinux", + VersionID: "8.8", + PlatformID: "platform:el8", + IDLike: "rhel centos fedora", + } + + c.Assert(isTargetFit(osInfo, "almalinux"), chk.Equals, true) + c.Assert(isTargetFit(osInfo, "almalinux8"), chk.Equals, true) + c.Assert(isTargetFit(osInfo, "el8"), chk.Equals, true) + c.Assert(isTargetFit(osInfo, "@fedora"), chk.Equals, true) + c.Assert(isTargetFit(osInfo, "test"), chk.Equals, false) + + osInfoFunc = func() (*system.OSInfo, error) { + return nil, fmt.Errorf("error") + } + + c.Assert(isApplicableTarget(s), chk.Equals, false) + + osInfoFunc = system.GetOSInfo +} + func (sc *CheckSuite) TestRPMLintParser(c *chk.C) { report := &Report{} alerts := []Alert{} @@ -556,7 +592,7 @@ func (sc *CheckSuite) TestRPMLintParser(c *chk.C) { a, ok := parseAlertLine("test.spec: W: no-buildroot-tag", s) c.Assert(ok, chk.Equals, true) - c.Assert(a.ID, chk.Equals, "") + c.Assert(a.ID, chk.Equals, "LNT0") c.Assert(a.Level, chk.Equals, LEVEL_ERROR) c.Assert(a.Info, chk.Equals, "no-buildroot-tag") c.Assert(a.Line.Index, chk.Equals, -1) @@ -565,7 +601,7 @@ func (sc *CheckSuite) TestRPMLintParser(c *chk.C) { a, ok = parseAlertLine("test.spec: E: specfile-error error: line 10: Unknown tag: Release1", s) c.Assert(ok, chk.Equals, true) - c.Assert(a.ID, chk.Equals, "") + c.Assert(a.ID, chk.Equals, "LNT0") c.Assert(a.Level, chk.Equals, LEVEL_CRITICAL) c.Assert(a.Info, chk.Equals, "Unknown tag: Release1") c.Assert(a.Line.Index, chk.Equals, 10) @@ -574,14 +610,14 @@ func (sc *CheckSuite) TestRPMLintParser(c *chk.C) { a, ok = parseAlertLine("test.spec:67: W: macro-in-%changelog %record", s) c.Assert(ok, chk.Equals, true) - c.Assert(a.ID, chk.Equals, "") + c.Assert(a.ID, chk.Equals, "LNT0") c.Assert(a.Level, chk.Equals, LEVEL_ERROR) c.Assert(a.Info, chk.Equals, "macro-in-%changelog %record") c.Assert(a.Line.Index, chk.Equals, 67) alerts = append(alerts, a) a, ok = parseAlertLine("test.spec:68: W: macro-in-%changelog %record", s) - a.Line.Skip = true + a.Line.Ignore = true alerts = append(alerts, a) a, ok = parseAlertLine("test.spec: E: specfile-error error: line A: Unknown tag: Release1", s) @@ -603,21 +639,21 @@ func (sc *CheckSuite) TestAux(c *chk.C) { c.Assert(getCheckers(), chk.HasLen, 27) r := &Report{} - c.Assert(r.IsPerfect(), chk.Equals, true) + c.Assert(r.IsPerfect, chk.Equals, false) r = &Report{Notices: []Alert{Alert{}}} - c.Assert(r.IsPerfect(), chk.Equals, false) + c.Assert(r.IsPerfect, chk.Equals, false) r = &Report{Warnings: []Alert{Alert{}}} - c.Assert(r.IsPerfect(), chk.Equals, false) + c.Assert(r.IsPerfect, chk.Equals, false) r = &Report{Errors: []Alert{Alert{}}} - c.Assert(r.IsPerfect(), chk.Equals, false) + c.Assert(r.IsPerfect, chk.Equals, false) r = &Report{Criticals: []Alert{Alert{}}} - c.Assert(r.IsPerfect(), chk.Equals, false) + c.Assert(r.IsPerfect, chk.Equals, false) r = &Report{ Notices: []Alert{Alert{}}, Warnings: []Alert{Alert{ID: "PF0"}}, Errors: []Alert{Alert{ID: "PF0"}}, - Criticals: []Alert{Alert{ID: "PF0", Ignored: true}}, + Criticals: []Alert{Alert{ID: "PF0", IsIgnored: true}}, } c.Assert(r.IDs(), chk.HasLen, 1) @@ -633,7 +669,7 @@ func (sc *CheckSuite) TestAux(c *chk.C) { c.Assert(a.HasAlerts(), chk.Equals, false) a = Alerts{Alert{}} c.Assert(a.HasAlerts(), chk.Equals, true) - a = Alerts{Alert{Ignored: true}} + a = Alerts{Alert{IsIgnored: true}} c.Assert(a.HasAlerts(), chk.Equals, false) al, _ := parseAlertLine("../testdata/test_7.spec: E: specfile-error warning: some error", &spec.Spec{}) diff --git a/check/rpmlint.go b/check/rpmlint.go index 3cb9ed0..96f30e1 100644 --- a/check/rpmlint.go +++ b/check/rpmlint.go @@ -20,6 +20,11 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // +// RPMLINT_CHECK_ID is ID for all rpmlint checks +const RPMLINT_CHECK_ID = "LNT0" + +// ////////////////////////////////////////////////////////////////////////////////// // + var rpmLintBin = "rpmlint" // ////////////////////////////////////////////////////////////////////////////////// // @@ -77,9 +82,9 @@ func parseAlertLine(text string, s *spec.Spec) (Alert, bool) { switch level { case "W": - return NewAlert("", LEVEL_ERROR, desc, s.GetLine(line)), true + return NewAlert(RPMLINT_CHECK_ID, LEVEL_ERROR, desc, s.GetLine(line)), true case "E": - return NewAlert("", LEVEL_CRITICAL, desc, s.GetLine(line)), true + return NewAlert(RPMLINT_CHECK_ID, LEVEL_CRITICAL, desc, s.GetLine(line)), true } return Alert{}, false diff --git a/cli/cli.go b/cli/cli.go index 83fa3e5..e6dce4f 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -37,7 +37,7 @@ import ( // App info const ( APP = "Perfecto" - VER = "5.0.0" + VER = "6.0.0" DESC = "Tool for checking perfectly written RPM specs" ) @@ -192,8 +192,10 @@ func process(files options.Arguments) { printErrorAndExit("Output format %q is not supported", format) } + rndr := getRenderer(format, files) + for _, file := range files { - ec := checkSpec(file.Clean().String(), format) + ec := checkSpec(file.Clean().String(), rndr) exitCode = mathutil.Max(ec, exitCode) } @@ -203,35 +205,40 @@ func process(files options.Arguments) { // codebeat:disable[ABC] // checkSpec check spec file -func checkSpec(file, format string) int { - rnd := getRenderer(format) +func checkSpec(file string, rndr render.Renderer) int { + var ignoreChecks []string + s, err := spec.Read(file) if err != nil && !options.GetB(OPT_QUIET) { - rnd.Error(file, err) + rndr.Error(file, err) return 1 } + if options.Has(OPT_IGNORE) { + ignoreChecks = strings.Split(options.GetS(OPT_IGNORE), ",") + } + report := check.Check( s, !options.GetB(OPT_NO_LINT), options.GetS(OPT_LINT_CONFIG), - strings.Split(options.GetS(OPT_IGNORE), ","), + ignoreChecks, ) - if report.IsPerfect() { + switch { + case report.IsSkipped: if !options.GetB(OPT_QUIET) { - rnd.Perfect(file) + rndr.Skipped(file, report) + } + return 0 + case report.IsPerfect: + if !options.GetB(OPT_QUIET) { + rndr.Perfect(file, report) } - return 0 } - err = rnd.Report(file, report) - - if err != nil { - printError(err.Error()) - return 1 - } + rndr.Report(file, report) return getExitCode(report) } @@ -257,14 +264,16 @@ func getFormat(files options.Arguments) string { } // getRenderer returns renderer for given format -func getRenderer(format string) render.Renderer { +func getRenderer(format string, files options.Arguments) render.Renderer { + maxFilenameSize := getMaxFilenameSize(files) + switch format { case FORMAT_SUMMARY: - return &render.TerminalRenderer{Format: FORMAT_SUMMARY} + return &render.TerminalRenderer{Format: FORMAT_SUMMARY, FilenameSize: maxFilenameSize} case FORMAT_TINY: - return &render.TerminalRenderer{Format: FORMAT_TINY} + return &render.TerminalRenderer{Format: FORMAT_TINY, FilenameSize: maxFilenameSize} case FORMAT_SHORT: - return &render.TerminalRenderer{Format: FORMAT_SHORT} + return &render.TerminalRenderer{Format: FORMAT_SHORT, FilenameSize: maxFilenameSize} case FORMAT_GITHUB: return &render.GithubRenderer{} case FORMAT_JSON: @@ -272,10 +281,22 @@ func getRenderer(format string) render.Renderer { case FORMAT_XML: return &render.XMLRenderer{} default: - return &render.TerminalRenderer{Format: FORMAT_FULL} + return &render.TerminalRenderer{Format: FORMAT_FULL, FilenameSize: maxFilenameSize} } } +// getMaxFilenameSize returns maximum filename size without extension +func getMaxFilenameSize(files options.Arguments) int { + var result int + + for _, file := range files { + filenameSize := strutil.Exclude(file.Base().Clean().String(), ".spec") + result = mathutil.Max(result, len(filenameSize)) + } + + return result +} + // codebeat:enable[ABC] // getExitCode return exit code based on report data diff --git a/cli/render/render.go b/cli/render/render.go index 5c310d3..668a6ff 100644 --- a/cli/render/render.go +++ b/cli/render/render.go @@ -17,10 +17,13 @@ import ( type Renderer interface { // Report renders alerts from perfecto report - Report(file string, report *check.Report) error + Report(file string, report *check.Report) // Perfect renders message about perfect spec - Perfect(file string) + Perfect(file string, report *check.Report) + + // Skipped renders message about skipped check + Skipped(file string, report *check.Report) // Error renders global error message Error(file string, err error) diff --git a/cli/render/renderer_github.go b/cli/render/renderer_github.go index 707ead9..914541b 100644 --- a/cli/render/renderer_github.go +++ b/cli/render/renderer_github.go @@ -11,6 +11,8 @@ import ( "fmt" "github.com/essentialkaos/ek/v12/fmtc" + "github.com/essentialkaos/ek/v12/path" + "github.com/essentialkaos/ek/v12/strutil" "github.com/essentialkaos/perfecto/check" ) @@ -23,7 +25,7 @@ type GithubRenderer struct{} // ////////////////////////////////////////////////////////////////////////////////// // // Report renders alerts from perfecto report -func (r *GithubRenderer) Report(file string, report *check.Report) error { +func (r *GithubRenderer) Report(file string, report *check.Report) { if report.Notices.Total() != 0 { r.renderActionAlerts("notice", file, report.Notices) } @@ -39,13 +41,18 @@ func (r *GithubRenderer) Report(file string, report *check.Report) error { if report.Criticals.Total() != 0 { r.renderActionAlerts("error", file, report.Criticals) } - - return nil } // Perfect renders message about perfect spec -func (r *GithubRenderer) Perfect(file string) { - fmtc.Println("{g}This spec is perfect!{!}") +func (r *GithubRenderer) Perfect(file string, report *check.Report) { + specName := strutil.Exclude(path.Base(file), ".spec") + fmtc.Printf("{g}{*}%s.spec{!*} is perfect!{!}\n", specName) +} + +// Skipped renders message about skipped check +func (r *GithubRenderer) Skipped(file string, report *check.Report) { + specName := strutil.Exclude(path.Base(file), ".spec") + fmtc.Printf("{s}{*}%s.spec{!*} check skipped due to non-applicable target{!}\n", specName) } // Error renders global error message diff --git a/cli/render/renderer_json.go b/cli/render/renderer_json.go index 7b8468c..1d357d0 100644 --- a/cli/render/renderer_json.go +++ b/cli/render/renderer_json.go @@ -22,21 +22,18 @@ type JSONRenderer struct{} // ////////////////////////////////////////////////////////////////////////////////// // // Report renders alerts from perfecto report -func (r *JSONRenderer) Report(file string, report *check.Report) error { - data, err := json.MarshalIndent(report, "", " ") - - if err != nil { - return err - } - - fmt.Println(string(data)) - - return nil +func (r *JSONRenderer) Report(file string, report *check.Report) { + encodeReport(report) } // Perfect renders message about perfect spec -func (r *JSONRenderer) Perfect(file string) { - fmt.Println("{}") +func (r *JSONRenderer) Perfect(file string, report *check.Report) { + encodeReport(report) +} + +// Skipped renders message about skipped check +func (r *JSONRenderer) Skipped(file string, report *check.Report) { + encodeReport(report) } // Error renders global error message @@ -45,3 +42,8 @@ func (r *JSONRenderer) Error(file string, err error) { } // ////////////////////////////////////////////////////////////////////////////////// // + +func encodeReport(report *check.Report) { + data, _ := json.MarshalIndent(report, "", " ") + fmt.Println(string(data)) +} diff --git a/cli/render/renderer_terminal.go b/cli/render/renderer_terminal.go index e9ca249..c339b98 100644 --- a/cli/render/renderer_terminal.go +++ b/cli/render/renderer_terminal.go @@ -25,7 +25,8 @@ import ( // TerminalRenderer renders report to terminal type TerminalRenderer struct { - Format string + Format string + FilenameSize int levelsPrefixes map[uint8]string bgColor map[uint8]string @@ -38,7 +39,72 @@ type TerminalRenderer struct { // ////////////////////////////////////////////////////////////////////////////////// // // Report renders alerts from perfecto report -func (r *TerminalRenderer) Report(file string, report *check.Report) error { +func (r *TerminalRenderer) Report(file string, report *check.Report) { + r.initUI() + + switch r.Format { + case "summary": + r.renderSummary(report) + case "short": + r.renderShortReport(report) + case "tiny": + r.renderTinyReport(file, report) + default: + r.renderFull(report) + } +} + +// Perfect renders message about perfect spec +func (r *TerminalRenderer) Perfect(file string, report *check.Report) { + r.initUI() + + specName := strutil.Exclude(path.Base(file), ".spec") + + switch r.Format { + case "tiny": + fmtc.Printf("%s{s}:{!} {g}✔ {!}\n", fmtutil.Align(specName, fmtutil.RIGHT, r.FilenameSize+2)) + case "summary": + r.renderSummary(report) + default: + fmtc.Printf("{g}{*}%s.spec{!*} is perfect!{!}\n", specName) + } +} + +// Skipped renders message about skipped check +func (r *TerminalRenderer) Skipped(file string, report *check.Report) { + r.initUI() + + specName := strutil.Exclude(path.Base(file), ".spec") + + switch r.Format { + case "tiny": + fmtc.Printf("%s{s}:{!} {s}—{!}\n", fmtutil.Align(specName, fmtutil.RIGHT, r.FilenameSize+2)) + default: + fmtc.Printf("{s}{*}%s.spec{!*} check skipped due to non-applicable target{!}\n", specName) + } +} + +// Error renders global error message +func (r *TerminalRenderer) Error(file string, err error) { + r.initUI() + + specName := strutil.Exclude(path.Base(file), ".spec") + + switch r.Format { + case "tiny": + fmtc.Printf( + "%s{s}:{!} {r}✖ (%v){!}\n", + fmtutil.Align(specName, fmtutil.RIGHT, r.FilenameSize+2), err, + ) + default: + fmtc.Fprintf(os.Stderr, "{r}%v{!}\n", err) + } +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// initUI initialize UI +func (r *TerminalRenderer) initUI() { r.levelsPrefixes = map[uint8]string{ check.LEVEL_NOTICE: "", check.LEVEL_WARNING: "", @@ -88,46 +154,11 @@ func (r *TerminalRenderer) Report(file string, report *check.Report) error { r.hlColor[check.LEVEL_ERROR] = "{*}{#214}" } - switch r.Format { - case "summary": - r.renderSummary(report) - case "short": - r.renderShortReport(report) - case "tiny": - r.renderTinyReport(file, report) - default: - r.renderFull(report) + if r.FilenameSize == 0 { + r.FilenameSize = 24 } - - return nil } -// Perfect renders message about perfect spec -func (r *TerminalRenderer) Perfect(file string) { - specName := strutil.Exclude(path.Base(file), ".spec") - - switch r.Format { - case "tiny": - fmtc.Printf("%24s{s}:{!} {g}✔ {!}\n", specName) - default: - fmtc.Println("{g}This spec is perfect!{!}") - } -} - -// Error renders global error message -func (r *TerminalRenderer) Error(file string, err error) { - specName := strutil.Exclude(path.Base(file), ".spec") - - switch r.Format { - case "tiny": - fmtc.Printf("%24s{s}:{!} {r}✖ ERROR: %v{!}\n", specName, err) - default: - fmtc.Fprintf(os.Stderr, "{r}%v{!}\n", err) - } -} - -// ////////////////////////////////////////////////////////////////////////////////// // - // renderFull prints full report func (r *TerminalRenderer) renderFull(report *check.Report) { fmtc.NewLine() @@ -180,17 +211,12 @@ func (r *TerminalRenderer) renderShortReport(report *check.Report) { if report.Criticals.Total() != 0 { r.renderShortAlerts(check.LEVEL_CRITICAL, report.Criticals) } - - fmtc.NewLine() - - r.renderSummary(report) } // renderTinyReport prints tiny report (useful for mass check) func (r *TerminalRenderer) renderTinyReport(file string, report *check.Report) { specName := strutil.Exclude(path.Base(file), ".spec") - - fmtc.Printf("%24s{s}:{!} ", specName) + fmtc.Printf("%s{s}:{!} ", fmtutil.Align(specName, fmtutil.RIGHT, r.FilenameSize+2)) categories := map[uint8][]check.Alert{ check.LEVEL_NOTICE: report.Notices, @@ -215,13 +241,13 @@ func (r *TerminalRenderer) renderTinyReport(file string, report *check.Report) { for _, alert := range alerts { if fmtc.DisableColors { - if alert.Ignored { + if alert.IsIgnored { fmtc.Printf("X ") } else { fmtc.Printf(r.fallbackLevel[level] + " ") } } else { - if alert.Ignored { + if alert.IsIgnored { fmtc.Printf("{s-}%s{!}", "•") } else { fmtc.Printf(r.fgColor[level]+"%s{!}", "•") @@ -265,7 +291,7 @@ func (r *TerminalRenderer) renderAlert(alert check.Alert) { hl := r.hlColor[alert.Level] lc := fg - if alert.Ignored { + if alert.IsIgnored { fg = "{s}" hl = "{s*}" } @@ -278,8 +304,8 @@ func (r *TerminalRenderer) renderAlert(alert check.Alert) { fmtc.Printf(hl + "[global]{!} ") } - if alert.Ignored { - fmtc.Printf("{s}[A]{!} ") + if alert.IsIgnored { + fmtc.Printf("{s}[I]{!} ") } if alert.ID != "" { @@ -290,7 +316,11 @@ func (r *TerminalRenderer) renderAlert(alert check.Alert) { if alert.Line.Text != "" { text := strutil.Ellipsis(alert.Line.Text, 86) - fmtc.Printf(lc+"│ {s-}%s{!}\n", text) + if alert.IsIgnored { + fmtc.Printf(lc+"│ {s-}%s{!}\n", text) + } else { + fmtc.Printf(lc+"│ {s}%s{!}\n", text) + } } } @@ -307,7 +337,7 @@ func (r *TerminalRenderer) renderLinks(report *check.Report) { fmtc.Println("\n{*}Links:{!}\n") for _, id := range report.IDs() { - fmtc.Printf(" • %s%s\n", "https://kaos.sh/perfecto/w/", id) + fmtc.Printf(" {s}•{!} %s%s\n", "https://kaos.sh/perfecto/w/", id) } fmtc.NewLine() @@ -315,7 +345,7 @@ func (r *TerminalRenderer) renderLinks(report *check.Report) { // renderSummary prints report statistics func (r *TerminalRenderer) renderSummary(report *check.Report) { - categories := map[uint8][]check.Alert{ + categories := map[uint8]check.Alerts{ check.LEVEL_NOTICE: report.Notices, check.LEVEL_WARNING: report.Warnings, check.LEVEL_ERROR: report.Errors, @@ -331,25 +361,26 @@ func (r *TerminalRenderer) renderSummary(report *check.Report) { var result []string - fmtc.Printf("{*}Summary:{!} ") - for _, level := range levels { alerts := categories[level] if len(alerts) == 0 { + result = append(result, + r.fgColor[level]+"{*}"+r.headers[level]+":{!*} 0{!}", + ) continue } - total, absolved := report.Total(), report.Ignored() - actual := total - absolved + total, ignored := alerts.Total(), alerts.Ignored() + actual := total - ignored - if absolved != 0 { + if ignored != 0 { result = append(result, - r.fgColor[level]+r.headers[level]+": "+strconv.Itoa(actual)+"{s}/"+strconv.Itoa(absolved)+"{!}", + r.fgColor[level]+"{*}"+r.headers[level]+":{!*} "+strconv.Itoa(actual)+"{s-}/"+strconv.Itoa(ignored)+"{!}", ) } else { result = append(result, - r.fgColor[level]+r.headers[level]+": "+strconv.Itoa(actual)+"{!}", + r.fgColor[level]+"{*}"+r.headers[level]+":{!*} "+strconv.Itoa(actual), ) } } @@ -379,8 +410,8 @@ func (r *TerminalRenderer) renderShortAlert(alert check.Alert) { fmtc.Printf(hl + "[global]{!} ") } - if alert.Ignored { - fmtc.Printf("{s}[A]{!} ") + if alert.IsIgnored { + fmtc.Printf("{s}[I]{!} ") } if alert.ID != "" { diff --git a/cli/render/renderer_xml.go b/cli/render/renderer_xml.go index e0f844a..991a193 100644 --- a/cli/render/renderer_xml.go +++ b/cli/render/renderer_xml.go @@ -22,9 +22,13 @@ type XMLRenderer struct{} // ////////////////////////////////////////////////////////////////////////////////// // // Report renders alerts from perfecto report -func (r *XMLRenderer) Report(file string, report *check.Report) error { +func (r *XMLRenderer) Report(file string, report *check.Report) { fmt.Println(``) - fmt.Println("") + fmt.Printf( + "\n", + report.NoLint, report.IsPerfect, report.IsSkipped, + ) + fmt.Println(" ") if len(report.Notices) != 0 { r.renderAlertsAsXML("notices", report.Notices) @@ -42,47 +46,64 @@ func (r *XMLRenderer) Report(file string, report *check.Report) error { r.renderAlertsAsXML("criticals", report.Criticals) } - fmt.Println("") + fmt.Println(" ") + fmt.Println("") +} - return nil +// Perfect renders message about perfect spec +func (r *XMLRenderer) Perfect(file string, report *check.Report) { + fmt.Println(``) + fmt.Printf( + "\n", + report.NoLint, report.IsPerfect, report.IsSkipped, + ) + fmt.Println(" ") + fmt.Println("") } // Perfect renders message about perfect spec -func (r *XMLRenderer) Perfect(file string) { +func (r *XMLRenderer) Skipped(file string, report *check.Report) { fmt.Println(``) - fmt.Println("\n") + fmt.Printf( + "\n", + report.NoLint, report.IsPerfect, report.IsSkipped, + ) + fmt.Println(" ") + fmt.Println("") } // Error renders global error message func (r *XMLRenderer) Error(file string, err error) { fmt.Println(``) - fmt.Println("") - fmt.Printf(" %v\n", err) - fmt.Println("") + fmt.Println("") + fmt.Println(" ") + fmt.Printf(" %v\n", err) + fmt.Println(" ") + fmt.Println("") } // ////////////////////////////////////////////////////////////////////////////////// // // renderAlertsAsXML renders alerts category as XML node func (r *XMLRenderer) renderAlertsAsXML(category string, alerts []check.Alert) { - fmt.Printf(" <%s>\n", category) + fmt.Printf(" <%s>\n", category) for _, alert := range alerts { - fmt.Printf(" \n", alert.ID, alert.Ignored) - fmt.Printf(" %s\n", r.escapeStringForXML(alert.Info)) + fmt.Printf(" \n", alert.ID, alert.IsIgnored) + fmt.Printf(" %s\n", r.escapeStringForXML(alert.Info)) if alert.Line.Index != -1 { fmt.Printf( - " %s\n", - alert.Line.Index, alert.Line.Skip, + " %s\n", + alert.Line.Index, alert.Line.Ignore, r.escapeStringForXML(alert.Line.Text), ) } - fmt.Println(" ") + fmt.Println(" ") } - fmt.Printf(" \n", category) + fmt.Printf(" \n", category) } // escapeStringForXML returns properly escaped XML equivalent diff --git a/cli/support/support.go b/cli/support/support.go index 490591e..f4cb367 100644 --- a/cli/support/support.go +++ b/cli/support/support.go @@ -92,6 +92,7 @@ func showOSInfo() { printInfo(12, "ID Like", osInfo.IDLike) printInfo(12, "Version ID", osInfo.VersionID) printInfo(12, "Version Code", osInfo.VersionCodename) + printInfo(12, "Platform ID", osInfo.PlatformID) printInfo(12, "CPE", osInfo.CPEName) } diff --git a/common/perfecto.spec b/common/perfecto.spec index 96dc180..1055350 100644 --- a/common/perfecto.spec +++ b/common/perfecto.spec @@ -10,7 +10,7 @@ Summary: Tool for checking perfectly written RPM specs Name: perfecto -Version: 5.0.0 +Version: 6.0.0 Release: 0%{?dist} Group: Development/Tools License: Apache License, Version 2.0 @@ -105,6 +105,15 @@ fi ################################################################################ %changelog +* Thu Jul 20 2023 Anton Novojilov - 6.0.0-0 +- Added 'target' directive +- Improved XML render +- Improved JSON render +- Improved github render +- Improved terminal render +- Added extra info to report for XML and JSON renders +- Code refactoring + * Mon Jul 10 2023 Anton Novojilov - 5.0.0-0 - -A/--absolve option renamed to -I/--ignore - 'absolve' directive renamed to 'ignore' diff --git a/go.mod b/go.mod index 56693de..b0daa27 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.18 require ( github.com/essentialkaos/check v1.4.0 github.com/essentialkaos/depsy v1.1.0 - github.com/essentialkaos/ek/v12 v12.68.0 + github.com/essentialkaos/ek/v12 v12.71.0 ) require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect - golang.org/x/sys v0.9.0 // indirect + golang.org/x/sys v0.10.0 // indirect ) diff --git a/go.sum b/go.sum index 2c3babf..0502a2a 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1n github.com/essentialkaos/check v1.4.0/go.mod h1:LMKPZ2H+9PXe7Y2gEoKyVAwUqXVgx7KtgibfsHJPus0= github.com/essentialkaos/depsy v1.1.0 h1:U6dp687UkQwXlZU17Hg2KMxbp3nfZAoZ8duaeUFYvJI= github.com/essentialkaos/depsy v1.1.0/go.mod h1:kpiTAV17dyByVnrbNaMcZt2jRwvuXClUYOzpyJQwtG8= -github.com/essentialkaos/ek/v12 v12.68.0 h1:/tNqKHfGypPUUu+aQ8LcmmWWEOO8itJZoJgOCPOZFY8= -github.com/essentialkaos/ek/v12 v12.68.0/go.mod h1:CAPhbYcAiQsCP7CMyBlkgD/MgvuLcbr4EXjCIKn3DIk= +github.com/essentialkaos/ek/v12 v12.71.0 h1:r5rz4C+wVhsjiXV/vwnaeojrRAOx/Q8aufyRGKLUuRI= +github.com/essentialkaos/ek/v12 v12.71.0/go.mod h1:juDcZWOWaj1QmYShZkT9RzdqJ3n0tmeP/iq4sw5fQF0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -13,5 +13,5 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/spec/spec.go b/spec/spec.go index 6e6b7e2..bbbde26 100644 --- a/spec/spec.go +++ b/spec/spec.go @@ -45,19 +45,25 @@ const ( SECTION_VERIFYSCRIPT = "verifyscript" ) +const ( + DIRECTIVE_IGNORE = "perfecto:ignore" + DIRECTIVE_TARGET = "perfecto:target" +) + // ////////////////////////////////////////////////////////////////////////////////// // // Spec spec contains data from spec file type Spec struct { - File string `json:"file"` - Data []Line `json:"data"` + File string `json:"file"` + Data []Line `json:"data"` + Targets []string `json:"targets"` } // Line contains line data and index type Line struct { - Index int `json:"index"` - Text string `json:"text"` - Skip bool `json:"skip"` + Index int `json:"index"` + Text string `json:"text"` + Ignore bool `json:"ignore"` } // Header header contains header info and data @@ -143,7 +149,7 @@ var sectionRegex = regexp.MustCompile(`^%(prep|setup|build|install|check|clean|f // Read read and parse rpm spec file func Read(file string) (*Spec, error) { - err := checkFile(file) + err := fsutil.ValidatePerms("FRS", file) if err != nil { return nil, err @@ -226,7 +232,7 @@ func readFile(file string) (*Spec, error) { defer fd.Close() - line, skip := 1, 0 + line, ignore := 1, 0 spec := &Spec{File: file} r := bufio.NewReader(fd) @@ -235,54 +241,39 @@ LOOP: text, err := r.ReadString('\n') if err != nil { - spec.Data = append(spec.Data, Line{line, text, skip != 0}) + spec.Data = append(spec.Data, Line{line, text, ignore != 0}) break LOOP } text = strings.TrimRight(text, "\r\n") - if isSkipTag(text) { - skip = extractSkipCount(text) - skip++ + switch { + case strings.Contains(text, DIRECTIVE_IGNORE), + strings.Contains(text, "perfecto:absolve"): + ignore = extractIgnoreCount(text) + ignore++ + case strings.Contains(text, DIRECTIVE_TARGET): + spec.Targets = extractTargets(text) + line++ + continue } - spec.Data = append(spec.Data, Line{line, text, skip != 0}) + spec.Data = append(spec.Data, Line{line, text, ignore != 0}) - if skip != 0 { - skip-- + if ignore != 0 { + ignore-- } line++ } if !isSpec(spec) { - return nil, fmt.Errorf("File %s is not a spec file", file) + return nil, fmt.Errorf("File %s is not a spec file or it is misformatted", file) } return spec, nil } -// checkFile checks file for errors -func checkFile(file string) error { - if !fsutil.IsExist(file) { - return fmt.Errorf("File %s doesn't exist", file) - } - - if !fsutil.IsRegular(file) { - return fmt.Errorf("%s isn't a regular file", file) - } - - if !fsutil.IsReadable(file) { - return fmt.Errorf("File %s isn't readable", file) - } - - if !fsutil.IsNonEmpty(file) { - return fmt.Errorf("File %s is empty", file) - } - - return nil -} - // hasSection returns true if spec contains given section func hasSection(s *Spec, section string) bool { for _, line := range s.Data { @@ -372,10 +363,6 @@ func extractSources(s *Spec) []Line { var result []Line for _, line := range s.Data { - if line.Skip { - continue - } - if isSectionHeader(line.Text) { break } @@ -473,14 +460,21 @@ func isSpec(spec *Spec) bool { return count >= 3 } -// isSkipTag returns true if text contains skip tag -func isSkipTag(text string) bool { - return strings.Contains(text, "perfecto:absolve") || - strings.Contains(text, "perfecto:ignore") +// getSectionRegexp creates new regex struct and cache it +func getSectionRegexp(section string) *regexp.Regexp { + _, exist := regexpCache[section] + + if exist { + return regexpCache[section] + } + + regexpCache[section] = regexp.MustCompile(`^%` + section + `($| )`) + + return regexpCache[section] } -// extractSkipCount returns number of lines to skip -func extractSkipCount(text string) int { +// extractIgnoreCount extracts number of lines to ignore from directive +func extractIgnoreCount(text string) int { count := strutil.ReadField(text, 2, true) if count == "" { @@ -496,15 +490,19 @@ func extractSkipCount(text string) int { return countInt } -// getSectionRegexp creates new regex struct and cache it -func getSectionRegexp(section string) *regexp.Regexp { - _, exist := regexpCache[section] +// extractTargets extracts targets from directive +func extractTargets(text string) []string { + var result []string - if exist { - return regexpCache[section] - } + for i := 2; i < 32; i++ { + target := strutil.ReadField(text, i, true, " ", ",") - regexpCache[section] = regexp.MustCompile(`^%` + section + `($| )`) + if target == "" { + break + } - return regexpCache[section] + result = append(result, strings.ToLower(target)) + } + + return result } diff --git a/spec/spec_test.go b/spec/spec_test.go index be7884c..f0d0427 100644 --- a/spec/spec_test.go +++ b/spec/spec_test.go @@ -8,7 +8,7 @@ package spec // ////////////////////////////////////////////////////////////////////////////////// // import ( - "io/ioutil" + "os" "testing" . "github.com/essentialkaos/check" @@ -31,12 +31,8 @@ func (s *SpecSuite) TestFileCheck(c *C) { tmpFile1 := tmpDir + "test1.spec" tmpFile2 := tmpDir + "test2.spec" - ioutil.WriteFile(tmpFile1, []byte(""), 0644) - ioutil.WriteFile(tmpFile2, []byte("TEST"), 0200) - - c.Assert(checkFile(tmpDir), NotNil) - c.Assert(checkFile(tmpFile1), NotNil) - c.Assert(checkFile(tmpFile2), NotNil) + os.WriteFile(tmpFile1, []byte(""), 0644) + os.WriteFile(tmpFile2, []byte("TEST"), 0200) } func (s *SpecSuite) TestParsing(c *C) { @@ -62,7 +58,7 @@ func (s *SpecSuite) TestParsing(c *C) { c.Assert(spec.GetLine(-1), DeepEquals, Line{-1, "", false}) c.Assert(spec.GetLine(99), DeepEquals, Line{-1, "", false}) - c.Assert(spec.GetLine(43), DeepEquals, Line{43, "%{__make} %{?_smp_mflags}", false}) + c.Assert(spec.GetLine(44), DeepEquals, Line{44, "%{__make} %{?_smp_mflags}", false}) } func (s *SpecSuite) TestSections(c *C) { @@ -90,6 +86,8 @@ func (s *SpecSuite) TestSections(c *C) { c.Assert(sections, HasLen, 2) c.Assert(sections[1].GetPackageName(), Equals, "magic") + c.Assert(spec.Targets, DeepEquals, []string{"ubuntu", "el8", "el9", "@rhel"}) + spec, err = Read("../testdata/test_12.spec") c.Assert(err, IsNil) @@ -113,7 +111,7 @@ func (s *SpecSuite) TestHeaders(c *C) { c.Assert(headers, HasLen, 2) c.Assert(headers[0].Package, Equals, "") c.Assert(headers[0].IsSubpackage, Equals, false) - c.Assert(headers[0].Data, HasLen, 16) + c.Assert(headers[0].Data, HasLen, 13) c.Assert(headers[1].Package, Equals, "magic") c.Assert(headers[1].IsSubpackage, Equals, true) c.Assert(headers[1].Data, HasLen, 4) @@ -137,16 +135,28 @@ func (s *SpecSuite) TestSourceExtractor(c *C) { c.Assert(sources, HasLen, 1) } -func (s *SpecSuite) TestSkipTag(c *C) { - c.Assert(isSkipTag("# perfecto:ignore 3"), Equals, true) - c.Assert(isSkipTag("# perfecto:absolve 3"), Equals, true) - c.Assert(isSkipTag("# abcd 1"), Equals, false) +func (s *SpecSuite) TestIgnoreDirective(c *C) { + spec, err := Read("../testdata/test_18.spec") + + c.Assert(err, IsNil) + c.Assert(spec, NotNil) + + c.Assert(spec.Targets, DeepEquals, []string{"mysuppaos"}) + c.Assert(spec.Data[22].Ignore, Equals, true) + + c.Assert(extractIgnoreCount("# perfecto:ignore"), Equals, 1) + c.Assert(extractIgnoreCount("# perfecto:ignore ABC"), Equals, 0) + c.Assert(extractIgnoreCount("# perfecto:ignore 1"), Equals, 1) + c.Assert(extractIgnoreCount("# perfecto:ignore 10"), Equals, 10) + c.Assert(extractIgnoreCount("# perfecto:absolve 10"), Equals, 10) +} - c.Assert(extractSkipCount("# perfecto:ignore"), Equals, 1) - c.Assert(extractSkipCount("# perfecto:ignore ABC"), Equals, 0) - c.Assert(extractSkipCount("# perfecto:ignore 1"), Equals, 1) - c.Assert(extractSkipCount("# perfecto:ignore 10"), Equals, 10) - c.Assert(extractSkipCount("# perfecto:absolve 10"), Equals, 10) +func (s *SpecSuite) TestTargetDirective(c *C) { + c.Assert(extractTargets("# perfecto:target"), IsNil) + c.Assert(extractTargets("# perfecto:target el7"), DeepEquals, []string{"el7"}) + c.Assert(extractTargets("# perfecto:target EL8"), DeepEquals, []string{"el8"}) + c.Assert(extractTargets("# perfecto:target EL8,EL9"), DeepEquals, []string{"el8", "el9"}) + c.Assert(extractTargets("# perfecto:target EL7 EL8"), DeepEquals, []string{"el7", "el8"}) } func (s *SpecSuite) TestSectionPackageParsing(c *C) { diff --git a/testdata/test.spec b/testdata/test.spec index 0736d2f..b30784f 100644 --- a/testdata/test.spec +++ b/testdata/test.spec @@ -1,5 +1,9 @@ ################################################################################ +# perfecto:target ubuntu el8 el9 @rhel + +################################################################################ + %{!?_without_check: %define _with_check 1} ################################################################################ @@ -16,9 +20,6 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} - Source0: https://source.kaos.st/perfecto/%{name}-%{version}.tar.gz -# perfecto:ignore -Source1: http://source.kaos.st/perfecto/%{name}-%{version}.tar.gz - ################################################################################ %description diff --git a/testdata/test_18.spec b/testdata/test_18.spec new file mode 100644 index 0000000..962e484 --- /dev/null +++ b/testdata/test_18.spec @@ -0,0 +1,85 @@ +################################################################################ + +# perfecto:target mysuppaos + +################################################################################ + +%{!?_without_check: %define _with_check 1} + +################################################################################ + +Summary: Test spec for perfecto +Name: perfecto +Version: 1.0.0 +Release: 0%{?dist} +Group: System Environment/Base +License: MIT +URL: https://domain.com + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +Source0: https://source.kaos.st/perfecto/%{name}-%{version}.tar.gz + +# perfecto:ignore +Source1: http://source.kaos.st/perfecto/%{name}-%{version}.tar.gz + +################################################################################ + +%description +Test spec for perfecto app. + +################################################################################ + +%package magic + +Summary: Test subpackage for perfecto +Group: System Environment/Base + +%description magic +Test subpackage for perfecto app. + +################################################################################ + +%prep +%setup -qn %{name}-%{version} + +%build +%{__make} %{?_smp_mflags} + +%install +rm -rf %{buildroot} + +%{make_install} PREFIX=%{buildroot}%{_prefix} + +%clean +rm -rf %{buildroot} + +%check +%if %{?_with_check:1}%{?_without_check:0} +%{make} check +%endif + +%post +%{__chkconfig} --add %{name} &>/dev/null || : + +%preun +%{__chkconfig} --del %{name} &> /dev/null || : + +%postun +%{__chkconfig} --del %{name} &> /dev/null || : + +################################################################################ + +%files +%defattr(-,root,root,-) +%{_bindir}/%{name} + +%files magic +%defattr(-,root,root,-) +%{_bindir}/%{name}-magic + +################################################################################ + +%changelog +* Wed Jan 24 2018 Anton Novojilov - 1.0.0-0 +- Test changelog record