go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/clustering/algorithms/testname/rules/rule_test.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rules
    16  
    17  import (
    18  	"testing"
    19  
    20  	configpb "go.chromium.org/luci/analysis/proto/config"
    21  
    22  	. "github.com/smartystreets/goconvey/convey"
    23  	. "go.chromium.org/luci/common/testing/assertions"
    24  )
    25  
    26  func TestRule(t *testing.T) {
    27  	Convey(`Evaluate`, t, func() {
    28  		Convey(`Valid Examples`, func() {
    29  			Convey(`Blink Web Tests`, func() {
    30  				rule := &configpb.TestNameClusteringRule{
    31  					Name:         "Blink Web Tests",
    32  					Pattern:      `^ninja://:blink_web_tests/(virtual/[^/]+/)?(?P<testname>([^/]+/)+[^/]+\.[a-zA-Z]+).*$`,
    33  					LikeTemplate: `ninja://:blink\_web\_tests/%${testname}%`,
    34  				}
    35  				eval, err := Compile(rule)
    36  				So(err, ShouldBeNil)
    37  
    38  				inputs := []string{
    39  					"ninja://:blink_web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-getImageData.html",
    40  					"ninja://:blink_web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-getImageData.html?param=a",
    41  					"ninja://:blink_web_tests/virtual/oopr-canvas3d/fast/canvas/canvas-getImageData.html?param=b",
    42  					"ninja://:blink_web_tests/fast/canvas/canvas-getImageData.html",
    43  				}
    44  				for _, testname := range inputs {
    45  					like, ok := eval(testname)
    46  					So(ok, ShouldBeTrue)
    47  					So(like, ShouldEqual, `ninja://:blink\_web\_tests/%fast/canvas/canvas-getImageData.html%`)
    48  				}
    49  
    50  				_, ok := eval("ninja://:not_blink_web_tests/fast/canvas/canvas-getImageData.html")
    51  				So(ok, ShouldBeFalse)
    52  			})
    53  			Convey(`Google Tests`, func() {
    54  				rule := &configpb.TestNameClusteringRule{
    55  					Name: "Google Test (Value-parameterized)",
    56  					// E.g. ninja:{target}/Prefix/ColorSpaceTest.testNullTransform/11
    57  					// Note that "Prefix/" portion may be blank/omitted.
    58  					Pattern:      `^ninja:(?P<target>[\w/]+:\w+)/(\w+/)?(?P<suite>\w+)\.(?P<case>\w+)/\w+$`,
    59  					LikeTemplate: `ninja:${target}/%${suite}.${case}%`,
    60  				}
    61  				eval, err := Compile(rule)
    62  				So(err, ShouldBeNil)
    63  
    64  				inputs := []string{
    65  					"ninja://chrome/test:interactive_ui_tests/Name/ColorSpaceTest.testNullTransform/0",
    66  					"ninja://chrome/test:interactive_ui_tests/Name/ColorSpaceTest.testNullTransform/0",
    67  					"ninja://chrome/test:interactive_ui_tests/Name/ColorSpaceTest.testNullTransform/11",
    68  				}
    69  				for _, testname := range inputs {
    70  					like, ok := eval(testname)
    71  					So(ok, ShouldBeTrue)
    72  					So(like, ShouldEqual, "ninja://chrome/test:interactive\\_ui\\_tests/%ColorSpaceTest.testNullTransform%")
    73  				}
    74  
    75  				_, ok := eval("ninja://:blink_web_tests/virtual/oopr-canvas2d/fast/canvas/canvas-getImageData.html")
    76  				So(ok, ShouldBeFalse)
    77  			})
    78  		})
    79  		Convey(`Test name escaping in LIKE output`, func() {
    80  			Convey(`Test name is escaped when substituted`, func() {
    81  				rule := &configpb.TestNameClusteringRule{
    82  					Name:         "Escape test",
    83  					Pattern:      `^(?P<testname>.*)$`,
    84  					LikeTemplate: `${testname}_%`,
    85  				}
    86  				eval, err := Compile(rule)
    87  				So(err, ShouldBeNil)
    88  
    89  				// Verify that the test name is not injected varbatim in the generated
    90  				// like expression, but is escaped to avoid it being interpreted.
    91  				like, ok := eval(`_\%`)
    92  				So(ok, ShouldBeTrue)
    93  				So(like, ShouldEqual, `\_\\\%_%`)
    94  			})
    95  			Convey(`Unsafe LIKE templates are rejected`, func() {
    96  				rule := &configpb.TestNameClusteringRule{
    97  					Name:    "Escape test",
    98  					Pattern: `^path\\(?P<testname>.*)$`,
    99  					// The user as incorrectly used an unfinished LIKE escape sequence
   100  					// (with trailing '\') before the testname substitution.
   101  					// If substitution were allowed, this may allow the test name to be
   102  					// interpreted as a LIKE expression instead as literal text.
   103  					// E.g. a test name of `path\%` may yield `path\\%` after template
   104  					// evaluation which invokes the LIKE '%' operator.
   105  					LikeTemplate: `path\${testname}`,
   106  				}
   107  				_, err := Compile(rule)
   108  				So(err, ShouldErrLike, `"path\\" is not a valid standalone LIKE expression: unfinished escape sequence "\" at end of LIKE pattern`)
   109  			})
   110  		})
   111  		Convey(`Substitution operator`, func() {
   112  			Convey(`Dollar sign can be inserted into output`, func() {
   113  				rule := &configpb.TestNameClusteringRule{
   114  					Name:         "Insert $",
   115  					Pattern:      `^(?P<testname>.*)$`,
   116  					LikeTemplate: `${testname}$$blah$$$$`,
   117  				}
   118  				eval, err := Compile(rule)
   119  				So(err, ShouldBeNil)
   120  
   121  				like, ok := eval(`test`)
   122  				So(ok, ShouldBeTrue)
   123  				So(like, ShouldEqual, `test$blah$$`)
   124  			})
   125  			Convey(`Invalid uses of substitution operator are rejected`, func() {
   126  				rule := &configpb.TestNameClusteringRule{
   127  					Name:         "Invalid use of $ (neither $$ or ${name})",
   128  					Pattern:      `^(?P<testname>.*)$`,
   129  					LikeTemplate: `${testname}blah$$$`,
   130  				}
   131  				_, err := Compile(rule)
   132  				So(err, ShouldErrLike, `invalid use of the $ operator at position 17 in "${testname}blah$$$"`)
   133  
   134  				rule = &configpb.TestNameClusteringRule{
   135  					Name:         "Invalid use of $ (invalid capture group name)",
   136  					Pattern:      `^(?P<testname>.*)$`,
   137  					LikeTemplate: `${template@}blah`,
   138  				}
   139  				_, err = Compile(rule)
   140  				So(err, ShouldErrLike, `invalid use of the $ operator at position 0 in "${template@}blah"`)
   141  
   142  				rule = &configpb.TestNameClusteringRule{
   143  					Name:         "Capture group name not defined",
   144  					Pattern:      `^(?P<testname>.*)$`,
   145  					LikeTemplate: `${myname}blah`,
   146  				}
   147  				_, err = Compile(rule)
   148  				So(err, ShouldErrLike, `like_template: contains reference to non-existant capturing group with name "myname"`)
   149  			})
   150  		})
   151  		Convey(`Invalid Pattern`, func() {
   152  			rule := &configpb.TestNameClusteringRule{
   153  				Name:         "Invalid Pattern",
   154  				Pattern:      `[`,
   155  				LikeTemplate: ``,
   156  			}
   157  			_, err := Compile(rule)
   158  			So(err, ShouldErrLike, `pattern: error parsing regexp`)
   159  		})
   160  	})
   161  }