go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/model/filters_test.go (about) 1 // Copyright 2024 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 model 16 17 import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 "go.chromium.org/luci/gae/service/datastore" 23 24 apipb "go.chromium.org/luci/swarming/proto/api_v2" 25 26 . "github.com/smartystreets/goconvey/convey" 27 . "go.chromium.org/luci/common/testing/assertions" 28 ) 29 30 func TestFilter(t *testing.T) { 31 t.Parallel() 32 33 Convey("Ok", t, func() { 34 f, err := NewFilter([]*apipb.StringPair{ 35 {Key: "x", Value: "c|b|a"}, 36 {Key: "x", Value: "y"}, 37 {Key: "pool", Value: "P1"}, 38 {Key: "pool", Value: "P1|P2|P3"}, 39 }) 40 So(err, ShouldBeNil) 41 So(f, ShouldResemble, Filter{ 42 filters: []perKeyFilter{ 43 {key: "pool", values: []string{"P1"}}, 44 {key: "pool", values: []string{"P1", "P2", "P3"}}, 45 {key: "x", values: []string{"a", "b", "c"}}, 46 {key: "x", values: []string{"y"}}, 47 }, 48 }) 49 So(f.Pools(), ShouldResemble, []string{"P1", "P2", "P3"}) 50 }) 51 52 Convey("Errors", t, func() { 53 call := func(k, v string) error { 54 _, err := NewFilter([]*apipb.StringPair{ 55 {Key: k, Value: v}, 56 }) 57 return err 58 } 59 So(call("", "val"), ShouldErrLike, "bad key") 60 So(call(" key", "val"), ShouldErrLike, "bad key") 61 So(call("key", ""), ShouldErrLike, "bad value") 62 So(call("key", " val"), ShouldErrLike, "bad value") 63 }) 64 65 Convey("Empty", t, func() { 66 f, err := NewFilter(nil) 67 So(err, ShouldBeNil) 68 So(f.IsEmpty(), ShouldBeTrue) 69 }) 70 71 Convey("SplitForQuery", t, func() { 72 split := func(q string, mode SplitMode) []string { 73 var pairs []*apipb.StringPair 74 for _, kv := range strings.Split(q, " ") { 75 k, v, _ := strings.Cut(kv, ":") 76 pairs = append(pairs, &apipb.StringPair{ 77 Key: k, 78 Value: v, 79 }) 80 } 81 in, err := NewFilter(pairs) 82 So(err, ShouldBeNil) 83 84 parts := in.SplitForQuery(mode) 85 86 var out []string 87 for _, part := range parts { 88 var elems []string 89 for _, f := range part.filters { 90 elems = append(elems, fmt.Sprintf("%s:%s", f.key, strings.Join(f.values, "|"))) 91 } 92 out = append(out, strings.Join(elems, " ")) 93 } 94 return out 95 } 96 97 Convey("Empty", func() { 98 f, err := NewFilter(nil) 99 So(err, ShouldBeNil) 100 for _, mode := range []SplitMode{SplitCompletely, SplitOptimally} { 101 out := f.SplitForQuery(mode) 102 So(out, ShouldHaveLength, 1) 103 So(out[0].IsEmpty(), ShouldBeTrue) 104 105 q := datastore.NewQuery("Something") 106 split := f.Apply(q, "doesntmatter", mode) 107 So(split, ShouldHaveLength, 1) 108 So(split[0] == q, ShouldBeTrue) // the exact same original query 109 } 110 }) 111 112 Convey("SplitCompletely", func() { 113 // Already simple query. 114 So(split("k1:v1 k2:v2", SplitCompletely), ShouldResemble, []string{"k1:v1 k2:v2"}) 115 // One disjunction. 116 So(split("k1:v1|v2 k2:v3", SplitCompletely), ShouldResemble, []string{ 117 "k1:v1 k2:v3", 118 "k1:v2 k2:v3", 119 }) 120 // Two disjunctions. 121 So(split("k1:v1|v2 k2:v3 k3:v4|v5", SplitCompletely), ShouldResemble, []string{ 122 "k1:v1 k2:v3 k3:v4", 123 "k1:v1 k2:v3 k3:v5", 124 "k1:v2 k2:v3 k3:v4", 125 "k1:v2 k2:v3 k3:v5", 126 }) 127 // Repeated keys are OK, but may result in redundant filters. 128 So(split("k1:v1|v2 k1:v2|v3 k1:v4", SplitCompletely), ShouldResemble, []string{ 129 "k1:v1 k1:v2 k1:v4", 130 "k1:v1 k1:v3 k1:v4", 131 "k1:v2 k1:v2 k1:v4", 132 "k1:v2 k1:v3 k1:v4", 133 }) 134 }) 135 136 Convey("SplitOptimally", func() { 137 // Already simple enough query. 138 So(split("k1:v1 k2:v2", SplitOptimally), ShouldResemble, []string{"k1:v1 k2:v2"}) 139 So(split("k1:v1|v2 k2:v3", SplitOptimally), ShouldResemble, []string{"k1:v1|v2 k2:v3"}) 140 // Splits on the smallest term. 141 So(split("k1:v1|v2|v3 k2:v1|v2 k3:v3", SplitOptimally), ShouldResemble, []string{ 142 "k1:v1|v2|v3 k2:v1 k3:v3", 143 "k1:v1|v2|v3 k2:v2 k3:v3", 144 }) 145 // Leaves at most one disjunction (the largest one). 146 So(split("k1:v1|v2|v3 k2:v1|v2|v3|v4 k3:v1|v2 k4:v1", SplitOptimally), ShouldResemble, []string{ 147 "k1:v1 k2:v1|v2|v3|v4 k3:v1 k4:v1", 148 "k1:v2 k2:v1|v2|v3|v4 k3:v1 k4:v1", 149 "k1:v3 k2:v1|v2|v3|v4 k3:v1 k4:v1", 150 "k1:v1 k2:v1|v2|v3|v4 k3:v2 k4:v1", 151 "k1:v2 k2:v1|v2|v3|v4 k3:v2 k4:v1", 152 "k1:v3 k2:v1|v2|v3|v4 k3:v2 k4:v1", 153 }) 154 }) 155 }) 156 }