cts: fix generated test names in get-test-plan tool

testnames: sectionX_ruleY
fix init value

Bug: tint:1159 tint:1158
Change-Id: Icc92668ee141b2631d9705f41a5155d6483f9713
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/64041
Reviewed-by: Sarah Mashayekhi <sarahmashay@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Sarah Mashayekhi <sarahmashay@google.com>
This commit is contained in:
Sarah 2021-09-13 23:26:00 +00:00 committed by Tint LUCI CQ
parent acaecab29d
commit 7f4d02c7ab
1 changed files with 100 additions and 134 deletions

View File

@ -48,15 +48,14 @@ import (
const ( const (
toolName = "get-test-plan" toolName = "get-test-plan"
specPath = "https://www.w3.org/TR/WGSL/" specPath = "https://www.w3.org/TR/WGSL/"
specVersionUsedtoDevelopmentThisTool = "https://www.w3.org/TR/2021/WD-WGSL-20210831" specVersionUsed = "https://www.w3.org/TR/2021/WD-WGSL-20210910/"
) )
var ( var (
errInvalidArg = errors.New("invalid arguments") errInvalidArg = errors.New("invalid arguments")
finalSpecURL = "" headURL = specVersionUsed
markedNodesSet = make(map[*html.Node]bool) markedNodesSet = make(map[*html.Node]bool)
testNamesSet = make(map[string]bool) testNamesSet = make(map[string]bool)
visitedBuiltinsSet = make(map[string]int)
sha1sSet = make(map[string]bool) sha1sSet = make(map[string]bool)
keywords = []string{ keywords = []string{
"MUST ", "MUST NOT ", "REQUIRED ", "SHALL ", "MUST ", "MUST NOT ", "REQUIRED ", "SHALL ",
@ -64,8 +63,8 @@ var (
"RECOMMENDED ", "MAY ", "OPTIONAL ", "RECOMMENDED ", "MAY ", "OPTIONAL ",
} }
globalSection = "" globalSection = ""
globalPrevSection = "" globalPrevSectionX = -1
globalCounter = 0 globalRuleCounter = 0
) )
// Holds all the information about a wgsl rule // Holds all the information about a wgsl rule
@ -92,7 +91,7 @@ func main() {
fmt.Fprintf(out, "spec is an optional local file or a URL to the WGSL specification.\n") fmt.Fprintf(out, "spec is an optional local file or a URL to the WGSL specification.\n")
fmt.Fprintf(out, "If spec is omitted then the specification is fetched from %v\n\n", specPath) fmt.Fprintf(out, "If spec is omitted then the specification is fetched from %v\n\n", specPath)
fmt.Fprintf(out, "this tools is developed based on: %v\n", specVersionUsedtoDevelopmentThisTool) fmt.Fprintf(out, "this tools is developed based on: %v\n", specVersionUsed)
fmt.Fprintf(out, "flags may be any combination of:\n") fmt.Fprintf(out, "flags may be any combination of:\n")
flag.PrintDefaults() flag.PrintDefaults()
} }
@ -152,8 +151,8 @@ if omitted, a human readable version of the rules is written to stdout`)
if *ctsDir != "" { if *ctsDir != "" {
getUnimplementedTestPlan(*parser, *ctsDir) getUnimplementedTestPlan(*parser, *ctsDir)
} }
txt, tsv := concatRules(rules)
txt, tsv := concatRules(rules)
// if no output then write rules to stdout // if no output then write rules to stdout
if *output == "" { if *output == "" {
fmt.Println(txt) fmt.Println(txt)
@ -179,8 +178,8 @@ if omitted, a human readable version of the rules is written to stdout`)
// example: section = [x, y, z] ie. x.y.z(.w)* it returns (start = min(w),end = max(w)) // example: section = [x, y, z] ie. x.y.z(.w)* it returns (start = min(w),end = max(w))
// if there are no rules extracted from x.y.z it returns (-1, -1) // if there are no rules extracted from x.y.z it returns (-1, -1)
func getSectionRange(rules []rule, s []int) (start, end int, err error) { func getSectionRange(rules []rule, s []int) (start, end int, err error) {
start = 1 start = -1
end = 1 end = -1
for _, r := range rules { for _, r := range rules {
sectionDims, err := parseSection(r.SubSection) sectionDims, err := parseSection(r.SubSection)
if err != nil { if err != nil {
@ -250,10 +249,10 @@ func concatRules(rules []rule) (string, string) {
"Rule Number " + strconv.Itoa(r.Number) + ":", "Rule Number " + strconv.Itoa(r.Number) + ":",
"Unique Id: " + r.Sha, "Unique Id: " + r.Sha,
"Section: " + r.SubSection, "Section: " + r.SubSection,
"URL: " + r.URL,
"Keyword: " + r.Keyword, "Keyword: " + r.Keyword,
"testName: " + r.TestName, "testName: " + r.TestName,
"Description: " + r.Description, "URL: " + r.URL,
r.Description,
"---------------------------------------------------"}, "\n")) "---------------------------------------------------"}, "\n"))
tsvLines = append(tsvLines, strings.Join([]string{ tsvLines = append(tsvLines, strings.Join([]string{
@ -282,31 +281,6 @@ func writeFile(path, content string) error {
return nil return nil
} }
// getBetween returns all the substrings of 'in' that are between 'begins[i]' and 'end'
// in other words for input of ".*[begin][middle][end].*"
// output will be []"cleanUpString([begin][middle][end])"
// example:
// in: `T is f32 or vecN<f32>
// clamp(e1: T ,e2: T ,e3: T) -> T
// Returns min(max(e1,e2),e3). Component-wise when T is a vector.
// (GLSLstd450NClamp)`
// begins: []string{"\n", ""}
// end: "("
// middles: "clamp(", "Returns min(max(", "(", "clamp(", "Returns min(max(", "("
func getBetween(in string, begins []string, end string) []string {
middles := []string{}
for _, right := range begins {
re := regexp.MustCompile(regexp.QuoteMeta(right) +
`.*` +
regexp.QuoteMeta(end) + `|$^`)
for _, m := range re.FindAllString(in, -1) {
middles = append(middles, cleanUpString(m))
}
}
return middles
}
// parseSpec reads the spec from a local file, or the URL to WGSL spec // parseSpec reads the spec from a local file, or the URL to WGSL spec
func parseSpec(args []string) (*html.Node, error) { func parseSpec(args []string) (*html.Node, error) {
// Check for explicit WGSL spec path // Check for explicit WGSL spec path
@ -380,7 +354,6 @@ func parseSpec(args []string) (*html.Node, error) {
return nil, fmt.Errorf("unsupported URL scheme: %v", specURL.Scheme) return nil, fmt.Errorf("unsupported URL scheme: %v", specURL.Scheme)
} }
defer specContent.Close() defer specContent.Close()
finalSpecURL = specURL.String()
// Parse spec // Parse spec
spec, err := html.Parse(specContent) spec, err := html.Parse(specContent)
@ -486,19 +459,14 @@ func (p *Parser) getKeywordRule(node *html.Node, section int, subSection string)
} }
id := getID(node) id := getID(node)
url := finalSpecURL + "#" + id
desc := cleanUpString(getNodeData(node)) desc := cleanUpString(getNodeData(node))
title := "" t, _, err := testName(id, desc, subSection)
if index := strings.Index(desc, "."); index > -1 {
title = desc[0:index]
}
t, _, err := testName(id, desc, title, subSection)
if err != nil { if err != nil {
return err return err
} }
sha, err := getSha1(desc, subSection) sha, err := getSha1(desc, id)
if err != nil { if err != nil {
return err return err
} }
@ -508,7 +476,7 @@ func (p *Parser) getKeywordRule(node *html.Node, section int, subSection string)
Number: len(p.rules) + 1, Number: len(p.rules) + 1,
Section: section, Section: section,
SubSection: subSection, SubSection: subSection,
URL: url, URL: headURL + "#" + id,
Description: desc, Description: desc,
TestName: t, TestName: t,
Keyword: keyword, Keyword: keyword,
@ -546,14 +514,14 @@ func (p *Parser) getAlgorithmRule(node *html.Node, section int, subSection strin
sb := strings.Builder{} sb := strings.Builder{}
printNodeText(node, &sb) printNodeText(node, &sb)
title := cleanUpStartEnd(getNodeAttrValue(node, "data-algorithm")) title := cleanUpStartEnd(getNodeAttrValue(node, "data-algorithm"))
desc := title + ":\n" + cleanUpStartEnd(sb.String()) desc := title + ":\n" + cleanUpString(sb.String())
id := getID(node) id := getID(node)
testName, builtinName, err := testName(id, desc, title, subSection) testName, _, err := testName(id, desc, subSection)
if err != nil { if err != nil {
return err return err
} }
sha, err := getSha1(desc, "") sha, err := getSha1(desc, id)
if err != nil { if err != nil {
return err return err
} }
@ -563,27 +531,11 @@ func (p *Parser) getAlgorithmRule(node *html.Node, section int, subSection strin
Number: len(p.rules) + 1, Number: len(p.rules) + 1,
Section: section, Section: section,
SubSection: subSection, SubSection: subSection,
URL: finalSpecURL + "#" + id, URL: headURL + "#" + id,
Description: desc, Description: desc,
TestName: testName, TestName: testName,
Keyword: "ALGORITHM", Keyword: "ALGORITHM",
} }
if strings.Contains(id, "builtin-functions") {
prevRuleIndex, builtinExist := visitedBuiltinsSet[builtinName]
if builtinExist {
// TODO(sarahM0): https://bugs.c/tint/1159
// Overloads of a builtin function is merged to the previous rule
// depending on what we decide to choose as key sha1, this might change
(p.rules)[prevRuleIndex].Description += ("\nNext overload:" +
"\nURL:" + r.URL + "\nDescription: " + r.Description)
r.Desc = append(r.Desc, desc)
return nil
} else {
visitedBuiltinsSet[builtinName] = len(p.rules)
}
}
p.rules = append(p.rules, r) p.rules = append(p.rules, r)
return nil return nil
} }
@ -603,13 +555,12 @@ func (p *Parser) getNowrapRule(node *html.Node, section int, subSection string)
desc := cleanUpStartEnd(getNodeData(node)) desc := cleanUpStartEnd(getNodeData(node))
id := getID(node) id := getID(node)
url := finalSpecURL + "#" + id t, _, err := testName(id, desc, subSection)
t, _, err := testName(id, desc, "", subSection)
if err != nil { if err != nil {
return err return err
} }
sha, err := getSha1(desc, subSection) sha, err := getSha1(desc, id)
if err != nil { if err != nil {
return err return err
} }
@ -619,7 +570,7 @@ func (p *Parser) getNowrapRule(node *html.Node, section int, subSection string)
Number: len(p.rules) + 1, Number: len(p.rules) + 1,
SubSection: subSection, SubSection: subSection,
Section: section, Section: section,
URL: url, URL: headURL + "#" + id,
Description: desc, Description: desc,
TestName: t, TestName: t,
Keyword: "Nowrap", Keyword: "Nowrap",
@ -701,19 +652,32 @@ func getID(node *html.Node) string {
} }
var ( var (
reCleanUpString = regexp.MustCompile(`\s+|\t|\n`) reCleanUpString = regexp.MustCompile(`\n(\n|\s|\t)+|(\s|\t)+\n`)
// regex for starts with spaces reSpacePlusTwo = regexp.MustCompile(`\t|\s{2,}`)
reBeginOrEndWithSpace = regexp.MustCompile(`^\s|\s$`) reBeginOrEndWithSpace = regexp.MustCompile(`^\s|\s$`)
reIrregularWhiteSpace = regexp.MustCompile(`§.`)
) )
// cleanUpString creates a string by removing all extra spaces, newlines and tabs // cleanUpString creates a string by removing all extra spaces, newlines and tabs
// form input string 'in' and returns it // form input string 'in' and returns it
// This is done so that the uniqueID does not change because of a change in white spaces
//
// example in:
// ` float abs:
// T is f32 or vecN<f32>
// abs(e: T ) -> T
// Returns the absolute value of e (e.g. e with a positive sign bit). Component-wise when T is a vector.
// (GLSLstd450Fabs)`
//
// example out:
// `float abs:
// T is f32 or vecN<f32> abs(e: T ) -> T Returns the absolute value of e (e.g. e with a positive sign bit). Component-wise when T is a vector. (GLSLstd450Fabs)`
func cleanUpString(in string) string { func cleanUpString(in string) string {
// regex for more than one space, newlines or a tabs out := reCleanUpString.ReplaceAllString(in, "\n")
out := reCleanUpString.ReplaceAllString(in, " ") out = reSpacePlusTwo.ReplaceAllString(out, " ")
//`§.` is not a valid character for a cts description //`§.` is not a valid character for a cts description
// ie. this is invalid: g.test().desc(`§.`) // ie. this is invalid: g.test().desc(`§.`)
out = strings.ReplaceAll(out, "§.", "section ") out = reIrregularWhiteSpace.ReplaceAllString(out, "section ")
out = reBeginOrEndWithSpace.ReplaceAllString(out, "") out = reBeginOrEndWithSpace.ReplaceAllString(out, "")
return out return out
} }
@ -736,73 +700,73 @@ func cleanUpStartEnd(in string) string {
var ( var (
name = "^[a-zA-Z0-9_]+$" name = "^[a-zA-Z0-9_]+$"
reName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) reName = regexp.MustCompile(`[^a-zA-Z0-9_]`)
reUnderScore = regexp.MustCompile(`[-]+|\s+`) reUnderScore = regexp.MustCompile(`[_]+`)
reDoNotBegin = regexp.MustCompile(`^[0-9_]+|[_]+$`) reDoNotBegin = regexp.MustCompile(`^[0-9_]+|[_]$`)
) )
// testName proposes a test name for the CTS implementation of this rule // testName creates a test name given a rule id (ie. section name), description and section
// TODO(crbug.com/tint/1158): update this function to just use section x.y then a number:w // returns for a builtin rule:
func testName(id string, desc string, title string, section string) (fileName, builtinName string, err error) { // testName: ${builtin name} + "_" + ${section name}
// builtinName: ${builtin name}
// err: nil
// returns for a other rules:
// testName: ${section name} + "_rule_ + " + ${string(counter)}
// builtinName: ""
// err: nil
// if it cannot create a unique name it returns "", "", err.
func testName(id string, desc string, section string) (testName, builtinName string, err error) {
// regex for every thing other than letters and numbers // regex for every thing other than letters and numbers
if len(desc) == 0 { if desc == "" || section == "" || id == "" {
return "", "", fmt.Errorf("unable to generate a test name for empty description") return "", "", fmt.Errorf("cannot generate test name")
} }
fileName = "" // avoid any characters other than letters, numbers and underscore
title = reName.ReplaceAllString(title, "_")
id = reName.ReplaceAllString(id, "_") id = reName.ReplaceAllString(id, "_")
// avoid underscore repeats // avoid underscore repeats
title = reUnderScore.ReplaceAllString(title, "_")
id = reUnderScore.ReplaceAllString(id, "_") id = reUnderScore.ReplaceAllString(id, "_")
// test name must not start with underscore or a number // test name must not start with underscore or a number
// nor end with and underscore // nor end with and underscore
title = reDoNotBegin.ReplaceAllString(title, "")
id = reDoNotBegin.ReplaceAllString(id, "") id = reDoNotBegin.ReplaceAllString(id, "")
sectionX, err := parseSection(section)
if err != nil {
return "", "", err
}
builtinName = "" builtinName = ""
if strings.Contains(id, "builtin_functions") { index := strings.Index(desc, ":")
n := getBetween(desc, []string{"\n", "\t", " ", ""}, "(") if strings.Contains(id, "builtin_functions") && index > -1 {
if len(n) > 0 { builtinName = reName.ReplaceAllString(desc[:index], "_")
builtinName = n[0][:len(n[0])-1] builtinName = reDoNotBegin.ReplaceAllString(builtinName, "")
builtinName = reUnderScore.ReplaceAllString(builtinName, "_")
match, _ := regexp.MatchString(name, builtinName) match, _ := regexp.MatchString(name, builtinName)
if match { if match {
fileName = builtinName testName = builtinName + "," + id
// no need to check, it's an overload for i := 1; testNamesSet[testName]; i++ {
testNamesSet[fileName] = true testName = builtinName + "_" + id + "_" + strconv.Itoa(i)
return fileName, builtinName, nil
}
} }
testNamesSet[testName] = true
return testName, builtinName, nil
} }
if title != "" {
fileName = id + "," + title
if !testNamesSet[fileName] && len(fileName) < 32 {
testNamesSet[fileName] = true
return fileName, "", nil
}
} }
if section != "" { if sectionX[0] == globalPrevSectionX {
section = reName.ReplaceAllString(section, "_") globalRuleCounter++
if section == globalPrevSection {
globalCounter++
} else { } else {
globalCounter = 0 globalRuleCounter = 0
globalPrevSection = section globalPrevSectionX = sectionX[0]
} }
fileName = "section_" + section + "_rule_" + strconv.Itoa(globalCounter) testName = "section" + strconv.Itoa(sectionX[0]) + "_rule" + strconv.Itoa(globalRuleCounter)
return fileName, "", nil if testNamesSet[testName] {
testName = "error-unable-to-generate-unique-file-name"
return testName, "", fmt.Errorf("unable to generate unique test name\n" + desc)
} }
fileName = "error-unable-to-generate-unique-file-name" testNamesSet[testName] = true
return fileName, "", fmt.Errorf("unable to generate unique test name\n" + desc) return testName, "", nil
} }
// printNodeText traverses node and its children, writing the Data of all // printNodeText traverses node and its children, writing the Data of all TextNodes to sb.
// TextNodes to sb.
func printNodeText(node *html.Node, sb *strings.Builder) { func printNodeText(node *html.Node, sb *strings.Builder) {
// mark this node as seen // mark this node as seen
markedNodesSet[node] = true markedNodesSet[node] = true
@ -992,13 +956,15 @@ func getBuiltinSectionNum(rules []rule) (int, error) {
} }
func isBuiltinFunctionRule(r rule) bool { func isBuiltinFunctionRule(r rule) bool {
_, builtinName, _ := testName(r.URL, r.Description, "", r.SubSection) _, builtinName, _ := testName(r.URL, r.Description, r.SubSection)
return builtinName != "" return builtinName != "" || strings.Contains(r.URL, "builtin-functions")
} }
func testPlan(r rule) string { func testPlan(r rule) string {
sb := strings.Builder{} sb := strings.Builder{}
sb.WriteString(fmt.Sprintf(unImplementedTestTemplate, r.TestName, r.Sha, "`\n"+r.Description+"\n`")) sb.WriteString(fmt.Sprintf(unImplementedTestTemplate, r.TestName,
r.Sha, "`\n"+r.URL+"\n"+r.Description+"\n`"))
return sb.String() return sb.String()
} }