-
Notifications
You must be signed in to change notification settings - Fork 14
moved the compute file hash function to utils #46
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package hashutils | ||
|
|
||
| import ( | ||
| "crypto/sha1" | ||
| "crypto/sha256" | ||
| "encoding/hex" | ||
| "fmt" | ||
| "hash" | ||
| "io" | ||
| "os" | ||
| ) | ||
|
|
||
| type HashType int | ||
|
|
||
| const ( | ||
| HashTypeNone HashType = 0 | ||
| HashTypeSHA1 HashType = 1 | ||
| HashTypeSHA256 HashType = 2 | ||
| ) | ||
|
|
||
| func GetHashAlgorithm(hashOpt HashType) (hash.Hash, error) { | ||
| switch hashOpt { | ||
| case HashTypeSHA1: | ||
| return sha1.New(), nil | ||
| case HashTypeSHA256: | ||
| return sha256.New(), nil | ||
| default: | ||
| return nil, fmt.Errorf("unsupported hash type option: %v", hashOpt) | ||
| } | ||
| } | ||
|
|
||
| // This is a separate function from ComputeHash because streaming the file contents into the hasher is | ||
| // more efficient than reading the entire file into memory at once, especially for larger files. | ||
| func ComputeFileHash(filePath string, hashAlg hash.Hash) (string, error) { | ||
| // make sure filepath is not empty and file exists | ||
| if filePath == "" { | ||
| return "", fmt.Errorf("file path cannot be empty") | ||
| } | ||
| if _, err := os.Stat(filePath); os.IsNotExist(err) { | ||
| return "", fmt.Errorf("file does not exist at path: %s", filePath) | ||
| } | ||
|
|
||
| f, err := os.Open(filePath) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to open file for hashing: %w", err) | ||
| } | ||
| defer f.Close() | ||
| // We can stream the file contents to the hasher which is more efficient for large files. | ||
| if _, err := io.Copy(hashAlg, f); err != nil { | ||
| return "", fmt.Errorf("failed to read file for hashing: %w", err) | ||
| } | ||
|
|
||
| hash := hashAlg.Sum(nil) | ||
| hashStr := hex.EncodeToString(hash[:]) | ||
|
|
||
| return hashStr, nil | ||
| } | ||
|
|
||
| func ComputeHash(contents string, hashAlg hash.Hash) string { | ||
| var hashStr string | ||
| hashAlg.Write([]byte(contents)) | ||
| hash := hashAlg.Sum(nil) | ||
| hashStr = hex.EncodeToString(hash[:]) | ||
| return hashStr | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| package hashutils | ||
|
|
||
| import ( | ||
| "crypto/sha1" | ||
| "crypto/sha256" | ||
| "encoding/hex" | ||
| "os" | ||
| "runtime" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestComputeFileHash_Success(t *testing.T) { | ||
| content := []byte("hello world") | ||
| tmpFile, err := os.CreateTemp(t.TempDir(), "hash_test_*.txt") | ||
| if err != nil { | ||
| t.Fatalf("failed to create temp file: %v", err) | ||
| } | ||
| if _, err := tmpFile.Write(content); err != nil { | ||
| t.Fatalf("failed to write to temp file: %v", err) | ||
| } | ||
| tmpFile.Close() | ||
| defer os.Remove(tmpFile.Name()) | ||
|
|
||
| hashAlg := sha256.New() | ||
| got, err := ComputeFileHash(tmpFile.Name(), hashAlg) | ||
|
|
||
| require.Nil(t, err, "expected no error from ComputeFileHash, got: %v", err) | ||
| require.NotEmpty(t, got, "expected non-empty hash result") | ||
|
|
||
| // Verify hash matches expected | ||
| expected := sha256.Sum256(content) | ||
| require.Equal(t, hex.EncodeToString(expected[:]), got, "hash mismatch") | ||
| } | ||
|
|
||
| func TestComputeFileHash_EmptyFilePath(t *testing.T) { | ||
| hashAlg := sha256.New() | ||
| _, err := ComputeFileHash("", hashAlg) | ||
| require.NotNil(t, err, "expected error for empty file path") | ||
| require.Equal(t, "file path cannot be empty", err.Error(), "unexpected error message") | ||
| } | ||
|
|
||
| func TestComputeFileHash_FileDoesNotExist(t *testing.T) { | ||
| hashAlg := sha256.New() | ||
| nonExistentPath := "./nonexistent_file.txt" | ||
| _, err := ComputeFileHash(nonExistentPath, hashAlg) | ||
| require.NotNil(t, err, "expected error for non-existent file path") | ||
| require.Contains(t, err.Error(), "file does not exist at path", "unexpected error message") | ||
| } | ||
|
|
||
| func TestComputeFileHash_FileNotReadable(t *testing.T) { | ||
| if runtime.GOOS == "windows" { | ||
| t.Skip("permission-based test not reliable on Windows") | ||
| } | ||
|
|
||
| tmpFile, err := os.CreateTemp(t.TempDir(), "no_read_*.txt") | ||
| if err != nil { | ||
| t.Fatalf("failed to create temp file: %v", err) | ||
| } | ||
| tmpFile.WriteString("some content") | ||
| tmpFile.Close() | ||
| defer os.Remove(tmpFile.Name()) | ||
|
|
||
| // Remove read permission | ||
| if err := os.Chmod(tmpFile.Name(), 0000); err != nil { | ||
| t.Fatalf("failed to change file permissions: %v", err) | ||
| } | ||
| t.Cleanup(func() { | ||
| os.Chmod(tmpFile.Name(), 0644) // restore for cleanup | ||
| }) | ||
|
|
||
| hashAlg := sha256.New() | ||
| _, err = ComputeFileHash(tmpFile.Name(), hashAlg) | ||
| require.NotNil(t, err, "expected error for unreadable file") | ||
| require.Contains(t, err.Error(), "failed to open file for hashing", "unexpected error message") | ||
| } | ||
|
|
||
| func TestComputeFileHash_EmptyFile(t *testing.T) { | ||
| tmpFile, err := os.CreateTemp(t.TempDir(), "empty_*.txt") | ||
| if err != nil { | ||
| t.Fatalf("failed to create temp file: %v", err) | ||
| } | ||
| tmpFile.Close() | ||
| defer os.Remove(tmpFile.Name()) | ||
|
|
||
| hashAlg := sha256.New() | ||
| got, err := ComputeFileHash(tmpFile.Name(), hashAlg) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error for empty file: %v", err) | ||
| } | ||
|
|
||
| expected := sha256.Sum256([]byte{}) | ||
| require.Equal(t, hex.EncodeToString(expected[:]), got, "hash mismatch for empty file") | ||
| } | ||
|
|
||
| func TestComputeHash_Success(t *testing.T) { | ||
| input := "hello world" | ||
| hashAlg := sha256.New() | ||
| got := ComputeHash(input, hashAlg) | ||
| require.NotEmpty(t, got, "expected non-empty hash result") | ||
|
|
||
| expected := sha256.Sum256([]byte(input)) | ||
| require.Equal(t, hex.EncodeToString(expected[:]), got, "hash mismatch") | ||
| } | ||
|
|
||
| func TestComputeHash_EmptyString(t *testing.T) { | ||
| hashAlg := sha256.New() | ||
| got := ComputeHash("", hashAlg) | ||
| require.NotEmpty(t, got, "expected non-empty hash result for empty string") | ||
|
|
||
| expected := sha256.Sum256([]byte{}) | ||
| require.Equal(t, hex.EncodeToString(expected[:]), got, "hash mismatch for empty string") | ||
| } | ||
|
|
||
| func TestComputeHash_DifferentInputsDifferentHashes(t *testing.T) { | ||
| hash1 := ComputeHash("input1", sha256.New()) | ||
| hash2 := ComputeHash("input2", sha256.New()) | ||
| require.NotEqual(t, hash1, hash2, "expected different hashes for different inputs") | ||
| } | ||
|
|
||
| func TestComputeHash_SameInputSameHash(t *testing.T) { | ||
| input := "consistent input" | ||
| hash1 := ComputeHash(input, sha256.New()) | ||
| hash2 := ComputeHash(input, sha256.New()) | ||
| require.Equal(t, hash1, hash2, "expected same hash for same input") | ||
| } | ||
|
|
||
| func TestComputeHash_DifferentAlgorithm(t *testing.T) { | ||
| input := "test" | ||
| sha256Hash := ComputeHash(input, sha256.New()) | ||
| sha1Hash := ComputeHash(input, sha1.New()) | ||
|
|
||
| require.NotEqual(t, sha256Hash, sha1Hash, "expected different hashes for different algorithms") | ||
| require.Equal(t, 64, len(sha256Hash), "expected SHA-256 hex string length 64") | ||
| require.Equal(t, 40, len(sha1Hash), "expected SHA-1 hex string length 40") | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Streaming via io.Copy instead of os.ReadFile into memory is probably good for large files.