github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/patch_lifecycle_test.go (about)

     1  package model
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/evergreen-ci/evergreen/model/patch"
    11  	"github.com/evergreen-ci/evergreen/testutil"
    12  	"github.com/mongodb/grip"
    13  	. "github.com/smartystreets/goconvey/convey"
    14  )
    15  
    16  func TestMakePatchedConfig(t *testing.T) {
    17  	Convey("With calling MakePatchedConfig with a config and remote configuration path", t, func() {
    18  		cwd := testutil.GetDirectoryOfFile()
    19  
    20  		Convey("the config should be patched correctly", func() {
    21  			remoteConfigPath := filepath.Join("config", "evergreen.yml")
    22  			fileBytes, err := ioutil.ReadFile(filepath.Join(cwd, "testdata", "patch.diff"))
    23  			So(err, ShouldBeNil)
    24  			// update patch with remove config path variable
    25  			diffString := fmt.Sprintf(string(fileBytes),
    26  				remoteConfigPath, remoteConfigPath, remoteConfigPath, remoteConfigPath)
    27  			// the patch adds a new task
    28  			p := &patch.Patch{
    29  				Patches: []patch.ModulePatch{{
    30  					Githash: "revision",
    31  					PatchSet: patch.PatchSet{
    32  						Patch: diffString,
    33  						Summary: []patch.Summary{{
    34  							Name:      remoteConfigPath,
    35  							Additions: 3,
    36  							Deletions: 3,
    37  						}},
    38  					},
    39  				}},
    40  			}
    41  			projectBytes, err := ioutil.ReadFile(filepath.Join(cwd, "testdata", "project.config"))
    42  			So(err, ShouldBeNil)
    43  			project, err := MakePatchedConfig(p, remoteConfigPath, string(projectBytes))
    44  			So(err, ShouldBeNil)
    45  			So(project, ShouldNotBeNil)
    46  			So(len(project.Tasks), ShouldEqual, 2)
    47  		})
    48  		Convey("an empty base config should be patched correctly", func() {
    49  			remoteConfigPath := filepath.Join("model", "testdata", "project2.config")
    50  			fileBytes, err := ioutil.ReadFile(filepath.Join(cwd, "testdata", "project.diff"))
    51  			So(err, ShouldBeNil)
    52  			p := &patch.Patch{
    53  				Patches: []patch.ModulePatch{{
    54  					Githash: "revision",
    55  					PatchSet: patch.PatchSet{
    56  						Patch:   string(fileBytes),
    57  						Summary: []patch.Summary{{Name: remoteConfigPath}},
    58  					},
    59  				}},
    60  			}
    61  
    62  			project, err := MakePatchedConfig(p, remoteConfigPath, "")
    63  			So(err, ShouldBeNil)
    64  			So(project, ShouldNotBeNil)
    65  			So(len(project.Tasks), ShouldEqual, 1)
    66  			So(project.Tasks[0].Name, ShouldEqual, "hello")
    67  
    68  			Reset(func() {
    69  				grip.Warning(os.Remove(remoteConfigPath))
    70  			})
    71  		})
    72  	})
    73  }
    74  
    75  // shouldContainPair returns a blank string if its arguments resemble each other, and returns a
    76  // list of pretty-printed diffs between the objects if they do not match.
    77  func shouldContainPair(actual interface{}, expected ...interface{}) string {
    78  	actualPairsList, ok := actual.([]TVPair)
    79  
    80  	if !ok {
    81  		return fmt.Sprintf("Assertion requires a list of TVPair objects")
    82  	}
    83  
    84  	if len(expected) != 1 {
    85  		return fmt.Sprintf("Assertion requires 1 expected value, you provided %v", len(expected))
    86  	}
    87  
    88  	expectedPair, ok := expected[0].(TVPair)
    89  	if !ok {
    90  		return fmt.Sprintf("Assertion requires expected value to be an instance of TVPair")
    91  	}
    92  
    93  	for _, ap := range actualPairsList {
    94  		if ap.Variant == expectedPair.Variant && ap.TaskName == expectedPair.TaskName {
    95  			return ""
    96  		}
    97  	}
    98  	return fmt.Sprintf("Expected list to contain pair '%v', but it didn't", expectedPair)
    99  }
   100  
   101  func TestIncludePatchDependencies(t *testing.T) {
   102  	Convey("With a project task config with cross-variant dependencies", t, func() {
   103  		p := &Project{
   104  			Tasks: []ProjectTask{
   105  				{Name: "t1"},
   106  				{Name: "t2", DependsOn: []TaskDependency{{Name: "t1"}}},
   107  				{Name: "t3"},
   108  				{Name: "t4", Patchable: new(bool)},
   109  				{Name: "t5", DependsOn: []TaskDependency{{Name: "t4"}}},
   110  			},
   111  			BuildVariants: []BuildVariant{
   112  				{Name: "v1", Tasks: []BuildVariantTask{{Name: "t1"}, {Name: "t2"}}},
   113  				{Name: "v2", Tasks: []BuildVariantTask{
   114  					{Name: "t3", DependsOn: []TaskDependency{{Name: "t2", Variant: "v1"}}}}},
   115  			},
   116  		}
   117  
   118  		Convey("a patch against v1/t1 should remain unchanged", func() {
   119  			pairs := IncludePatchDependencies(p, []TVPair{{"v1", "t1"}})
   120  			So(len(pairs), ShouldEqual, 1)
   121  			So(pairs[0], ShouldResemble, TVPair{"v1", "t1"})
   122  		})
   123  
   124  		Convey("a patch against v1/t2 should add t1", func() {
   125  			pairs := IncludePatchDependencies(p, []TVPair{{"v1", "t2"}})
   126  			So(len(pairs), ShouldEqual, 2)
   127  			So(pairs, shouldContainPair, TVPair{"v1", "t2"})
   128  			So(pairs, shouldContainPair, TVPair{"v1", "t1"})
   129  		})
   130  
   131  		Convey("a patch against v2/t3 should add t1,t2, and v1", func() {
   132  			pairs := IncludePatchDependencies(p, []TVPair{{"v2", "t3"}})
   133  			So(len(pairs), ShouldEqual, 3)
   134  			So(pairs, shouldContainPair, TVPair{"v1", "t2"})
   135  			So(pairs, shouldContainPair, TVPair{"v1", "t1"})
   136  			So(pairs, shouldContainPair, TVPair{"v2", "t3"})
   137  		})
   138  
   139  		Convey("a patch against v2/t5 should be pruned, since its dependency is not patchable", func() {
   140  			pairs := IncludePatchDependencies(p, []TVPair{{"v2", "t5"}})
   141  			So(len(pairs), ShouldEqual, 0)
   142  		})
   143  	})
   144  
   145  	Convey("With a project task config with * selectors", t, func() {
   146  		p := &Project{
   147  			Tasks: []ProjectTask{
   148  				{Name: "t1"},
   149  				{Name: "t2"},
   150  				{Name: "t3", DependsOn: []TaskDependency{{Name: AllDependencies}}},
   151  				{Name: "t4", DependsOn: []TaskDependency{{Name: "t3", Variant: AllVariants}}},
   152  				{Name: "t5", DependsOn: []TaskDependency{{Name: AllDependencies, Variant: AllVariants}}},
   153  			},
   154  			BuildVariants: []BuildVariant{
   155  				{Name: "v1", Tasks: []BuildVariantTask{{Name: "t1"}, {Name: "t2"}, {Name: "t3"}}},
   156  				{Name: "v2", Tasks: []BuildVariantTask{{Name: "t1"}, {Name: "t2"}, {Name: "t3"}}},
   157  				{Name: "v3", Tasks: []BuildVariantTask{{Name: "t4"}}},
   158  				{Name: "v4", Tasks: []BuildVariantTask{{Name: "t5"}}},
   159  			},
   160  		}
   161  
   162  		Convey("a patch against v1/t3 should include t2 and t1", func() {
   163  			pairs := IncludePatchDependencies(p, []TVPair{{"v1", "t3"}})
   164  			So(len(pairs), ShouldEqual, 3)
   165  			So(pairs, shouldContainPair, TVPair{"v1", "t2"})
   166  			So(pairs, shouldContainPair, TVPair{"v1", "t1"})
   167  			So(pairs, shouldContainPair, TVPair{"v1", "t3"})
   168  		})
   169  
   170  		Convey("a patch against v3/t4 should include v1, v2, t3, t2, and t1", func() {
   171  			pairs := IncludePatchDependencies(p, []TVPair{{"v3", "t4"}})
   172  			So(len(pairs), ShouldEqual, 7)
   173  
   174  			So(pairs, shouldContainPair, TVPair{"v3", "t4"})
   175  			// requires t3 on the other variants
   176  			So(pairs, shouldContainPair, TVPair{"v1", "t3"})
   177  			So(pairs, shouldContainPair, TVPair{"v2", "t3"})
   178  
   179  			// t3 requires all the others
   180  			So(pairs, shouldContainPair, TVPair{"v1", "t2"})
   181  			So(pairs, shouldContainPair, TVPair{"v1", "t1"})
   182  			So(pairs, shouldContainPair, TVPair{"v2", "t2"})
   183  			So(pairs, shouldContainPair, TVPair{"v2", "t1"})
   184  
   185  		})
   186  
   187  		Convey("a patch against v4/t5 should include v1, v2, v3, t4, t3, t2, and t1", func() {
   188  			pairs := IncludePatchDependencies(p, []TVPair{{"v4", "t5"}})
   189  			So(len(pairs), ShouldEqual, 8)
   190  			So(pairs, shouldContainPair, TVPair{"v4", "t5"})
   191  			So(pairs, shouldContainPair, TVPair{"v1", "t1"})
   192  			So(pairs, shouldContainPair, TVPair{"v1", "t2"})
   193  			So(pairs, shouldContainPair, TVPair{"v1", "t3"})
   194  			So(pairs, shouldContainPair, TVPair{"v2", "t1"})
   195  			So(pairs, shouldContainPair, TVPair{"v2", "t2"})
   196  			So(pairs, shouldContainPair, TVPair{"v2", "t3"})
   197  			So(pairs, shouldContainPair, TVPair{"v3", "t4"})
   198  		})
   199  	})
   200  
   201  	Convey("With a project task config with required tasks", t, func() {
   202  		all := []BuildVariantTask{{Name: "1"}, {Name: "2"}, {Name: "3"},
   203  			{Name: "before"}, {Name: "after"}}
   204  		beforeDep := []TaskDependency{{Name: "before"}}
   205  		p := &Project{
   206  			Tasks: []ProjectTask{
   207  				{Name: "before", Requires: []TaskRequirement{{Name: "after"}}},
   208  				{Name: "1", DependsOn: beforeDep},
   209  				{Name: "2", DependsOn: beforeDep},
   210  				{Name: "3", DependsOn: beforeDep},
   211  				{Name: "after", DependsOn: []TaskDependency{
   212  					{Name: "before"},
   213  					{Name: "1", PatchOptional: true},
   214  					{Name: "2", PatchOptional: true},
   215  					{Name: "3", PatchOptional: true},
   216  				}},
   217  			},
   218  			BuildVariants: []BuildVariant{
   219  				{Name: "v1", Tasks: all},
   220  				{Name: "v2", Tasks: all},
   221  			},
   222  		}
   223  
   224  		Convey("scheduling the 'before' task should also schedule 'after'", func() {
   225  			pairs := IncludePatchDependencies(p, []TVPair{{"v1", "before"}})
   226  			So(len(pairs), ShouldEqual, 2)
   227  			So(pairs, shouldContainPair, TVPair{"v1", "before"})
   228  			So(pairs, shouldContainPair, TVPair{"v1", "after"})
   229  		})
   230  		Convey("scheduling the middle tasks should include 'before' and 'after'", func() {
   231  			Convey("for '1'", func() {
   232  				pairs := IncludePatchDependencies(p, []TVPair{{"v1", "1"}})
   233  				So(len(pairs), ShouldEqual, 3)
   234  				So(pairs, shouldContainPair, TVPair{"v1", "before"})
   235  				So(pairs, shouldContainPair, TVPair{"v1", "after"})
   236  				So(pairs, shouldContainPair, TVPair{"v1", "1"})
   237  			})
   238  			Convey("for '1' '2' '3'", func() {
   239  				pairs := IncludePatchDependencies(p, []TVPair{{"v1", "1"}, {"v1", "2"}, {"v1", "3"}})
   240  
   241  				So(len(pairs), ShouldEqual, 5)
   242  				So(pairs, shouldContainPair, TVPair{"v1", "before"})
   243  				So(pairs, shouldContainPair, TVPair{"v1", "1"})
   244  				So(pairs, shouldContainPair, TVPair{"v1", "2"})
   245  				So(pairs, shouldContainPair, TVPair{"v1", "3"})
   246  				So(pairs, shouldContainPair, TVPair{"v1", "after"})
   247  			})
   248  		})
   249  	})
   250  	Convey("With a project task config with cyclical requirements", t, func() {
   251  		all := []BuildVariantTask{{Name: "1"}, {Name: "2"}, {Name: "3"}}
   252  		p := &Project{
   253  			Tasks: []ProjectTask{
   254  				{Name: "1", Requires: []TaskRequirement{{Name: "2"}, {Name: "3"}}},
   255  				{Name: "2", Requires: []TaskRequirement{{Name: "1"}, {Name: "3"}}},
   256  				{Name: "3", Requires: []TaskRequirement{{Name: "2"}, {Name: "1"}}},
   257  			},
   258  			BuildVariants: []BuildVariant{
   259  				{Name: "v1", Tasks: all},
   260  				{Name: "v2", Tasks: all},
   261  			},
   262  		}
   263  		Convey("all tasks should be scheduled no matter which is initially added", func() {
   264  			Convey("for '1'", func() {
   265  				pairs := IncludePatchDependencies(p, []TVPair{{"v1", "1"}})
   266  				So(len(pairs), ShouldEqual, 3)
   267  				So(pairs, shouldContainPair, TVPair{"v1", "1"})
   268  				So(pairs, shouldContainPair, TVPair{"v1", "2"})
   269  				So(pairs, shouldContainPair, TVPair{"v1", "3"})
   270  			})
   271  			Convey("for '2'", func() {
   272  				pairs := IncludePatchDependencies(p, []TVPair{{"v1", "2"}, {"v2", "2"}})
   273  				So(len(pairs), ShouldEqual, 6)
   274  				So(pairs, shouldContainPair, TVPair{"v1", "1"})
   275  				So(pairs, shouldContainPair, TVPair{"v1", "2"})
   276  				So(pairs, shouldContainPair, TVPair{"v1", "3"})
   277  				So(pairs, shouldContainPair, TVPair{"v2", "1"})
   278  				So(pairs, shouldContainPair, TVPair{"v2", "2"})
   279  				So(pairs, shouldContainPair, TVPair{"v2", "3"})
   280  			})
   281  			Convey("for '3'", func() {
   282  				pairs := IncludePatchDependencies(p, []TVPair{{"v2", "3"}})
   283  				So(len(pairs), ShouldEqual, 3)
   284  				So(pairs, shouldContainPair, TVPair{"v2", "1"})
   285  				So(pairs, shouldContainPair, TVPair{"v2", "2"})
   286  				So(pairs, shouldContainPair, TVPair{"v2", "3"})
   287  			})
   288  		})
   289  	})
   290  	Convey("With a project task config that requires a non-patchable task", t, func() {
   291  		p := &Project{
   292  			Tasks: []ProjectTask{
   293  				{Name: "1", Requires: []TaskRequirement{{Name: "2"}}},
   294  				{Name: "2", Patchable: new(bool)},
   295  			},
   296  			BuildVariants: []BuildVariant{
   297  				{Name: "v1", Tasks: []BuildVariantTask{{Name: "1"}, {Name: "2"}}},
   298  			},
   299  		}
   300  		Convey("the non-patchable task should not be added", func() {
   301  			pairs := IncludePatchDependencies(p, []TVPair{{"v1", "1"}})
   302  
   303  			So(len(pairs), ShouldEqual, 0)
   304  		})
   305  	})
   306  
   307  }