Support result formatting

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2023-09-01 12:08:49 +03:00
parent 8ae5fa912b
commit ca943e1dc8
8 changed files with 456 additions and 225 deletions

View file

@ -19,5 +19,5 @@ $ ./bin/s3-tests-parser compatibility tests/fixture/suite.csv
You can also use json You can also use json
```shell ```shell
$ ./bin/s3-tests-parser compatibility tests/fixture/suite.json --format json $ ./bin/s3-tests-parser compatibility tests/fixture/suite.json --format json --output-format md --output result.md
``` ```

View file

@ -1,56 +1,65 @@
package modules package modules
import ( import (
"bytes"
_ "embed" _ "embed"
"encoding/csv"
"encoding/json"
"fmt" "fmt"
"os" "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/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
//go:embed resources/tests-struct.json
var testStructData []byte
var compatibilityCmd = &cobra.Command{ var compatibilityCmd = &cobra.Command{
Use: "compatibility", Use: "compatibility",
Short: "Shows compatibility results", Short: "Shows compatibility results",
Long: "Form compatibility table based on passed s3 tests", Long: "Form compatibility table based on passed s3 tests",
Example: `s3-tests-parser compatibility suite.csv 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, RunE: runCompatibilityCmd,
} }
type ( type (
TestsStructure struct { Results struct {
Groups []Group `json:"groups"` Legend []Status
TagGroups []TagGroup
} }
Group struct { TagGroup struct {
Name string `json:"name"` Name string
Tag string `json:"tag"` Tests []TestResult
Skip bool `json:"skip"`
SkipReason string `json:"skip_reason"`
Tests []string `json:"tests"`
} }
)
var ( Status struct {
Reset = "\033[0m" Color string
Red = "\033[31m" Description string
Green = "\033[32m" }
Yellow = "\033[33m"
TestResult struct {
Color string
Name string
Comment string
Passed int
Total int
}
) )
const ( const (
formatFlag = "format" formatFlag = "format"
outputFlag = "output"
outputFormatFlag = "output-format"
) )
func initCompatibilityCmd() { func initCompatibilityCmd() {
compatibilityCmd.Flags().String(formatFlag, "csv", "format of input test suite file") 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 { 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) return fmt.Errorf("expected exactly one arg, got: %v", args)
} }
var testStruct TestsStructure testStruct, err := s3.ParseTestsStruct()
if err := json.Unmarshal(testStructData, &testStruct); err != nil { if err != nil {
return fmt.Errorf("failed to parse tests struct: %w", err) return err
} }
testsMap, err := parseSuite(args[0], viper.GetString(formatFlag)) testsMap, err := parser.ParseSuite(args[0], viper.GetString(formatFlag))
if err != nil { if err != nil {
return fmt.Errorf("parse tests: %w", err) return fmt.Errorf("parse tests: %w", err)
} }
res := formResults(testStruct, testsMap)
return printResults(cmd, res)
}
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 { for _, group := range testStruct.Groups {
if group.Skip { tagGroup, ok := tagGroups[group.Tag]
cmd.Println(fmt.Sprintf("%s; skip: %s", group.Name, group.SkipReason)) if !ok {
continue tagGroup.Name = group.Tag
} }
printResult(cmd, group, testsMap) tagGroup.Tests = append(tagGroup.Tests, formTestResult(group, testsMap))
tagGroups[group.Tag] = tagGroup
} }
return nil res := Results{Legend: legend}
for _, group := range tagGroups {
res.TagGroups = append(res.TagGroups, group)
} }
func printResult(cmd *cobra.Command, group Group, testsMap map[string]bool) { sort.Slice(res.TagGroups, func(i, j int) bool {
ln := float64(len(group.Tests)) return res.TagGroups[i].Name < res.TagGroups[j].Name
pass := 0.0 })
return res
}
func formTestResult(group s3.Group, testsMap map[string]bool) TestResult {
ln := len(group.Tests)
pass := 0
for _, test := range group.Tests { for _, test := range group.Tests {
if testsMap[test] { if testsMap[test] {
@ -90,95 +138,47 @@ func printResult(cmd *cobra.Command, group Group, testsMap map[string]bool) {
} }
} }
color := Red var color string
rate := pass / ln if strings.Contains(group.Comment, "Not supported yet") {
color = templates.BlueColor
} else if strings.Contains(group.Comment, "Not applicable") {
color = templates.BlackColor
}
if color == "" {
color = templates.RedColor
rate := float64(pass) / float64(ln)
if rate > 0.9 { if rate > 0.9 {
color = Green color = templates.GreenColor
} else if rate > 0.5 { } else if rate > 0.5 {
color = Yellow color = templates.YellowColor
}
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) { return TestResult{
data, err := os.ReadFile(filePath) Color: color,
Name: group.Name,
Comment: group.Comment,
Passed: pass,
Total: ln,
}
}
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 { if err != nil {
return nil, fmt.Errorf("failed to read suite file: %w", err) return fmt.Errorf("create out file: %w", err)
}
w = f
defer f.Close()
} }
rr := csv.NewReader(bytes.NewReader(data)) outTemplate, err := templates.GetTemplate(viper.GetString(outputFormatFlag))
records, err := rr.ReadAll()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse suite file: %w", err) return fmt.Errorf("form out template: %w", err)
} }
indexName := -1 return outTemplate.Execute(w, res)
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)
}
} }

91
internal/parser/parser.go Normal file
View file

@ -0,0 +1,91 @@
package parser
import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"os"
)
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)
}
}

View file

@ -11,7 +11,7 @@
"test_list_multipart_upload_owner" "test_list_multipart_upload_owner"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "CompleteMultipartUpload", "name": "CompleteMultipartUpload",
@ -40,7 +40,7 @@
"test_multipart_copy_multiple_sizes" "test_multipart_copy_multiple_sizes"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "CopyObject", "name": "CopyObject",
@ -72,7 +72,7 @@
"test_object_copy_key_not_found" "test_object_copy_key_not_found"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "CreateBucket", "name": "CreateBucket",
@ -130,7 +130,7 @@
"test_buckets_list_ctime" "test_buckets_list_ctime"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "CreateMultipartUpload", "name": "CreateMultipartUpload",
@ -175,7 +175,7 @@
"test_sse_s3_default_multipart_upload" "test_sse_s3_default_multipart_upload"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "DeleteBucket", "name": "DeleteBucket",
@ -190,14 +190,14 @@
"test_atomic_write_bucket_gone" "test_atomic_write_bucket_gone"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "DeleteBucketAnalyticsConfiguration", "name": "DeleteBucketAnalyticsConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketCors", "name": "DeleteBucketCors",
@ -206,7 +206,7 @@
"test_set_cors" "test_set_cors"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "DeleteBucketEncryption", "name": "DeleteBucketEncryption",
@ -216,42 +216,42 @@
"test_delete_bucket_encryption_kms" "test_delete_bucket_encryption_kms"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketIntelligentTieringConfiguration", "name": "DeleteBucketIntelligentTieringConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketInventoryConfiguration", "name": "DeleteBucketInventoryConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketLifecycle", "name": "DeleteBucketLifecycle",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketMetricsConfiguration", "name": "DeleteBucketMetricsConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketOwnershipControls", "name": "DeleteBucketOwnershipControls",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketPolicy", "name": "DeleteBucketPolicy",
@ -261,14 +261,14 @@
"test_bucketv2_policy_acl" "test_bucketv2_policy_acl"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketReplication", "name": "DeleteBucketReplication",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteBucketTagging", "name": "DeleteBucketTagging",
@ -277,14 +277,14 @@
"test_set_bucket_tagging" "test_set_bucket_tagging"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "DeleteBucketWebsite", "name": "DeleteBucketWebsite",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "DeleteObject", "name": "DeleteObject",
@ -326,7 +326,7 @@
"test_object_lock_uploading_obj" "test_object_lock_uploading_obj"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "DeleteObjects", "name": "DeleteObjects",
@ -344,7 +344,7 @@
"test_versioning_concurrent_multi_object_delete" "test_versioning_concurrent_multi_object_delete"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "DeleteObjectTagging", "name": "DeleteObjectTagging",
@ -354,21 +354,21 @@
"test_delete_tags_obj_public" "test_delete_tags_obj_public"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "DeletePublicAccessBlock", "name": "DeletePublicAccessBlock",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketAccelerateConfiguration", "name": "GetBucketAccelerateConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not applicable or will never be supported" "comment": "Not applicable or will never be supported"
}, },
{ {
"name": "GetBucketAcl", "name": "GetBucketAcl",
@ -395,14 +395,14 @@
"test_bucket_acl_grant_userid_writeacp" "test_bucket_acl_grant_userid_writeacp"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetBucketAnalyticsConfiguration", "name": "GetBucketAnalyticsConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketCors", "name": "GetBucketCors",
@ -414,7 +414,7 @@
"test_cors_header_option" "test_cors_header_option"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetBucketEncryption", "name": "GetBucketEncryption",
@ -424,28 +424,28 @@
"test_get_bucket_encryption_kms" "test_get_bucket_encryption_kms"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketIntelligentTieringConfiguration", "name": "GetBucketIntelligentTieringConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketInventoryConfiguration", "name": "GetBucketInventoryConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketLifecycle", "name": "GetBucketLifecycle",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketLifecycleConfiguration", "name": "GetBucketLifecycleConfiguration",
@ -455,7 +455,7 @@
"test_lifecycle_get_no_id" "test_lifecycle_get_no_id"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketLocation", "name": "GetBucketLocation",
@ -464,7 +464,7 @@
"test_bucket_get_location" "test_bucket_get_location"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetBucketLogging", "name": "GetBucketLogging",
@ -473,35 +473,35 @@
"test_logging_toggle" "test_logging_toggle"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketMetricsConfiguration", "name": "GetBucketMetricsConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketNotification", "name": "GetBucketNotification",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketNotificationConfiguration", "name": "GetBucketNotificationConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketOwnershipControls", "name": "GetBucketOwnershipControls",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketPolicy", "name": "GetBucketPolicy",
@ -512,7 +512,7 @@
"test_bucket_policy_set_condition_operator_end_with_IfExists" "test_bucket_policy_set_condition_operator_end_with_IfExists"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetBucketPolicyStatus", "name": "GetBucketPolicyStatus",
@ -526,21 +526,21 @@
"test_get_nonpublicpolicy_deny_bucket_policy_status" "test_get_nonpublicpolicy_deny_bucket_policy_status"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketReplication", "name": "GetBucketReplication",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetBucketRequestPayment", "name": "GetBucketRequestPayment",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not applicable or will never be supported" "comment": "Not applicable or will never be supported"
}, },
{ {
"name": "GetBucketTagging", "name": "GetBucketTagging",
@ -549,7 +549,7 @@
"test_set_bucket_tagging" "test_set_bucket_tagging"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetBucketVersioning", "name": "GetBucketVersioning",
@ -563,14 +563,14 @@
"test_versioning_obj_plain_null_version_overwrite_suspended" "test_versioning_obj_plain_null_version_overwrite_suspended"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetBucketWebsite", "name": "GetBucketWebsite",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "GetObject", "name": "GetObject",
@ -634,7 +634,7 @@
"test_bucket_create_special_key_names" "test_bucket_create_special_key_names"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetObjectAcl", "name": "GetObjectAcl",
@ -664,14 +664,14 @@
"test_bucket_policy_get_obj_acl_existing_tag" "test_bucket_policy_get_obj_acl_existing_tag"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetObjectAttributes", "name": "GetObjectAttributes",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetObjectLegalHold", "name": "GetObjectLegalHold",
@ -681,7 +681,7 @@
"test_object_lock_get_legal_hold_invalid_bucket" "test_object_lock_get_legal_hold_invalid_bucket"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetObjectLockConfiguration", "name": "GetObjectLockConfiguration",
@ -691,7 +691,7 @@
"test_object_lock_get_obj_lock_invalid_bucket" "test_object_lock_get_obj_lock_invalid_bucket"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetObjectRetention", "name": "GetObjectRetention",
@ -705,7 +705,7 @@
"test_object_lock_put_obj_retention_shorten_period_bypass" "test_object_lock_put_obj_retention_shorten_period_bypass"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetObjectTagging", "name": "GetObjectTagging",
@ -728,7 +728,7 @@
"test_bucket_policy_get_obj_acl_existing_tag" "test_bucket_policy_get_obj_acl_existing_tag"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "GetObjectTorrent", "name": "GetObjectTorrent",
@ -737,7 +737,7 @@
"test_get_object_torrent" "test_get_object_torrent"
], ],
"skip": true, "skip": true,
"skip_reason": "Not applicable or will never be supported" "comment": "Not applicable or will never be supported"
}, },
{ {
"name": "GetPublicAccessBlock", "name": "GetPublicAccessBlock",
@ -746,7 +746,7 @@
"test_get_default_public_block" "test_get_default_public_block"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "HeadBucket", "name": "HeadBucket",
@ -758,7 +758,7 @@
"test_bucket_head_extended" "test_bucket_head_extended"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "HeadObject", "name": "HeadObject",
@ -782,35 +782,35 @@
"test_sse_s3_default_method_head" "test_sse_s3_default_method_head"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "ListBucketAnalyticsConfigurations", "name": "ListBucketAnalyticsConfigurations",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "ListBucketIntelligentTieringConfigurations", "name": "ListBucketIntelligentTieringConfigurations",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "ListBucketInventoryConfigurations", "name": "ListBucketInventoryConfigurations",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "ListBucketMetricsConfigurations", "name": "ListBucketMetricsConfigurations",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "ListBuckets", "name": "ListBuckets",
@ -824,7 +824,7 @@
"test_list_buckets_bad_auth" "test_list_buckets_bad_auth"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "ListMultipartUploads", "name": "ListMultipartUploads",
@ -835,7 +835,7 @@
"test_lifecycle_multipart_expiration" "test_lifecycle_multipart_expiration"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "ListObjects", "name": "ListObjects",
@ -896,7 +896,7 @@
"test_bucket_list_delimiter_prefix_underscore" "test_bucket_list_delimiter_prefix_underscore"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "ListObjectsV2", "name": "ListObjectsV2",
@ -963,7 +963,7 @@
"test_sse_s3_default_multipart_upload" "test_sse_s3_default_multipart_upload"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "ListObjectVersions", "name": "ListObjectVersions",
@ -992,21 +992,21 @@
"test_versioning_bucket_multipart_upload_return_version_id" "test_versioning_bucket_multipart_upload_return_version_id"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "ListParts", "name": "ListParts",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutBucketAccelerateConfiguration", "name": "PutBucketAccelerateConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not applicable or will never be supported" "comment": "Not applicable or will never be supported"
}, },
{ {
"name": "PutBucketAcl", "name": "PutBucketAcl",
@ -1052,14 +1052,14 @@
"test_access_bucket_publicreadwrite_object_publicreadwrite" "test_access_bucket_publicreadwrite_object_publicreadwrite"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutBucketAnalyticsConfiguration", "name": "PutBucketAnalyticsConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketCors", "name": "PutBucketCors",
@ -1073,7 +1073,7 @@
"test_cors_presigned_get_object_tenant" "test_cors_presigned_get_object_tenant"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutBucketEncryption", "name": "PutBucketEncryption",
@ -1095,28 +1095,28 @@
"test_sse_kms_default_upload_8mb" "test_sse_kms_default_upload_8mb"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketIntelligentTieringConfiguration", "name": "PutBucketIntelligentTieringConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketInventoryConfiguration", "name": "PutBucketInventoryConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketLifecycle", "name": "PutBucketLifecycle",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketLifecycleConfiguration", "name": "PutBucketLifecycleConfiguration",
@ -1160,7 +1160,7 @@
"test_lifecycle_cloud_transition_large_obj" "test_lifecycle_cloud_transition_large_obj"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketLogging", "name": "PutBucketLogging",
@ -1169,35 +1169,35 @@
"test_logging_toggle" "test_logging_toggle"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketMetricsConfiguration", "name": "PutBucketMetricsConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketNotification", "name": "PutBucketNotification",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketNotificationConfiguration", "name": "PutBucketNotificationConfiguration",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketOwnershipControls", "name": "PutBucketOwnershipControls",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketPolicy", "name": "PutBucketPolicy",
@ -1226,21 +1226,21 @@
"test_multipart_upload_on_a_bucket_with_policy" "test_multipart_upload_on_a_bucket_with_policy"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutBucketReplication", "name": "PutBucketReplication",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutBucketRequestPayment", "name": "PutBucketRequestPayment",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not applicable or will never be supported" "comment": "Not applicable or will never be supported"
}, },
{ {
"name": "PutBucketTagging", "name": "PutBucketTagging",
@ -1249,7 +1249,7 @@
"test_set_bucket_tagging" "test_set_bucket_tagging"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutBucketVersioning", "name": "PutBucketVersioning",
@ -1281,14 +1281,14 @@
"test_versioning_bucket_multipart_upload_return_version_id" "test_versioning_bucket_multipart_upload_return_version_id"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutBucketWebsite", "name": "PutBucketWebsite",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "PutObject", "name": "PutObject",
@ -1340,7 +1340,7 @@
"test_object_acl_canned" "test_object_acl_canned"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutObjectAcl", "name": "PutObjectAcl",
@ -1372,7 +1372,7 @@
"test_access_bucket_publicreadwrite_object_publicreadwrite" "test_access_bucket_publicreadwrite_object_publicreadwrite"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutObjectLegalHold", "name": "PutObjectLegalHold",
@ -1388,7 +1388,7 @@
"test_object_lock_uploading_obj" "test_object_lock_uploading_obj"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutObjectLockConfiguration", "name": "PutObjectLockConfiguration",
@ -1405,7 +1405,7 @@
"test_object_lock_put_obj_retention_override_default_retention" "test_object_lock_put_obj_retention_override_default_retention"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutObjectRetention", "name": "PutObjectRetention",
@ -1430,7 +1430,7 @@
"test_object_lock_changing_mode_from_compliance" "test_object_lock_changing_mode_from_compliance"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutObjectTagging", "name": "PutObjectTagging",
@ -1460,7 +1460,7 @@
"test_bucket_policy_get_obj_acl_existing_tag" "test_bucket_policy_get_obj_acl_existing_tag"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "PutPublicAccessBlock", "name": "PutPublicAccessBlock",
@ -1473,14 +1473,14 @@
"test_ignore_public_acls" "test_ignore_public_acls"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "RestoreObject", "name": "RestoreObject",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "SelectObjectContent", "name": "SelectObjectContent",
@ -1522,7 +1522,7 @@
"test_output_serial_expressions" "test_output_serial_expressions"
], ],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "UploadPart", "name": "UploadPart",
@ -1550,7 +1550,7 @@
"test_list_multipart_upload_owner" "test_list_multipart_upload_owner"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "UploadPartCopy", "name": "UploadPartCopy",
@ -1565,18 +1565,18 @@
"test_multipart_copy_multiple_sizes" "test_multipart_copy_multiple_sizes"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
}, },
{ {
"name": "WriteGetObjectResponse", "name": "WriteGetObjectResponse",
"tag": "API", "tag": "API",
"tests": [], "tests": [],
"skip": true, "skip": true,
"skip_reason": "Not supported yet, but will be in future" "comment": "Not supported yet, but will be in future"
}, },
{ {
"name": "encryption", "name": "encryption",
"tag": "feature", "tag": "Features",
"tests": [ "tests": [
"test_sse_kms_method_head", "test_sse_kms_method_head",
"test_sse_kms_present", "test_sse_kms_present",
@ -1626,11 +1626,11 @@
"test_encryption_sse_c_post_object_authenticated_request" "test_encryption_sse_c_post_object_authenticated_request"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": "Only sse-c currently supported"
}, },
{ {
"name": "post_upload", "name": "post_upload",
"tag": "feature", "tag": "Features",
"tests": [ "tests": [
"test_post_object_anonymous_request", "test_post_object_anonymous_request",
"test_post_object_authenticated_request", "test_post_object_authenticated_request",
@ -1671,11 +1671,11 @@
"test_sse_kms_post_object_authenticated_request" "test_sse_kms_post_object_authenticated_request"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": "Supported only basic POST upload"
}, },
{ {
"name": "aws_v2_signature", "name": "aws_v2_signature",
"tag": "feature", "tag": "Features",
"tests": [ "tests": [
"test_object_create_bad_md5_invalid_garbage_aws2", "test_object_create_bad_md5_invalid_garbage_aws2",
"test_object_create_bad_contentlength_mismatch_below_aws2", "test_object_create_bad_contentlength_mismatch_below_aws2",
@ -1691,11 +1691,11 @@
"test_object_create_bad_date_after_end_aws2" "test_object_create_bad_date_after_end_aws2"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": "AWS v2 signature is deprecated"
}, },
{ {
"name": "locking", "name": "locking",
"tag": "feature", "tag": "Features",
"tests": [ "tests": [
"test_object_lock_put_obj_lock", "test_object_lock_put_obj_lock",
"test_object_lock_put_obj_lock_invalid_bucket", "test_object_lock_put_obj_lock_invalid_bucket",
@ -1728,7 +1728,7 @@
"test_object_lock_get_obj_metadata" "test_object_lock_get_obj_metadata"
], ],
"skip": false, "skip": false,
"skip_reason": "" "comment": ""
} }
] ]
} }

31
internal/s3/structure.go Normal file
View file

@ -0,0 +1,31 @@
package s3
import (
_ "embed"
"encoding/json"
"fmt"
)
//go:embed resources/tests-struct.json
var testStructData []byte
type TestsStructure struct {
Groups []Group `json:"groups"`
}
type Group struct {
Name string `json:"name"`
Tag string `json:"tag"`
Skip bool `json:"skip"`
Comment string `json:"comment"`
Tests []string `json:"tests"`
}
func ParseTestsStruct() (TestsStructure, error) {
var testStruct TestsStructure
if err := json.Unmarshal(testStructData, &testStruct); err != nil {
return TestsStructure{}, fmt.Errorf("failed to parse tests struct: %w", err)
}
return testStruct, nil
}

View file

@ -0,0 +1,17 @@
# S3 Protocol Compatibility
Reference:
* [AWS S3 API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/s3-api.pdf)
| | Legend |
|---|--------|{{range .Legend}}
| {{colorToCircle .Color}} | {{.Description}} |{{end}}
{{range .TagGroups}}
## {{.Name}}
| | Name | Comments |
|---|------|----------|{{range .Tests}}
| {{colorToCircle .Color}} | {{.Name}} | {{.Comment}} |{{end}}
{{end}}

View file

@ -0,0 +1,6 @@
# S3 Protocol Compatibility
{{range .TagGroups}}
## {{.Name}}
{{range .Tests}}
{{colorToTerminal .Color}}{{.Name}}: {{.Passed}}/{{.Total}}; {{.Comment}} {{colorToTerminal "black"}} {{end}}
{{end}}

View file

@ -0,0 +1,86 @@
package templates
import (
_ "embed"
"fmt"
"text/template"
)
const (
GreenColor = "green"
YellowColor = "yellow"
RedColor = "red"
BlueColor = "blue"
BlackColor = "black"
)
const (
blackTerminal = "\033[0m"
redTerminal = "\033[31m"
greenTerminal = "\033[32m"
yellowTerminal = "\033[33m"
blueTerminal = "\033[34m"
)
const (
greenCircle = "🟢"
yellowCircle = "🟡"
redCircle = "🔴"
blueCircle = "🔵"
blackCircle = "⚫"
)
var (
//go:embed resources/txt-template.gotmpl
txtTemplate []byte
//go:embed resources/md-template.gotmpl
mdTemplate []byte
)
func GetTemplate(outFormat string) (*template.Template, error) {
var templateData []byte
switch outFormat {
case "txt":
templateData = txtTemplate
case "md":
templateData = mdTemplate
default:
return nil, fmt.Errorf("unkonwn output format: %s", outFormat)
}
return template.New("out-format").Funcs(template.FuncMap{
"colorToCircle": ColorToCircle,
"colorToTerminal": ColorToTerminal,
}).Parse(string(templateData))
}
func ColorToCircle(color string) string {
switch color {
case GreenColor:
return greenCircle
case YellowColor:
return yellowCircle
case RedColor:
return redCircle
case BlueColor:
return blueCircle
default:
return blackCircle
}
}
func ColorToTerminal(color string) string {
switch color {
case GreenColor:
return greenTerminal
case YellowColor:
return yellowTerminal
case RedColor:
return redTerminal
case BlueColor:
return blueTerminal
default:
return blackTerminal
}
}