Support result formatting
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
8ae5fa912b
commit
ca943e1dc8
8 changed files with 456 additions and 225 deletions
|
@ -1,56 +1,65 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/s3-tests-parser/internal/parser"
|
||||
"git.frostfs.info/TrueCloudLab/s3-tests-parser/internal/s3"
|
||||
"git.frostfs.info/TrueCloudLab/s3-tests-parser/internal/templates"
|
||||
"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`,
|
||||
s3-tests-parser compatibility suite.json --format json
|
||||
s3-tests-parser compatibility suite.json --format json --output-format md
|
||||
s3-tests-parser compatibility suite.json --format json --output-format md --output result.md`,
|
||||
RunE: runCompatibilityCmd,
|
||||
}
|
||||
|
||||
type (
|
||||
TestsStructure struct {
|
||||
Groups []Group `json:"groups"`
|
||||
Results struct {
|
||||
Legend []Status
|
||||
TagGroups []TagGroup
|
||||
}
|
||||
|
||||
Group struct {
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag"`
|
||||
Skip bool `json:"skip"`
|
||||
SkipReason string `json:"skip_reason"`
|
||||
Tests []string `json:"tests"`
|
||||
TagGroup struct {
|
||||
Name string
|
||||
Tests []TestResult
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
Reset = "\033[0m"
|
||||
Red = "\033[31m"
|
||||
Green = "\033[32m"
|
||||
Yellow = "\033[33m"
|
||||
Status struct {
|
||||
Color string
|
||||
Description string
|
||||
}
|
||||
|
||||
TestResult struct {
|
||||
Color string
|
||||
Name string
|
||||
Comment string
|
||||
Passed int
|
||||
Total int
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
formatFlag = "format"
|
||||
formatFlag = "format"
|
||||
outputFlag = "output"
|
||||
outputFormatFlag = "output-format"
|
||||
)
|
||||
|
||||
func initCompatibilityCmd() {
|
||||
compatibilityCmd.Flags().String(formatFlag, "csv", "format of input test suite file")
|
||||
compatibilityCmd.Flags().String(outputFlag, "", "file to write output, if missed the stdout is used")
|
||||
compatibilityCmd.Flags().String(outputFormatFlag, "txt", "format of output")
|
||||
}
|
||||
|
||||
func runCompatibilityCmd(cmd *cobra.Command, args []string) error {
|
||||
|
@ -58,31 +67,70 @@ func runCompatibilityCmd(cmd *cobra.Command, args []string) error {
|
|||
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)
|
||||
testStruct, err := s3.ParseTestsStruct()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
testsMap, err := parseSuite(args[0], viper.GetString(formatFlag))
|
||||
testsMap, err := parser.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
|
||||
res := formResults(testStruct, testsMap)
|
||||
return printResults(cmd, res)
|
||||
}
|
||||
|
||||
func printResult(cmd *cobra.Command, group Group, testsMap map[string]bool) {
|
||||
ln := float64(len(group.Tests))
|
||||
pass := 0.0
|
||||
var legend = []Status{
|
||||
{
|
||||
Color: templates.GreenColor,
|
||||
Description: "Supported",
|
||||
},
|
||||
{
|
||||
Color: templates.YellowColor,
|
||||
Description: "Partially supported",
|
||||
},
|
||||
{
|
||||
Color: templates.RedColor,
|
||||
Description: "Badly supported",
|
||||
},
|
||||
{
|
||||
Color: templates.BlueColor,
|
||||
Description: "Not supported yet, but will be in future",
|
||||
},
|
||||
{
|
||||
Color: templates.BlackColor,
|
||||
Description: "Not applicable or will never be supported",
|
||||
},
|
||||
}
|
||||
|
||||
func formResults(testStruct s3.TestsStructure, testsMap map[string]bool) Results {
|
||||
tagGroups := make(map[string]TagGroup)
|
||||
for _, group := range testStruct.Groups {
|
||||
tagGroup, ok := tagGroups[group.Tag]
|
||||
if !ok {
|
||||
tagGroup.Name = group.Tag
|
||||
}
|
||||
|
||||
tagGroup.Tests = append(tagGroup.Tests, formTestResult(group, testsMap))
|
||||
tagGroups[group.Tag] = tagGroup
|
||||
}
|
||||
|
||||
res := Results{Legend: legend}
|
||||
for _, group := range tagGroups {
|
||||
res.TagGroups = append(res.TagGroups, group)
|
||||
}
|
||||
|
||||
sort.Slice(res.TagGroups, func(i, j int) bool {
|
||||
return res.TagGroups[i].Name < res.TagGroups[j].Name
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func formTestResult(group s3.Group, testsMap map[string]bool) TestResult {
|
||||
ln := len(group.Tests)
|
||||
pass := 0
|
||||
|
||||
for _, test := range group.Tests {
|
||||
if testsMap[test] {
|
||||
|
@ -90,95 +138,47 @@ func printResult(cmd *cobra.Command, group Group, testsMap map[string]bool) {
|
|||
}
|
||||
}
|
||||
|
||||
color := Red
|
||||
rate := pass / ln
|
||||
if rate > 0.9 {
|
||||
color = Green
|
||||
} else if rate > 0.5 {
|
||||
color = Yellow
|
||||
var color string
|
||||
if strings.Contains(group.Comment, "Not supported yet") {
|
||||
color = templates.BlueColor
|
||||
} else if strings.Contains(group.Comment, "Not applicable") {
|
||||
color = templates.BlackColor
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
if color == "" {
|
||||
color = templates.RedColor
|
||||
rate := float64(pass) / float64(ln)
|
||||
if rate > 0.9 {
|
||||
color = templates.GreenColor
|
||||
} else if rate > 0.5 {
|
||||
color = templates.YellowColor
|
||||
}
|
||||
|
||||
tests[recs[indexName]] = recs[indexStatus] == "passed"
|
||||
}
|
||||
|
||||
return tests, nil
|
||||
return TestResult{
|
||||
Color: color,
|
||||
Name: group.Name,
|
||||
Comment: group.Comment,
|
||||
Passed: pass,
|
||||
Total: ln,
|
||||
}
|
||||
}
|
||||
|
||||
type SuiteNode struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Children []SuiteNode `json:"children"`
|
||||
}
|
||||
func printResults(cmd *cobra.Command, res Results) error {
|
||||
w := cmd.OutOrStdout()
|
||||
if outFile := viper.GetString(outputFlag); outFile != "" {
|
||||
f, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create out file: %w", err)
|
||||
}
|
||||
w = f
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
func parseSuiteJSON(filePath string) (map[string]bool, error) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
outTemplate, err := templates.GetTemplate(viper.GetString(outputFormatFlag))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read suite file: %w", err)
|
||||
return fmt.Errorf("form out template: %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)
|
||||
}
|
||||
return outTemplate.Execute(w, res)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue