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

directory.go « testhelper « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: f66862c76b9bf561964a919c422c18cea019e6ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package testhelper

import (
	"archive/tar"
	"bytes"
	"errors"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/gitaly/v16/internal/helper/perm"
)

// DirectoryEntry models an entry in a directory.
type DirectoryEntry struct {
	// Mode is the file mode of the entry.
	Mode fs.FileMode
	// Content contains the file content if this is a regular file.
	Content any
	// ParseContent is a function that receives the file's absolute path, actual content
	// and returns it parsed into the expected form. The returned value is ultimately
	// asserted for equality with the Content.
	ParseContent func(tb testing.TB, path string, content []byte) any
}

// DirectoryState models the contents of a directory. The key is relative of the entry in
// the rootDirectory as described on RequireDirectoryState.
type DirectoryState map[string]DirectoryEntry

// RequireDirectoryState asserts that given directory matches the expected state. The rootDirectory and
// relativeDirectory are joined together to decide the directory to walk. rootDirectory is trimmed out of the
// paths in the DirectoryState to make assertions easier by using the relative paths only. For example, given
// `/root-path` and `relative/path`, the directory walked is `/root-path/relative/path`. The paths in DirectoryState
// trim the root prefix, thus they should be like `/relative/path/...`. The beginning point of the walk has path "/".
func RequireDirectoryState(tb testing.TB, rootDirectory, relativeDirectory string, expected DirectoryState) {
	tb.Helper()

	actual := DirectoryState{}
	require.NoError(tb, filepath.WalkDir(filepath.Join(rootDirectory, relativeDirectory), func(path string, entry os.DirEntry, err error) error {
		if os.IsNotExist(err) {
			return nil
		}
		require.NoError(tb, err)

		trimmedPath := strings.TrimPrefix(path, rootDirectory)
		if trimmedPath == "" {
			// Store the walked directory itself as "/". Less confusing than having it be
			// an empty string.
			trimmedPath = string(os.PathSeparator)
		}

		info, err := entry.Info()
		require.NoError(tb, err)

		actualEntry := DirectoryEntry{
			Mode: info.Mode(),
		}

		if entry.Type().IsRegular() {
			content, err := os.ReadFile(path)
			require.NoError(tb, err)

			actualEntry.Content = content
			if expectedEntry, ok := expected[trimmedPath]; ok && expectedEntry.ParseContent != nil {
				actualEntry.Content = expectedEntry.ParseContent(tb, path, content)
			}
		}

		actual[trimmedPath] = actualEntry

		return nil
	}))

	// Functions are never equal unless they are nil, see https://pkg.go.dev/reflect#DeepEqual.
	// So to check of equality we set the ParseContent functions to nil.
	// We use a copy so we don't unexpectedly modify the original.
	expectedCopy := make(DirectoryState, len(expected))
	for key, value := range expected {
		value.ParseContent = nil
		expectedCopy[key] = value
	}

	require.Equal(tb, expectedCopy, actual)
}

// RequireTarState asserts that the provided tarball contents matches the expected state.
func RequireTarState(tb testing.TB, tarball io.Reader, expected DirectoryState) {
	tb.Helper()

	actual := DirectoryState{}
	tr := tar.NewReader(tarball)
	for {
		header, err := tr.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		require.NoError(tb, err)

		actualEntry := DirectoryEntry{
			Mode: fs.FileMode(header.Mode),
		}

		if header.Typeflag == tar.TypeReg {
			b, err := io.ReadAll(tr)
			require.NoError(tb, err)

			actualEntry.Content = b
		}

		actual[header.Name] = actualEntry
	}

	if expected == nil {
		expected = DirectoryState{}
	}

	require.Equal(tb, expected, actual)
}

// MustCreateCustomHooksTar creates a temporary custom hooks tar archive on disk
// for testing and returns its file path.
func MustCreateCustomHooksTar(tb testing.TB) io.Reader {
	tb.Helper()

	writeFile := func(writer *tar.Writer, path string, mode fs.FileMode, content string) {
		require.NoError(tb, writer.WriteHeader(&tar.Header{
			Name: path,
			Mode: int64(mode),
			Size: int64(len(content)),
		}))
		_, err := writer.Write([]byte(content))
		require.NoError(tb, err)
	}

	var buffer bytes.Buffer
	writer := tar.NewWriter(&buffer)
	defer MustClose(tb, writer)

	writeFile(writer, "custom_hooks/pre-commit", perm.SharedExecutable, "pre-commit content")
	writeFile(writer, "custom_hooks/pre-push", perm.SharedExecutable, "pre-push content")
	writeFile(writer, "custom_hooks/pre-receive", perm.SharedExecutable, "pre-receive content")

	return &buffer
}