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  }