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

@ -46,26 +46,25 @@ 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 ",
"SHALL NOT ", "SHOULD ", "SHOULD NOT ", "SHALL NOT ", "SHOULD ", "SHOULD NOT ",
"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, "")
match, _ := regexp.MatchString(name, builtinName) builtinName = reUnderScore.ReplaceAllString(builtinName, "_")
if match { match, _ := regexp.MatchString(name, builtinName)
fileName = builtinName if match {
// no need to check, it's an overload testName = builtinName + "," + id
testNamesSet[fileName] = true for i := 1; testNamesSet[testName]; i++ {
return fileName, builtinName, nil testName = builtinName + "_" + id + "_" + strconv.Itoa(i)
} }
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 != "" {
section = reName.ReplaceAllString(section, "_")
if section == globalPrevSection {
globalCounter++
} else {
globalCounter = 0
globalPrevSection = section
}
fileName = "section_" + section + "_rule_" + strconv.Itoa(globalCounter)
return fileName, "", nil
} }
fileName = "error-unable-to-generate-unique-file-name"
return fileName, "", fmt.Errorf("unable to generate unique test name\n" + desc) if sectionX[0] == globalPrevSectionX {
globalRuleCounter++
} else {
globalRuleCounter = 0
globalPrevSectionX = sectionX[0]
}
testName = "section" + strconv.Itoa(sectionX[0]) + "_rule" + strconv.Itoa(globalRuleCounter)
if testNamesSet[testName] {
testName = "error-unable-to-generate-unique-file-name"
return testName, "", fmt.Errorf("unable to generate unique test name\n" + desc)
}
testNamesSet[testName] = true
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()
} }