package modules import ( "bytes" _ "embed" "encoding/csv" "encoding/json" "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper" ) //go:embed resources/tests-struct.json var testStructData []byte var compatibilityCmd = &cobra.Command{ Use: "compatibility", Short: "Shows compatibility results", Long: "Form compatibility table based on passed s3 tests", Example: `s3-tests-parser compatibility suite.csv s3-tests-parser compatibility suite.json --format json`, RunE: runCompatibilityCmd, } type ( TestsStructure struct { Groups []Group `json:"groups"` } Group struct { Name string `json:"name"` Tag string `json:"tag"` Skip bool `json:"skip"` SkipReason string `json:"skip_reason"` Tests []string `json:"tests"` } ) var ( Reset = "\033[0m" Red = "\033[31m" Green = "\033[32m" Yellow = "\033[33m" ) const ( formatFlag = "format" ) func initCompatibilityCmd() { compatibilityCmd.Flags().String(formatFlag, "csv", "format of input test suite file") } func runCompatibilityCmd(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("expected exactly one arg, got: %v", args) } var testStruct TestsStructure if err := json.Unmarshal(testStructData, &testStruct); err != nil { return fmt.Errorf("failed to parse tests struct: %w", err) } testsMap, err := parseSuite(args[0], viper.GetString(formatFlag)) if err != nil { return fmt.Errorf("parse tests: %w", err) } for _, group := range testStruct.Groups { if group.Skip { cmd.Println(fmt.Sprintf("%s; skip: %s", group.Name, group.SkipReason)) continue } printResult(cmd, group, testsMap) } return nil } func printResult(cmd *cobra.Command, group Group, testsMap map[string]bool) { ln := float64(len(group.Tests)) pass := 0.0 for _, test := range group.Tests { if testsMap[test] { pass++ } } color := Red rate := pass / ln if rate > 0.9 { color = Green } else if rate > 0.5 { color = Yellow } cmd.Println(fmt.Sprintf("%s%s: %d/%d \u001B[0m", color, group.Name, int(pass), int(ln))) } func parseSuite(filePath string, format string) (map[string]bool, error) { switch format { case "csv": return parseSuiteCSV(filePath) case "json": return parseSuiteJSON(filePath) default: return nil, fmt.Errorf("unknown format: %s", format) } } func parseSuiteCSV(filePath string) (map[string]bool, error) { data, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read suite file: %w", err) } rr := csv.NewReader(bytes.NewReader(data)) records, err := rr.ReadAll() if err != nil { return nil, fmt.Errorf("failed to parse suite file: %w", err) } indexName := -1 indexStatus := -1 tests := make(map[string]bool) for i, recs := range records { if i == 0 { for j, rec := range recs { if rec == "Name" { indexName = j } else if rec == "Status" { indexStatus = j } } if indexName == -1 || indexStatus == -1 { return nil, fmt.Errorf("invalid csv format, couldn't find 'Name' and 'Status' fields") } } tests[recs[indexName]] = recs[indexStatus] == "passed" } return tests, nil } type SuiteNode struct { Name string `json:"name"` Status string `json:"status"` Children []SuiteNode `json:"children"` } func parseSuiteJSON(filePath string) (map[string]bool, error) { data, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read suite file: %w", err) } var suiteNode SuiteNode if err = json.Unmarshal(data, &suiteNode); err != nil { return nil, fmt.Errorf("failed to parse suite file: %w", err) } tests := make(map[string]bool) parseSuiteNode(suiteNode, tests) return tests, nil } func parseSuiteNode(node SuiteNode, res map[string]bool) { if node.Status != "" { res[node.Name] = node.Status == "passed" return } for _, child := range node.Children { parseSuiteNode(child, res) } }