Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <psteinhardt@gitlab.com>2022-01-11 16:53:43 +0300
committerPatrick Steinhardt <psteinhardt@gitlab.com>2022-01-14 17:25:03 +0300
commit1f74e9150cb8238cb45715a65a5b66e7b821bd2e (patch)
tree4d3922febc64877b2b836567283cb736be7e729c
parent8b8c314ddc9ec769db31a0b4603cc69c4f5b24ae (diff)
cmd/gitaly-wrapper: Add tests verifying the executable works as expected
While we now have a bunch of tests which verify that low-level components of gitaly-wrapper work as expected, we have no high-level tests which verify that it comes together nicely. Add a bunch of tests which verify that running the gitaly-wrapper produces desired results.
-rw-r--r--cmd/gitaly-wrapper/main_test.go174
-rw-r--r--internal/testhelper/testcfg/build.go5
2 files changed, 179 insertions, 0 deletions
diff --git a/cmd/gitaly-wrapper/main_test.go b/cmd/gitaly-wrapper/main_test.go
index 19a20f4a1..cad8fbceb 100644
--- a/cmd/gitaly-wrapper/main_test.go
+++ b/cmd/gitaly-wrapper/main_test.go
@@ -1,7 +1,10 @@
package main
import (
+ "bufio"
"errors"
+ "fmt"
+ "io"
"os"
"os/exec"
"path/filepath"
@@ -9,7 +12,9 @@ import (
"testing"
"github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/bootstrap"
"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg"
)
// TestStolenPid tests for regressions in https://gitlab.com/gitlab-org/gitaly/issues/1661
@@ -253,3 +258,172 @@ func TestIsProcessAlive(t *testing.T) {
})
})
}
+
+func TestRun(t *testing.T) {
+ binary := testcfg.BuildGitalyWrapper(t, testcfg.Build(t))
+
+ t.Run("missing arguments", func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ output, err := exec.CommandContext(ctx, binary).CombinedOutput()
+ require.Error(t, err)
+ require.Contains(t, string(output), "usage: ")
+ })
+
+ t.Run("missing PID file envvar", func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ output, err := exec.CommandContext(ctx, binary, "binary").CombinedOutput()
+ require.Error(t, err)
+ require.Contains(t, string(output), "missing pid file ENV variable")
+ })
+
+ t.Run("invalid executable", func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ pidPath := filepath.Join(testhelper.TempDir(t), "pid")
+
+ cmd := exec.CommandContext(ctx, binary, "does-not-exist")
+ cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", bootstrap.EnvPidFile, pidPath))
+
+ output, err := cmd.CombinedOutput()
+ require.Error(t, err)
+ require.Contains(t, string(output), "executable file not found in $PATH")
+ })
+
+ t.Run("adopting executable", func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ script := testhelper.WriteExecutable(t, filepath.Join(testhelper.TempDir(t), "script"), []byte(
+ `#!/usr/bin/env bash
+ echo ready
+ read wait_until_closed
+ `))
+
+ scriptCmd := exec.CommandContext(ctx, script)
+ scriptStdout, err := scriptCmd.StdoutPipe()
+ require.NoError(t, err)
+ scriptStdin, err := scriptCmd.StdinPipe()
+ require.NoError(t, err)
+
+ require.NoError(t, scriptCmd.Start())
+
+ // Read the first byte such that we know the process has been spawned.
+ _, err = scriptStdout.Read(make([]byte, 10))
+ require.NoError(t, err)
+
+ // Write the PID of the running process into the PID file. As a result, it should
+ // get adopted by gitaly-wrapper, which means it wouldn't try to execute it anew.
+ pidPath := filepath.Join(testhelper.TempDir(t), "pid")
+ require.NoError(t, os.WriteFile(pidPath, []byte(strconv.FormatInt(int64(scriptCmd.Process.Pid), 10)), 0o644))
+
+ // Run gitaly-script with a binary path whose basename matches, but which ultimately
+ // doesn't exist. This proves that it doesn't try to execute the script again.
+ wrapperCmd := exec.CommandContext(ctx, binary, "/does/not/exist/bash")
+ wrapperCmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", bootstrap.EnvPidFile, pidPath))
+ wrapperStdout, err := wrapperCmd.StdoutPipe()
+ require.NoError(t, err)
+ require.NoError(t, wrapperCmd.Start())
+
+ // We're now waiting for gitaly-wrapper to adopt the running script.
+ reader := bufio.NewReader(wrapperStdout)
+ for _, expectedLine := range []string{
+ "Wrapper started",
+ "finding process",
+ "adopting a process",
+ } {
+ line, err := reader.ReadBytes('\n')
+ require.NoError(t, err, "reading expected line %q",
+ expectedLine)
+ require.Contains(t, string(line), expectedLine)
+ }
+
+ // The script has been adopted, so we can now close its stdin and thus cause it to
+ // exit.
+ testhelper.MustClose(t, scriptStdin)
+ require.Error(t, scriptCmd.Wait())
+
+ // As a result, the wrapper should also exit successfully.
+ require.NoError(t, wrapperCmd.Wait())
+ })
+
+ t.Run("spawning executable", func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ script := testhelper.WriteExecutable(t, filepath.Join(testhelper.TempDir(t), "script"), []byte(
+ `#!/usr/bin/env bash
+ echo "I have been executed"
+ `))
+
+ pidPath := filepath.Join(testhelper.TempDir(t), "pid")
+
+ cmd := exec.CommandContext(ctx, binary, script)
+ cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", bootstrap.EnvPidFile, pidPath))
+ output, err := cmd.CombinedOutput()
+ require.NoError(t, err)
+
+ require.Contains(t, string(output), "spawning a process")
+ require.Contains(t, string(output), "I have been executed")
+
+ require.NoFileExists(t, pidPath)
+ })
+
+ t.Run("spawning executable with missing process", func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ script := testhelper.WriteExecutable(t, filepath.Join(testhelper.TempDir(t), "script"), []byte(
+ `#!/usr/bin/env bash
+ echo "I have been executed"
+ `))
+
+ pidPath := filepath.Join(testhelper.TempDir(t), "pid")
+ require.NoError(t, os.WriteFile(pidPath, []byte("12345"), 0o644))
+
+ cmd := exec.CommandContext(ctx, binary, script)
+ cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", bootstrap.EnvPidFile, pidPath))
+
+ output, err := cmd.CombinedOutput()
+ require.NoError(t, err)
+ require.Contains(t, string(output), "spawning a process")
+ require.Contains(t, string(output), "I have been executed")
+ })
+
+ t.Run("spawning executable with zombie process", func(t *testing.T) {
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ script := testhelper.WriteExecutable(t, filepath.Join(testhelper.TempDir(t), "script"), []byte(
+ `#!/usr/bin/env bash
+ echo "I have been executed"
+ `))
+
+ scriptCmd := exec.CommandContext(ctx, script)
+ scriptStdout, err := scriptCmd.StdoutPipe()
+ require.NoError(t, err)
+ require.NoError(t, scriptCmd.Start())
+
+ // Read until we get an EOF, which means that the script has terminated. It's now in
+ // a zombie state because we don't call `Wait()`.
+ _, err = io.ReadAll(scriptStdout)
+ require.NoError(t, err)
+
+ pidPath := filepath.Join(testhelper.TempDir(t), "pid")
+ require.NoError(t, os.WriteFile(pidPath, []byte(strconv.FormatInt(int64(scriptCmd.Process.Pid), 10)), 0o644))
+
+ cmd := exec.CommandContext(ctx, binary, script)
+ cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", bootstrap.EnvPidFile, pidPath))
+
+ output, err := cmd.CombinedOutput()
+ require.NoError(t, err)
+ require.Contains(t, string(output), "spawning a process")
+ require.Contains(t, string(output), "I have been executed")
+
+ require.NoError(t, scriptCmd.Wait())
+ })
+}
diff --git a/internal/testhelper/testcfg/build.go b/internal/testhelper/testcfg/build.go
index c18a9fae5..ce958358c 100644
--- a/internal/testhelper/testcfg/build.go
+++ b/internal/testhelper/testcfg/build.go
@@ -33,6 +33,11 @@ func BuildGitalyGit2Go(t testing.TB, cfg config.Cfg) string {
return symlinkPath
}
+// BuildGitalyWrapper builds the gitaly-wrapper command and installs it into the binary directory.
+func BuildGitalyWrapper(t *testing.T, cfg config.Cfg) string {
+ return BuildBinary(t, cfg.BinDir, gitalyCommandPath("gitaly-wrapper"))
+}
+
// BuildGitalyLFSSmudge builds the gitaly-lfs-smudge command and installs it into the binary
// directory.
func BuildGitalyLFSSmudge(t *testing.T, cfg config.Cfg) string {