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  }