go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipkg/base/actions/copy_test.go (about) 1 // Copyright 2023 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 actions 16 17 import ( 18 "context" 19 "embed" 20 "io" 21 "io/fs" 22 "os" 23 "path/filepath" 24 "runtime" 25 "testing" 26 27 "go.chromium.org/luci/cipkg/core" 28 "go.chromium.org/luci/cipkg/internal/testutils" 29 "go.chromium.org/luci/common/system/environ" 30 31 . "github.com/smartystreets/goconvey/convey" 32 ) 33 34 func TestProcessCopy(t *testing.T) { 35 Convey("Test action processor for copy", t, func() { 36 ap := NewActionProcessor() 37 pm := testutils.NewMockPackageManage("") 38 39 Convey("ok", func() { 40 copy := &core.ActionFilesCopy{ 41 Files: map[string]*core.ActionFilesCopy_Source{ 42 "test/file": { 43 Content: &core.ActionFilesCopy_Source_Local_{ 44 Local: &core.ActionFilesCopy_Source_Local{Path: "something"}, 45 }, 46 Mode: uint32(fs.ModeSymlink), 47 }, 48 }, 49 } 50 51 pkg, err := ap.Process("", pm, &core.Action{ 52 Name: "copy", 53 Deps: []*core.Action{ 54 {Name: "something", Spec: &core.Action_Command{Command: &core.ActionCommand{}}}, 55 }, 56 Spec: &core.Action_Copy{Copy: copy}, 57 }) 58 So(err, ShouldBeNil) 59 60 checkReexecArg(pkg.Derivation.Args, copy) 61 So(environ.New(pkg.Derivation.Env).Get("something"), ShouldEqual, pkg.BuildDependencies[0].Handler.OutputDirectory()) 62 }) 63 64 // DerivationID shouldn't depend on path if version is set. 65 Convey("version", func() { 66 copy1 := &core.ActionFilesCopy{ 67 Files: map[string]*core.ActionFilesCopy_Source{ 68 "test/file": { 69 Content: &core.ActionFilesCopy_Source_Local_{ 70 Local: &core.ActionFilesCopy_Source_Local{Path: "something", Version: "abc"}, 71 }, 72 Mode: uint32(fs.ModeDir), 73 }, 74 }, 75 } 76 77 pkg1, err := ap.Process("", pm, &core.Action{ 78 Name: "copy", 79 Deps: []*core.Action{ 80 {Name: "something", Spec: &core.Action_Command{Command: &core.ActionCommand{}}}, 81 }, 82 Spec: &core.Action_Copy{Copy: copy1}, 83 }) 84 So(err, ShouldBeNil) 85 86 copy2 := &core.ActionFilesCopy{ 87 Files: map[string]*core.ActionFilesCopy_Source{ 88 "test/file": { 89 Content: &core.ActionFilesCopy_Source_Local_{ 90 Local: &core.ActionFilesCopy_Source_Local{Path: "somethingelse", Version: "abc"}, 91 }, 92 Mode: uint32(fs.ModeDir), 93 }, 94 }, 95 } 96 97 pkg2, err := ap.Process("", pm, &core.Action{ 98 Name: "copy", 99 Deps: []*core.Action{ 100 {Name: "something", Spec: &core.Action_Command{Command: &core.ActionCommand{}}}, 101 }, 102 Spec: &core.Action_Copy{Copy: copy2}, 103 }) 104 So(err, ShouldBeNil) 105 106 So(pkg1.Derivation.Args, ShouldNotEqual, pkg2.Derivation.Args) 107 So(pkg1.DerivationID, ShouldEqual, pkg2.DerivationID) 108 }) 109 }) 110 } 111 112 //go:embed copy_test.go 113 var actionCopyTestEmbed embed.FS 114 115 func TestEmbedFSRegistration(t *testing.T) { 116 Convey("Test embedFS registration", t, func() { 117 e := newFilesCopyExecutor() 118 119 Convey("ok", func() { 120 So(func() { e.StoreEmbed("ref1", actionCopyTestEmbed) }, ShouldNotPanic) 121 _, ok := e.LoadEmbed("ref1") 122 So(ok, ShouldBeTrue) 123 _, ok = e.LoadEmbed("ref2") 124 So(ok, ShouldBeFalse) 125 }) 126 Convey("same ref", func() { 127 So(func() { e.StoreEmbed("ref1", actionCopyTestEmbed) }, ShouldNotPanic) 128 So(func() { e.StoreEmbed("ref2", actionCopyTestEmbed) }, ShouldNotPanic) 129 So(func() { e.StoreEmbed("ref1", actionCopyTestEmbed) }, ShouldPanic) 130 }) 131 Convey("sealed", func() { 132 So(func() { e.StoreEmbed("ref1", actionCopyTestEmbed) }, ShouldNotPanic) 133 e.LoadEmbed("ref1") 134 So(func() { e.StoreEmbed("ref2", actionCopyTestEmbed) }, ShouldPanic) 135 }) 136 137 Convey("global", func() { 138 RegisterEmbed("ref1", actionCopyTestEmbed) 139 _, ok := defaultFilesCopyExecutor.LoadEmbed("ref1") 140 So(ok, ShouldBeTrue) 141 }) 142 }) 143 } 144 145 func TestExecuteCopy(t *testing.T) { 146 Convey("Test execute action copy", t, func() { 147 ctx := context.Background() 148 e := newFilesCopyExecutor() 149 out := t.TempDir() 150 151 Convey("output", func() { 152 ctx = environ.New([]string{"somedrv=/abc/efg"}).SetInCtx(ctx) 153 a := &core.ActionFilesCopy{ 154 Files: map[string]*core.ActionFilesCopy_Source{ 155 filepath.FromSlash("test/file"): { 156 Content: &core.ActionFilesCopy_Source_Output_{ 157 Output: &core.ActionFilesCopy_Source_Output{Name: "somedrv", Path: "something"}, 158 }, 159 Mode: uint32(fs.ModeSymlink), 160 }, 161 }, 162 } 163 164 err := e.Execute(ctx, a, out) 165 So(err, ShouldBeNil) 166 167 { 168 l, err := os.Readlink(filepath.Join(out, "test/file")) 169 So(err, ShouldBeNil) 170 So(l, ShouldEqual, filepath.FromSlash("/abc/efg/something")) 171 } 172 }) 173 174 Convey("local", func() { 175 src := t.TempDir() 176 f, err := os.Create(filepath.Join(src, "file")) 177 So(err, ShouldBeNil) 178 _, err = f.WriteString("content") 179 So(err, ShouldBeNil) 180 err = f.Close() 181 So(err, ShouldBeNil) 182 183 Convey("dir", func() { 184 a := &core.ActionFilesCopy{ 185 Files: map[string]*core.ActionFilesCopy_Source{ 186 filepath.FromSlash("test/dir"): { 187 Content: &core.ActionFilesCopy_Source_Local_{ 188 Local: &core.ActionFilesCopy_Source_Local{Path: src}, 189 }, 190 Mode: uint32(fs.ModeDir), 191 }, 192 }, 193 } 194 195 err := e.Execute(ctx, a, out) 196 So(err, ShouldBeNil) 197 198 { 199 f, err := os.Open(filepath.Join(out, filepath.FromSlash("test/dir/file"))) 200 So(err, ShouldBeNil) 201 defer f.Close() 202 b, err := io.ReadAll(f) 203 So(err, ShouldBeNil) 204 So(string(b), ShouldEqual, "content") 205 } 206 }) 207 208 Convey("file", func() { 209 a := &core.ActionFilesCopy{ 210 Files: map[string]*core.ActionFilesCopy_Source{ 211 filepath.FromSlash("test/file"): { 212 Content: &core.ActionFilesCopy_Source_Local_{ 213 Local: &core.ActionFilesCopy_Source_Local{Path: filepath.Join(src, "file")}, 214 }, 215 Mode: uint32(0o666), 216 }, 217 }, 218 } 219 220 err := e.Execute(ctx, a, out) 221 So(err, ShouldBeNil) 222 223 { 224 f, err := os.Open(filepath.Join(out, filepath.FromSlash("test/file"))) 225 So(err, ShouldBeNil) 226 defer f.Close() 227 b, err := io.ReadAll(f) 228 So(err, ShouldBeNil) 229 So(string(b), ShouldEqual, "content") 230 } 231 }) 232 233 Convey("symlink", func() { 234 a := &core.ActionFilesCopy{ 235 Files: map[string]*core.ActionFilesCopy_Source{ 236 filepath.FromSlash("test/file"): { 237 Content: &core.ActionFilesCopy_Source_Local_{ 238 Local: &core.ActionFilesCopy_Source_Local{Path: "something"}, 239 }, 240 Mode: uint32(fs.ModeSymlink), 241 }, 242 }, 243 } 244 245 err := e.Execute(ctx, a, out) 246 So(err, ShouldBeNil) 247 248 { 249 l, err := os.Readlink(filepath.Join(out, "test/file")) 250 So(err, ShouldBeNil) 251 So(l, ShouldEqual, filepath.FromSlash("something")) 252 } 253 }) 254 }) 255 256 Convey("embed", func() { 257 e.StoreEmbed("something", actionCopyTestEmbed) 258 259 a := &core.ActionFilesCopy{ 260 Files: map[string]*core.ActionFilesCopy_Source{ 261 filepath.FromSlash("test/files"): { 262 Content: &core.ActionFilesCopy_Source_Embed_{ 263 Embed: &core.ActionFilesCopy_Source_Embed{Ref: "something"}, 264 }, 265 Mode: uint32(fs.ModeDir), 266 }, 267 filepath.FromSlash("test/file"): { 268 Content: &core.ActionFilesCopy_Source_Embed_{ 269 Embed: &core.ActionFilesCopy_Source_Embed{Ref: "something", Path: "copy_test.go"}, 270 }, 271 Mode: 0o666, 272 }, 273 }, 274 } 275 276 err := e.Execute(ctx, a, out) 277 So(err, ShouldBeNil) 278 279 { 280 f, err := os.Open(filepath.Join(out, filepath.FromSlash("test/files/copy_test.go"))) 281 So(err, ShouldBeNil) 282 defer f.Close() 283 b, err := io.ReadAll(f) 284 So(err, ShouldBeNil) 285 So(string(b), ShouldContainSubstring, "Test copy ref embed") 286 } 287 { 288 f, err := os.Open(filepath.Join(out, filepath.FromSlash("test/file"))) 289 So(err, ShouldBeNil) 290 defer f.Close() 291 b, err := io.ReadAll(f) 292 So(err, ShouldBeNil) 293 So(string(b), ShouldContainSubstring, "Test copy ref embed") 294 } 295 }) 296 297 Convey("raw", func() { 298 Convey("symlink", func() { 299 a := &core.ActionFilesCopy{ 300 Files: map[string]*core.ActionFilesCopy_Source{ 301 filepath.FromSlash("test/file"): { 302 Content: &core.ActionFilesCopy_Source_Raw{Raw: []byte("something")}, 303 Mode: uint32(os.ModeSymlink), 304 }, 305 }, 306 } 307 308 e := newFilesCopyExecutor() 309 err := e.Execute(ctx, a, out) 310 So(err, ShouldNotBeNil) 311 So(err.Error(), ShouldContainSubstring, "symlink is not supported") 312 }) 313 314 Convey("dir", func() { 315 a := &core.ActionFilesCopy{ 316 Files: map[string]*core.ActionFilesCopy_Source{ 317 filepath.FromSlash("test/file"): { 318 Content: &core.ActionFilesCopy_Source_Raw{}, 319 Mode: uint32(fs.ModeDir), 320 }, 321 }, 322 } 323 324 e := newFilesCopyExecutor() 325 err := e.Execute(ctx, a, out) 326 So(err, ShouldBeNil) 327 328 { 329 info, err := os.Stat(filepath.Join(out, filepath.FromSlash("test/file"))) 330 So(err, ShouldBeNil) 331 So(info.IsDir(), ShouldBeTrue) 332 } 333 }) 334 335 Convey("file", func() { 336 a := &core.ActionFilesCopy{ 337 Files: map[string]*core.ActionFilesCopy_Source{ 338 filepath.FromSlash("test/file"): { 339 Content: &core.ActionFilesCopy_Source_Raw{Raw: []byte("something")}, 340 Mode: 0o777, 341 }, 342 }, 343 } 344 345 e := newFilesCopyExecutor() 346 err := e.Execute(ctx, a, out) 347 So(err, ShouldBeNil) 348 349 { 350 f, err := os.Open(filepath.Join(out, filepath.FromSlash("test/file"))) 351 So(err, ShouldBeNil) 352 defer f.Close() 353 b, err := io.ReadAll(f) 354 So(err, ShouldBeNil) 355 So(string(b), ShouldEqual, "something") 356 357 // Windows will ignore mode bits 358 if runtime.GOOS != "windows" { 359 info, err := f.Stat() 360 So(err, ShouldBeNil) 361 So(info.Mode().Perm(), ShouldEqual, 0o777) 362 } 363 } 364 }) 365 }) 366 }) 367 } 368 369 func TestReexecExecuteCopy(t *testing.T) { 370 Convey("Test re-execute action copy", t, func() { 371 ap := NewActionProcessor() 372 pm := testutils.NewMockPackageManage("") 373 ctx := context.Background() 374 out := t.TempDir() 375 376 pkg, err := ap.Process("", pm, &core.Action{ 377 Name: "copy", 378 Spec: &core.Action_Copy{Copy: &core.ActionFilesCopy{ 379 Files: map[string]*core.ActionFilesCopy_Source{ 380 filepath.FromSlash("test/file"): { 381 Content: &core.ActionFilesCopy_Source_Raw{Raw: []byte("something")}, 382 Mode: 0o777, 383 }, 384 }, 385 }}, 386 }) 387 So(err, ShouldBeNil) 388 389 runWithDrv(ctx, pkg.Derivation, out) 390 391 { 392 f, err := os.Open(filepath.Join(out, "test", "file")) 393 So(err, ShouldBeNil) 394 defer f.Close() 395 b, err := io.ReadAll(f) 396 So(err, ShouldBeNil) 397 So(string(b), ShouldEqual, "something") 398 // Windows will ignore mode bits 399 if runtime.GOOS != "windows" { 400 info, err := f.Stat() 401 So(err, ShouldBeNil) 402 So(info.Mode().Perm(), ShouldEqual, 0o777) 403 } 404 } 405 }) 406 }