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:
authorSami Hiltunen <shiltunen@gitlab.com>2023-04-06 19:56:27 +0300
committerSami Hiltunen <shiltunen@gitlab.com>2023-04-17 17:25:32 +0300
commitf5e29f898471886311f8f5e44f67e0fa48546998 (patch)
tree1d8048239886c76b1364b677798c94948727b1d5
parentb7492443beb518d87a30ad0bd49a7df92e17c152 (diff)
Support parsing content in RequireDirectoryState
RequireDirectoryState allows for asserting that a directory matches the expected state. Right now it only supports matching file content against expected bytes. This is inconvenient as it can be difficult to make sense of binary file formats on failure and say what exactly is wrong without looking at the file itself. Further, some files may be physically different but semantically the same. An example of such a file would be Git's pack files, where the physical format may change for example depending on the delta compression but the actual contained objects are the same. This commit adds support for parsing the file's content with a parser passed in the assertion. The parser takes the file's contents and returns a parsed object of any type. The parsed type is ultimately asserted for equality with the expected content. With this in place, we can for example assert the content of pack files rather than than the physical file bytes. Missing tests are added for the function.
-rw-r--r--internal/testhelper/directory.go24
-rw-r--r--internal/testhelper/directory_test.go174
2 files changed, 193 insertions, 5 deletions
diff --git a/internal/testhelper/directory.go b/internal/testhelper/directory.go
index f4c8c6110..c56e66038 100644
--- a/internal/testhelper/directory.go
+++ b/internal/testhelper/directory.go
@@ -19,7 +19,11 @@ 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 []byte
+ Content any
+ // ParseContent is a function that receives the file's 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, content []byte) any
}
// DirectoryState models the contents of a directory. The key is relative of the entry in
@@ -56,8 +60,13 @@ func RequireDirectoryState(tb testing.TB, rootDirectory, relativeDirectory strin
}
if entry.Type().IsRegular() {
- actualEntry.Content, err = os.ReadFile(path)
+ 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, content)
+ }
}
actual[trimmedPath] = actualEntry
@@ -65,11 +74,16 @@ func RequireDirectoryState(tb testing.TB, rootDirectory, relativeDirectory strin
return nil
}))
- if expected == nil {
- expected = DirectoryState{}
+ // 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, expected, actual)
+ require.Equal(tb, expectedCopy, actual)
}
// RequireTarState asserts that the provided tarball contents matches the expected state.
diff --git a/internal/testhelper/directory_test.go b/internal/testhelper/directory_test.go
new file mode 100644
index 000000000..81022cb32
--- /dev/null
+++ b/internal/testhelper/directory_test.go
@@ -0,0 +1,174 @@
+package testhelper
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/helper/perm"
+)
+
+type tbRecorder struct {
+ // Embed a nil TB as we'd rather panic if some calls that were
+ // made were not captured by the recorder.
+ testing.TB
+ tb testing.TB
+
+ errorMessage string
+ helper bool
+ failNow bool
+}
+
+func (r *tbRecorder) Name() string {
+ return r.tb.Name()
+}
+
+func (r *tbRecorder) Errorf(format string, args ...any) {
+ r.errorMessage = fmt.Sprintf(format, args...)
+}
+
+func (r *tbRecorder) Helper() {
+ r.helper = true
+}
+
+func (r *tbRecorder) FailNow() {
+ r.failNow = true
+}
+
+func TestRequireDirectoryState(t *testing.T) {
+ umask := perm.GetUmask()
+
+ t.Parallel()
+
+ rootDir := t.TempDir()
+
+ relativePath := "assertion-root"
+
+ require.NoError(t,
+ os.MkdirAll(
+ filepath.Join(rootDir, relativePath, "dir-a"),
+ fs.ModePerm,
+ ),
+ )
+ require.NoError(t,
+ os.MkdirAll(
+ filepath.Join(rootDir, relativePath, "dir-b"),
+ perm.PrivateDir,
+ ),
+ )
+ require.NoError(t,
+ os.WriteFile(
+ filepath.Join(rootDir, relativePath, "dir-a", "unparsed-file"),
+ []byte("raw content"),
+ fs.ModePerm,
+ ),
+ )
+ require.NoError(t,
+ os.WriteFile(
+ filepath.Join(rootDir, relativePath, "parsed-file"),
+ []byte("raw content"),
+ perm.PrivateFile,
+ ),
+ )
+
+ for _, tc := range []struct {
+ desc string
+ modifyAssertion func(DirectoryState)
+ expectedErrorMessage string
+ }{
+ {
+ desc: "correct assertion",
+ modifyAssertion: func(DirectoryState) {},
+ },
+ {
+ desc: "unexpected directory",
+ modifyAssertion: func(state DirectoryState) {
+ delete(state, "/assertion-root")
+ },
+ expectedErrorMessage: `+ (string) (len=15) "/assertion-root": (testhelper.DirectoryEntry)`,
+ },
+ {
+ desc: "unexpected file",
+ modifyAssertion: func(state DirectoryState) {
+ delete(state, "/assertion-root/dir-a/unparsed-file")
+ },
+ expectedErrorMessage: `+ (string) (len=35) "/assertion-root/dir-a/unparsed-file": (testhelper.DirectoryEntry)`,
+ },
+ {
+ desc: "wrong mode",
+ modifyAssertion: func(state DirectoryState) {
+ modified := state["/assertion-root/dir-b"]
+ modified.Mode = fs.ModePerm
+ state["/assertion-root/dir-b"] = modified
+ },
+ expectedErrorMessage: `- Mode: (fs.FileMode)`,
+ },
+ {
+ desc: "wrong unparsed content",
+ modifyAssertion: func(state DirectoryState) {
+ modified := state["/assertion-root/dir-a/unparsed-file"]
+ modified.Content = "incorrect content"
+ state["/assertion-root/dir-a/unparsed-file"] = modified
+ },
+ expectedErrorMessage: `- Content: (string) (len=17) "incorrect content",
+ + Content: ([]uint8) (len=11) {
+ + 00000000 72 61 77 20 63 6f 6e 74 65 6e 74 |raw content|
+ + }`,
+ },
+ {
+ desc: "wrong parsed content",
+ modifyAssertion: func(state DirectoryState) {
+ modified := state["/assertion-root/parsed-file"]
+ modified.Content = "incorrect content"
+ state["/assertion-root/parsed-file"] = modified
+ },
+ expectedErrorMessage: `- Content: (string) (len=17) "incorrect content",
+ + Content: (string) (len=14) "parsed content"`,
+ },
+ {
+ desc: "missing entry",
+ modifyAssertion: func(state DirectoryState) {
+ state["/does/not/exist/on/disk"] = DirectoryEntry{}
+ },
+ expectedErrorMessage: `- (string) (len=23) "/does/not/exist/on/disk": (testhelper.DirectoryEntry)`,
+ },
+ } {
+ tc := tc
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+ expectedState := DirectoryState{
+ "/assertion-root": {Mode: umask.Mask(fs.ModeDir | fs.ModePerm)},
+ "/assertion-root/parsed-file": {
+ Mode: umask.Mask(perm.PrivateFile),
+ Content: "parsed content",
+ ParseContent: func(tb testing.TB, content []byte) any {
+ return "parsed content"
+ },
+ },
+ "/assertion-root/dir-a": {Mode: umask.Mask(fs.ModeDir | fs.ModePerm)},
+ "/assertion-root/dir-a/unparsed-file": {Mode: umask.Mask(fs.ModePerm), Content: []byte("raw content")},
+ "/assertion-root/dir-b": {Mode: umask.Mask(fs.ModeDir | perm.PrivateDir)},
+ }
+
+ tc.modifyAssertion(expectedState)
+
+ recordedTB := &tbRecorder{tb: t}
+ RequireDirectoryState(recordedTB, rootDir, relativePath, expectedState)
+ if tc.expectedErrorMessage != "" {
+ require.Contains(t, recordedTB.errorMessage, tc.expectedErrorMessage)
+ require.True(t, recordedTB.failNow)
+ } else {
+ require.Empty(t, recordedTB.errorMessage)
+ require.False(t, recordedTB.failNow)
+ }
+ require.True(t, recordedTB.helper)
+ require.NotNil(t,
+ expectedState["/assertion-root/parsed-file"].ParseContent,
+ "ParseContent should still be set on the original expected state",
+ )
+ })
+ }
+}