1package testhelper 2 3import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "sync" 13 "testing" 14 "time" 15 16 log "github.com/sirupsen/logrus" 17 "github.com/stretchr/testify/require" 18 "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" 19 gitalylog "gitlab.com/gitlab-org/gitaly/v14/internal/log" 20) 21 22var ( 23 configureOnce sync.Once 24 testDirectory string 25) 26 27// Configure sets up the global test configuration. On failure, 28// terminates the program. 29func Configure() func() { 30 configureOnce.Do(func() { 31 gitalylog.Configure(gitalylog.Loggers, "json", "panic") 32 33 var err error 34 testDirectory, err = ioutil.TempDir("", "gitaly-") 35 if err != nil { 36 log.Fatal(err) 37 } 38 39 for _, f := range []func() error{ 40 ConfigureGit, 41 } { 42 if err := f(); err != nil { 43 os.RemoveAll(testDirectory) 44 log.Fatalf("error configuring tests: %v", err) 45 } 46 } 47 }) 48 49 return func() { 50 if err := os.RemoveAll(testDirectory); err != nil { 51 log.Fatalf("error removing test directory: %v", err) 52 } 53 } 54} 55 56// ConfigureGit configures git for test purpose 57func ConfigureGit() error { 58 // We cannot use gittest here given that we ain't got no config yet. We thus need to 59 // manually resolve the git executable, which is either stored in below envvar if 60 // executed via our Makefile, or else just git as resolved via PATH. 61 gitPath := "git" 62 if path, ok := os.LookupEnv("GITALY_TESTING_GIT_BINARY"); ok { 63 gitPath = path 64 } 65 66 // Unset environment variables which have an effect on Git itself. 67 cmd := exec.Command(gitPath, "rev-parse", "--local-env-vars") 68 envvars, err := cmd.CombinedOutput() 69 if err != nil { 70 return fmt.Errorf("error computing local envvars: %w", err) 71 } 72 for _, envvar := range strings.Split(string(envvars), "\n") { 73 if err := os.Unsetenv(envvar); err != nil { 74 return fmt.Errorf("error unsetting envvar: %w", err) 75 } 76 } 77 78 _, currentFile, _, ok := runtime.Caller(0) 79 if !ok { 80 return fmt.Errorf("could not get caller info") 81 } 82 83 // Set both GOCACHE and GOPATH to the currently active settings to not 84 // have them be overridden by changing our home directory. default it 85 for _, envvar := range []string{"GOCACHE", "GOPATH"} { 86 cmd := exec.Command("go", "env", envvar) 87 88 output, err := cmd.Output() 89 if err != nil { 90 return err 91 } 92 93 err = os.Setenv(envvar, strings.TrimSpace(string(output))) 94 if err != nil { 95 return err 96 } 97 } 98 99 testHome := filepath.Join(filepath.Dir(currentFile), "testdata/home") 100 // overwrite HOME env variable so user global .gitconfig doesn't influence tests 101 return os.Setenv("HOME", testHome) 102} 103 104// ConfigureRuby configures Ruby settings for test purposes at run time. 105func ConfigureRuby(cfg *config.Cfg) error { 106 if dir := os.Getenv("GITALY_TEST_RUBY_DIR"); len(dir) > 0 { 107 // Sometimes runtime.Caller is unreliable. This environment variable provides a bypass. 108 cfg.Ruby.Dir = dir 109 } else { 110 _, currentFile, _, ok := runtime.Caller(0) 111 if !ok { 112 return fmt.Errorf("could not get caller info") 113 } 114 cfg.Ruby.Dir = filepath.Join(filepath.Dir(currentFile), "../../ruby") 115 } 116 117 if err := cfg.ConfigureRuby(); err != nil { 118 log.Fatalf("validate ruby config: %v", err) 119 } 120 121 return nil 122} 123 124// ConfigureGitalyGit2GoBin configures the gitaly-git2go command for tests 125func ConfigureGitalyGit2GoBin(t testing.TB, cfg config.Cfg) { 126 buildBinary(t, cfg.BinDir, "gitaly-git2go") 127} 128 129// ConfigureGitalyLfsSmudge configures the gitaly-lfs-smudge command for tests 130func ConfigureGitalyLfsSmudge(t *testing.T, outputDir string) { 131 buildCommand(t, outputDir, "gitaly-lfs-smudge") 132} 133 134// ConfigureGitalyHooksBin builds gitaly-hooks command for tests for the cfg. 135func ConfigureGitalyHooksBin(t testing.TB, cfg config.Cfg) { 136 buildBinary(t, cfg.BinDir, "gitaly-hooks") 137} 138 139// ConfigureGitalySSHBin builds gitaly-ssh command for tests for the cfg. 140func ConfigureGitalySSHBin(t testing.TB, cfg config.Cfg) { 141 buildBinary(t, cfg.BinDir, "gitaly-ssh") 142} 143 144func buildBinary(t testing.TB, dstDir, name string) { 145 // binsPath is a shared between all tests location where all compiled binaries should be placed 146 binsPath := filepath.Join(testDirectory, "bins") 147 // binPath is a path to a specific binary file 148 binPath := filepath.Join(binsPath, name) 149 // lockPath is a path to the special lock file used to prevent parallel build runs 150 lockPath := binPath + ".lock" 151 152 defer func() { 153 if !t.Failed() { 154 // copy compiled binary to the destination folder 155 require.NoError(t, os.MkdirAll(dstDir, os.ModePerm)) 156 MustRunCommand(t, nil, "cp", binPath, dstDir) 157 } 158 }() 159 160 require.NoError(t, os.MkdirAll(binsPath, os.ModePerm)) 161 162 lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL, 0600) 163 if err != nil { 164 if !errors.Is(err, os.ErrExist) { 165 require.FailNow(t, err.Error()) 166 } 167 // another process is creating the binary at the moment, wait for it to complete (5s) 168 for i := 0; i < 50; i++ { 169 if _, err := os.Stat(binPath); err != nil { 170 if !errors.Is(err, os.ErrExist) { 171 require.NoError(t, err) 172 } 173 time.Sleep(100 * time.Millisecond) 174 continue 175 } 176 // binary was created 177 return 178 } 179 require.FailNow(t, "another process is creating binary for too long") 180 } 181 defer func() { require.NoError(t, os.Remove(lockPath)) }() 182 require.NoError(t, lockFile.Close()) 183 184 if _, err := os.Stat(binPath); err != nil { 185 if !errors.Is(err, os.ErrNotExist) { 186 // something went wrong and for some reason the binary already exists 187 require.FailNow(t, err.Error()) 188 } 189 buildCommand(t, binsPath, name) 190 } 191} 192 193func buildCommand(t testing.TB, outputDir, cmd string) { 194 if outputDir == "" { 195 log.Fatal("BinDir must be set") 196 } 197 198 goBuildArgs := []string{ 199 "build", 200 "-tags", "static,system_libgit2", 201 "-o", filepath.Join(outputDir, cmd), 202 fmt.Sprintf("gitlab.com/gitlab-org/gitaly/v14/cmd/%s", cmd), 203 } 204 MustRunCommand(t, nil, "go", goBuildArgs...) 205} 206