github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/project_selector_test.go (about) 1 package model 2 3 import ( 4 "fmt" 5 "testing" 6 7 . "github.com/smartystreets/goconvey/convey" 8 ) 9 10 var materialTempAxes = []matrixAxis{ 11 { 12 Id: "material", 13 Values: []axisValue{ 14 {Id: "wood", Tags: []string{"organic", "soft"}}, 15 {Id: "carbon", Tags: []string{"organic"}}, 16 {Id: "iron", Tags: []string{"metal", "strong"}}, 17 }, 18 }, 19 { 20 Id: "temp", 21 Values: []axisValue{ 22 {Id: "100", Tags: []string{"hot", "boiling"}}, 23 {Id: "40", Tags: []string{"hot"}}, 24 {Id: "10", Tags: []string{"cold"}}, 25 {Id: "0", Tags: []string{"cold", "freezing"}}, 26 }, 27 }, 28 } 29 30 // helper for comparing a selector string with its expected output 31 func selectorShouldParse(s string, expected Selector) { 32 Convey(fmt.Sprintf(`selector string "%v" should parse correctly`, s), func() { 33 So(ParseSelector(s), ShouldResemble, expected) 34 }) 35 } 36 37 func TestBasicSelector(t *testing.T) { 38 Convey("With a set of test selection strings", t, func() { 39 40 Convey("single selectors should parse", func() { 41 selectorShouldParse("myTask", Selector{{name: "myTask"}}) 42 selectorShouldParse("!myTask", Selector{{name: "myTask", negated: true}}) 43 selectorShouldParse(".myTag", Selector{{name: "myTag", tagged: true}}) 44 selectorShouldParse("!.myTag", Selector{{name: "myTag", tagged: true, negated: true}}) 45 selectorShouldParse("*", Selector{{name: "*"}}) 46 }) 47 48 Convey("multi-selectors should parse", func() { 49 selectorShouldParse(".tag1 .tag2", Selector{ 50 {name: "tag1", tagged: true}, 51 {name: "tag2", tagged: true}, 52 }) 53 selectorShouldParse(".tag1 !.tag2", Selector{ 54 {name: "tag1", tagged: true}, 55 {name: "tag2", tagged: true, negated: true}, 56 }) 57 selectorShouldParse("!.tag1 .tag2", Selector{ 58 {name: "tag1", tagged: true, negated: true}, 59 {name: "tag2", tagged: true}, 60 }) 61 selectorShouldParse(".mytag !mytask", Selector{ 62 {name: "mytag", tagged: true}, 63 {name: "mytask", negated: true}, 64 }) 65 selectorShouldParse(".tag1 .tag2 .tag3 !.tag4", Selector{ 66 {name: "tag1", tagged: true}, 67 {name: "tag2", tagged: true}, 68 {name: "tag3", tagged: true}, 69 {name: "tag4", tagged: true, negated: true}, 70 }) 71 72 Convey("selectors with unusual whitespace should parse", func() { 73 selectorShouldParse(" .myTag ", Selector{{name: "myTag", tagged: true}}) 74 selectorShouldParse(".mytag\t\t!mytask", Selector{ 75 {name: "mytag", tagged: true}, 76 {name: "mytask", negated: true}, 77 }) 78 selectorShouldParse("\r\n.mytag\r\n!mytask\n", Selector{ 79 {name: "mytag", tagged: true}, 80 {name: "mytask", negated: true}, 81 }) 82 }) 83 }) 84 }) 85 } 86 87 func tagSelectorShouldEval(tse *tagSelectorEvaluator, s string, expected []string) { 88 Convey(fmt.Sprintf(`selector "%v" should evaluate to %v`, s, expected), func() { 89 names, err := tse.evalSelector(ParseSelector(s)) 90 if expected != nil { 91 So(err, ShouldBeNil) 92 } else { 93 So(err, ShouldNotBeNil) 94 } 95 So(len(names), ShouldEqual, len(expected)) 96 for _, e := range expected { 97 So(names, ShouldContain, e) 98 } 99 }) 100 } 101 102 type testSelectee struct { 103 Name string 104 Tags []string 105 } 106 107 func (ts testSelectee) name() string { return ts.Name } 108 func (ts testSelectee) tags() []string { return ts.Tags } 109 110 func TestTaskSelectorEvaluation(t *testing.T) { 111 var tse *tagSelectorEvaluator 112 113 Convey("With a colorful set of tags", t, func() { 114 defs := []tagged{ 115 testSelectee{Name: "red", Tags: []string{"primary", "warm"}}, 116 testSelectee{Name: "orange", Tags: []string{"secondary", "warm"}}, 117 testSelectee{Name: "yellow", Tags: []string{"primary", "warm"}}, 118 testSelectee{Name: "green", Tags: []string{"secondary", "cool"}}, 119 testSelectee{Name: "blue", Tags: []string{"primary", "cool"}}, 120 testSelectee{Name: "purple", Tags: []string{"secondary", "cool"}}, 121 testSelectee{Name: "brown", Tags: []string{"tertiary"}}, 122 testSelectee{Name: "black", Tags: []string{"special"}}, 123 testSelectee{Name: "white", Tags: []string{"special"}}, 124 } 125 126 Convey("a tagSelectorEvaluator", func() { 127 tse = newTagSelectorEvaluator(defs) 128 129 Convey("should evaluate single name selectors properly", func() { 130 tagSelectorShouldEval(tse, "red", []string{"red"}) 131 tagSelectorShouldEval(tse, "white", []string{"white"}) 132 }) 133 134 Convey("should evaluate single tag selectors properly", func() { 135 tagSelectorShouldEval(tse, ".warm", []string{"red", "orange", "yellow"}) 136 tagSelectorShouldEval(tse, ".cool", []string{"blue", "green", "purple"}) 137 tagSelectorShouldEval(tse, ".special", []string{"white", "black"}) 138 tagSelectorShouldEval(tse, ".primary", []string{"red", "blue", "yellow"}) 139 }) 140 141 Convey("should evaluate multi-tag selectors properly", func() { 142 tagSelectorShouldEval(tse, ".warm .cool", nil) 143 tagSelectorShouldEval(tse, ".cool .primary", []string{"blue"}) 144 tagSelectorShouldEval(tse, ".warm .secondary", []string{"orange"}) 145 }) 146 147 Convey("should evaluate selectors with negation properly", func() { 148 tagSelectorShouldEval(tse, "!.special", 149 []string{"red", "orange", "yellow", "green", "blue", "purple", "brown"}) 150 tagSelectorShouldEval(tse, ".warm !yellow", []string{"red", "orange"}) 151 tagSelectorShouldEval(tse, "!.primary !.secondary", []string{"black", "white", "brown"}) 152 }) 153 154 Convey("should evaluate special selectors", func() { 155 tagSelectorShouldEval(tse, "*", 156 []string{"red", "orange", "yellow", "green", "blue", "purple", "brown", "black", "white"}) 157 }) 158 159 Convey("should fail on bad selectors like", func() { 160 161 Convey("empty selectors", func() { 162 _, err := tse.evalSelector(Selector{}) 163 So(err, ShouldNotBeNil) 164 }) 165 166 Convey("names that don't exist", func() { 167 _, err := tse.evalSelector(ParseSelector("salmon")) 168 So(err, ShouldNotBeNil) 169 _, err = tse.evalSelector(ParseSelector("!azure")) 170 So(err, ShouldNotBeNil) 171 }) 172 173 Convey("tags that don't exist", func() { 174 _, err := tse.evalSelector(ParseSelector(".fall")) 175 So(err, ShouldNotBeNil) 176 _, err = tse.evalSelector(ParseSelector("!.spring")) 177 So(err, ShouldNotBeNil) 178 }) 179 180 Convey("using . and ! with *", func() { 181 _, err := tse.evalSelector(ParseSelector(".*")) 182 So(err, ShouldNotBeNil) 183 _, err = tse.evalSelector(ParseSelector("!*")) 184 So(err, ShouldNotBeNil) 185 }) 186 187 Convey("illegal names", func() { 188 _, err := tse.evalSelector(ParseSelector("!!purple")) 189 So(err, ShouldNotBeNil) 190 _, err = tse.evalSelector(ParseSelector(".!purple")) 191 So(err, ShouldNotBeNil) 192 _, err = tse.evalSelector(ParseSelector("..purple")) 193 So(err, ShouldNotBeNil) 194 }) 195 }) 196 }) 197 }) 198 } 199 200 func axisSelectorShouldEval(ase *axisSelectorEvaluator, axis, s string, expected []string) { 201 Convey(fmt.Sprintf(`selector %v:"%v" should evaluate to %v`, axis, s, expected), func() { 202 names, err := ase.evalSelector(axis, ParseSelector(s)) 203 if expected != nil { 204 So(err, ShouldBeNil) 205 } else { 206 So(err, ShouldNotBeNil) 207 } 208 So(len(names), ShouldEqual, len(expected)) 209 for _, e := range expected { 210 So(names, ShouldContain, e) 211 } 212 }) 213 } 214 215 func TestAxisSelectorEvaluation(t *testing.T) { 216 Convey("With a set of tagged axes and an axisSelectorEvaluator", t, func() { 217 218 ase := NewAxisSelectorEvaluator(materialTempAxes) 219 So(ase, ShouldNotBeNil) 220 Convey("valid selectors should return proper results", func() { 221 axisSelectorShouldEval(ase, "material", "*", []string{"wood", "carbon", "iron"}) 222 axisSelectorShouldEval(ase, "material", ".organic", []string{"wood", "carbon"}) 223 axisSelectorShouldEval(ase, "material", ".strong", []string{"iron"}) 224 axisSelectorShouldEval(ase, "material", "iron", []string{"iron"}) 225 axisSelectorShouldEval(ase, "material", ".organic !.soft", []string{"carbon"}) 226 axisSelectorShouldEval(ase, "temp", "*", []string{"0", "10", "40", "100"}) 227 axisSelectorShouldEval(ase, "temp", "0", []string{"0"}) 228 axisSelectorShouldEval(ase, "temp", ".hot", []string{"40", "100"}) 229 axisSelectorShouldEval(ase, "temp", ".hot !.boiling", []string{"40"}) 230 }) 231 Convey("attempts to use an undefined axis should error", func() { 232 r, err := ase.evalSelector("fake", ParseSelector("*")) 233 So(r, ShouldBeNil) 234 So(err, ShouldNotBeNil) 235 }) 236 Convey("attempts to use an undefined selector should error", func() { 237 r, err := ase.evalSelector("material", ParseSelector("nope")) 238 So(r, ShouldBeNil) 239 So(err, ShouldNotBeNil) 240 }) 241 }) 242 243 } 244 245 func TestVariantMatrixSelectorEvaluation(t *testing.T) { 246 Convey("With a set of tagged axes, a matrix, and an variantSelectorEvaluator", t, func() { 247 ase := NewAxisSelectorEvaluator(materialTempAxes) 248 m := matrix{ 249 Id: "test", 250 Spec: matrixDefinition{ 251 "material": []string{"*"}, 252 "temp": []string{"*"}, 253 }, 254 } 255 variants, errs := buildMatrixVariants(materialTempAxes, ase, []matrix{m}) 256 So(len(variants), ShouldEqual, 12) 257 So(errs, ShouldBeNil) 258 vse := NewVariantSelectorEvaluator(variants, ase) 259 So(vse, ShouldNotBeNil) 260 261 Convey("and a set of variant selectors", func() { 262 Convey("a single-cell matrix selector should return one variant", func() { 263 vs := &variantSelector{ 264 matrixSelector: matrixDefinition{ 265 "material": []string{"iron"}, 266 "temp": []string{"0"}, 267 }, 268 } 269 v, err := vse.evalSelector(vs) 270 So(err, ShouldBeNil) 271 So(len(v), ShouldEqual, 1) 272 So(v[0], ShouldEqual, "test__material~iron_temp~0") 273 }) 274 Convey("a 2x2 matrix selector should return four variants", func() { 275 vs := &variantSelector{ 276 matrixSelector: matrixDefinition{ 277 "material": []string{"iron", "wood"}, 278 "temp": []string{"0", "100"}, 279 }, 280 } 281 v, err := vse.evalSelector(vs) 282 So(err, ShouldBeNil) 283 So(len(v), ShouldEqual, 4) 284 So(v, ShouldContain, "test__material~iron_temp~0") 285 So(v, ShouldContain, "test__material~wood_temp~0") 286 So(v, ShouldContain, "test__material~iron_temp~100") 287 So(v, ShouldContain, "test__material~wood_temp~100") 288 }) 289 Convey("a *x* matrix selector should return all variants", func() { 290 vs := &variantSelector{ 291 matrixSelector: matrixDefinition{ 292 "material": []string{"*"}, 293 "temp": []string{"*"}, 294 }, 295 } 296 v, err := vse.evalSelector(vs) 297 So(err, ShouldBeNil) 298 So(len(v), ShouldEqual, 12) 299 }) 300 Convey("a tagged matrix selector should return all axis-tagged variants", func() { 301 vs := &variantSelector{ 302 matrixSelector: matrixDefinition{ 303 "material": []string{".metal"}, 304 "temp": []string{".hot"}, 305 }, 306 } 307 v, err := vse.evalSelector(vs) 308 So(err, ShouldBeNil) 309 So(len(v), ShouldEqual, 2) 310 So(v, ShouldContain, "test__material~iron_temp~40") 311 So(v, ShouldContain, "test__material~iron_temp~100") 312 }) 313 Convey("an empty matrix selector should error", func() { 314 vs := &variantSelector{ 315 matrixSelector: matrixDefinition{}, 316 } 317 v, err := vse.evalSelector(vs) 318 So(err, ShouldNotBeNil) 319 So(v, ShouldBeNil) 320 }) 321 Convey("a matrix selector with nonexistent axes should fail", func() { 322 vs := &variantSelector{ 323 matrixSelector: matrixDefinition{ 324 "wow": []string{"neat"}, 325 }, 326 } 327 v, err := vse.evalSelector(vs) 328 So(err, ShouldNotBeNil) 329 So(v, ShouldBeNil) 330 }) 331 Convey("a matrix selector with nonexistent selectors should fail", func() { 332 vs := &variantSelector{ 333 matrixSelector: matrixDefinition{ 334 "material": []string{".neat"}, 335 }, 336 } 337 v, err := vse.evalSelector(vs) 338 So(err, ShouldNotBeNil) 339 So(v, ShouldBeNil) 340 }) 341 Convey("a matrix selector with invalid selectors should fail", func() { 342 vs := &variantSelector{ 343 matrixSelector: matrixDefinition{ 344 "material": []string{""}, 345 }, 346 } 347 v, err := vse.evalSelector(vs) 348 So(err, ShouldNotBeNil) 349 So(v, ShouldBeNil) 350 }) 351 }) 352 }) 353 354 }