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 }