-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add symlink info to file metadata #153
Changes from 4 commits
6d88e63
911fda4
85722c7
8743bc7
b9faa1d
e3df0d0
552b0f1
97768db
f724e1c
447e40b
9d3ac56
32aa7fc
12076a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,37 +1,55 @@ | ||||
package filemetadata | ||||
|
||||
import ( | ||||
"crypto/rand" | ||||
"encoding/hex" | ||||
"errors" | ||||
"io/ioutil" | ||||
"os" | ||||
"path/filepath" | ||||
"testing" | ||||
|
||||
"github.com/bazelbuild/remote-apis-sdks/go/pkg/digest" | ||||
"github.com/google/go-cmp/cmp" | ||||
) | ||||
|
||||
func TestCompute(t *testing.T) { | ||||
type testFileParams struct { | ||||
contents string | ||||
executable bool | ||||
} | ||||
|
||||
// Used as a type enum | ||||
type testDirParams struct { | ||||
} | ||||
|
||||
func TestComputeFiles(t *testing.T) { | ||||
tests := []struct { | ||||
name string | ||||
contents string | ||||
executable bool | ||||
name string | ||||
*testFileParams | ||||
}{ | ||||
{ | ||||
name: "empty", | ||||
contents: "", | ||||
name: "empty", | ||||
testFileParams: &testFileParams{ | ||||
contents: "", | ||||
}, | ||||
}, | ||||
{ | ||||
name: "non-executable", | ||||
contents: "bla", | ||||
name: "non-executable", | ||||
testFileParams: &testFileParams{ | ||||
contents: "bla", | ||||
}, | ||||
}, | ||||
{ | ||||
name: "executable", | ||||
contents: "foo", | ||||
executable: true, | ||||
name: "executable", | ||||
testFileParams: &testFileParams{ | ||||
contents: "foo", | ||||
executable: true, | ||||
}, | ||||
}, | ||||
} | ||||
for _, tc := range tests { | ||||
t.Run(tc.name, func(t *testing.T) { | ||||
filename, err := createFile(t, tc.executable, tc.contents) | ||||
filename, err := createFile(t, tc.testFileParams) | ||||
if err != nil { | ||||
t.Fatalf("Failed to create tmp file for testing digests: %v", err) | ||||
} | ||||
|
@@ -66,10 +84,95 @@ func TestComputeDirectory(t *testing.T) { | |||
} | ||||
} | ||||
|
||||
func createFile(t *testing.T, executable bool, contents string) (string, error) { | ||||
type testSymlinkCreationResult struct { | ||||
symlink string | ||||
target string | ||||
} | ||||
|
||||
func (sc *testSymlinkCreationResult) cleanup() { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If everything's under temp, why do you need to clean it up? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually to follow
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I follow... Do you mean in the next patch? I meant to simplify as instead of cleaning up manually, create everything under a TempDir directory -- pass this directory to everything that creates files or symlinks if you need to reuse it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry about the confusion. My point is that the existing file tests are doing this clean up manually (even if those filenames are temporary and unique), therefore I followed that for consistency. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, right, now I wonder why I did that :-) But sure, let's keep it, then. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, i actually removed the |
||||
os.RemoveAll(sc.symlink) | ||||
os.RemoveAll(sc.target) | ||||
} | ||||
|
||||
func TestComputeSymlinks(t *testing.T) { | ||||
tests := []struct { | ||||
name string | ||||
target interface{} // If unspecified, this is an invalid symlink | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some cleaner options for doing this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, actually i split this into three tests now. Sorry for mixing the logic before, hopefully it now gets simpler?
Ack. I created BTW, I prepended all the test structs with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think flattening things out will be simplest. Also, I don't think the rest of the package can use the test code, at least not if compiled with Bazel (not sure how go build works), so no need to prefix with test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ack, removed |
||||
}{ | ||||
{ | ||||
name: "valid", | ||||
target: &testFileParams{ | ||||
contents: "bla", | ||||
}, | ||||
}, | ||||
{ | ||||
name: "valid-executable", | ||||
target: &testFileParams{ | ||||
contents: "executable", | ||||
executable: true, | ||||
}, | ||||
}, | ||||
{ | ||||
name: "invalid-file", | ||||
}, | ||||
{ | ||||
name: "symlink-dir", | ||||
target: &testDirParams{}, | ||||
}, | ||||
} | ||||
for _, tc := range tests { | ||||
t.Run(tc.name, func(t *testing.T) { | ||||
symlinkResult, err := createSymlink(t, tc.target) | ||||
if err != nil { | ||||
t.Fatalf("Failed to create tmp symlink for testing digests: %v", err) | ||||
} | ||||
defer symlinkResult.cleanup() | ||||
|
||||
symlinkPath := symlinkResult.symlink | ||||
got := Compute(symlinkPath) | ||||
|
||||
if tc.target == nil { | ||||
if got.Err == nil || !got.Symlink.IsInvalid { | ||||
t.Errorf("Compute(%v) should fail because the symlink is invalid", symlinkPath) | ||||
} | ||||
if got.Symlink.Target != "" { | ||||
t.Errorf("Compute(%v) should fail because the symlink is invalid, got target: %s", symlinkPath, got.Symlink.Target) | ||||
} | ||||
return | ||||
} | ||||
|
||||
if _, ok := tc.target.(*testDirParams); ok { | ||||
if fe, ok := got.Err.(*FileError); !ok || !fe.IsDirectory { | ||||
t.Errorf("Compute(%v).Err = %v, want FileError{IsDirectory:true}", symlinkPath, got.Err) | ||||
} | ||||
return | ||||
} | ||||
|
||||
if got.Err != nil { | ||||
t.Errorf("Compute(%v) failed. Got error: %v", symlinkPath, got.Err) | ||||
} | ||||
|
||||
fileParams := tc.target.(*testFileParams) | ||||
want := &Metadata{ | ||||
Symlink: &SymlinkMetadata{ | ||||
Target: symlinkResult.target, | ||||
IsInvalid: false, | ||||
}, | ||||
Digest: digest.NewFromBlob([]byte(fileParams.contents)), | ||||
IsExecutable: fileParams.executable, | ||||
} | ||||
|
||||
if diff := cmp.Diff(want, got); diff != "" { | ||||
t.Errorf("Compute(%v) returned diff. (-want +got)\n%s", symlinkPath, diff) | ||||
} | ||||
}) | ||||
} | ||||
} | ||||
|
||||
func createFile(t *testing.T, fp *testFileParams) (string, error) { | ||||
t.Helper() | ||||
perm := os.FileMode(0666) | ||||
if executable { | ||||
if fp.executable { | ||||
perm = os.FileMode(0766) | ||||
} | ||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "") | ||||
|
@@ -83,8 +186,49 @@ func createFile(t *testing.T, executable bool, contents string) (string, error) | |||
return "", err | ||||
} | ||||
filename := tmpFile.Name() | ||||
if err = ioutil.WriteFile(filename, []byte(contents), os.ModeTemporary); err != nil { | ||||
if err = ioutil.WriteFile(filename, []byte(fp.contents), os.ModeTemporary); err != nil { | ||||
return "", err | ||||
} | ||||
return filename, nil | ||||
} | ||||
|
||||
func createSymlink(t *testing.T, target interface{}) (*testSymlinkCreationResult, error) { | ||||
t.Helper() | ||||
|
||||
invalidTarget := target == nil | ||||
if invalidTarget { | ||||
// Create a temporary fake target so that os.Symlink() can work. | ||||
target = &testFileParams{ | ||||
contents: "transient", | ||||
} | ||||
} | ||||
targetPath, err := func() (string, error) { | ||||
switch tgt := target.(type) { | ||||
case *testFileParams: | ||||
return createFile(t, tgt) | ||||
case *testDirParams: | ||||
return ioutil.TempDir(os.TempDir(), "") | ||||
} | ||||
return "", errors.New("Unknown target type") | ||||
}() | ||||
if err != nil { | ||||
return nil, err | ||||
} | ||||
|
||||
randBytes := make([]byte, 16) | ||||
rand.Read(randBytes) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplifying: pass the symlink name as a parameter, so you don't need to create it and return it. You can base it on the name of the test case, if you don't want to add another variable. That way, you don't need the testSymlinkCreationResult at all, you just return the file name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thx! Fixed |
||||
symlinkPath := filepath.Join(os.TempDir(), hex.EncodeToString(randBytes)) | ||||
if err := os.Symlink(targetPath, symlinkPath); err != nil { | ||||
return nil, err | ||||
} | ||||
|
||||
result := &testSymlinkCreationResult{ | ||||
symlink: symlinkPath, | ||||
} | ||||
if invalidTarget { | ||||
os.RemoveAll(targetPath) | ||||
} | ||||
result.target = targetPath | ||||
|
||||
return result, nil | ||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: IsDangling is a more informative name, now that I think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done