All checks were successful
PR Check / check-and-test (pull_request) Successful in 21s
- Updated .gitignore to include all .exe files and ensure .smashignore is tracked. - Expanded README.md with advanced configuration options for garbage collection algorithms, detailing available algorithms and use cases. - Modified launch.json to include memory and disk garbage collection flags for better configuration. - Refactored root.go to introduce memoryGC and diskGC flags for garbage collection algorithms. - Implemented hash extraction and verification in steamcache.go to ensure data integrity during caching. - Added new tests in steamcache_test.go for hash extraction and verification, ensuring correctness of caching behavior. - Enhanced garbage collection strategies in gc.go, introducing LFU, FIFO, Largest, Smallest, and Hybrid algorithms with corresponding metrics. - Updated caching logic to conditionally cache responses based on hash verification results.
246 lines
6.0 KiB
Go
246 lines
6.0 KiB
Go
// steamcache/steamcache_test.go
|
|
package steamcache
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestCaching(t *testing.T) {
|
|
td := t.TempDir()
|
|
|
|
os.WriteFile(filepath.Join(td, "key2"), []byte("value2"), 0644)
|
|
|
|
sc := New("localhost:8080", "1G", "1G", td, "", "lru", "lru")
|
|
|
|
w, err := sc.vfs.Create("key", 5)
|
|
if err != nil {
|
|
t.Errorf("Create failed: %v", err)
|
|
}
|
|
w.Write([]byte("value"))
|
|
w.Close()
|
|
|
|
w, err = sc.vfs.Create("key1", 6)
|
|
if err != nil {
|
|
t.Errorf("Create failed: %v", err)
|
|
}
|
|
w.Write([]byte("value1"))
|
|
w.Close()
|
|
|
|
if sc.diskgc.Size() != 17 {
|
|
t.Errorf("Size failed: got %d, want %d", sc.diskgc.Size(), 17)
|
|
}
|
|
|
|
if sc.vfs.Size() != 17 {
|
|
t.Errorf("Size failed: got %d, want %d", sc.vfs.Size(), 17)
|
|
}
|
|
|
|
rc, err := sc.vfs.Open("key")
|
|
if err != nil {
|
|
t.Errorf("Open failed: %v", err)
|
|
}
|
|
d, _ := io.ReadAll(rc)
|
|
rc.Close()
|
|
if string(d) != "value" {
|
|
t.Errorf("Get failed: got %s, want %s", d, "value")
|
|
}
|
|
|
|
rc, err = sc.vfs.Open("key1")
|
|
if err != nil {
|
|
t.Errorf("Open failed: %v", err)
|
|
}
|
|
d, _ = io.ReadAll(rc)
|
|
rc.Close()
|
|
if string(d) != "value1" {
|
|
t.Errorf("Get failed: got %s, want %s", d, "value1")
|
|
}
|
|
|
|
rc, err = sc.vfs.Open("key2")
|
|
if err != nil {
|
|
t.Errorf("Open failed: %v", err)
|
|
}
|
|
d, _ = io.ReadAll(rc)
|
|
rc.Close()
|
|
if string(d) != "value2" {
|
|
t.Errorf("Get failed: got %s, want %s", d, "value2")
|
|
}
|
|
|
|
if sc.diskgc.Size() != 17 {
|
|
t.Errorf("Size failed: got %d, want %d", sc.diskgc.Size(), 17)
|
|
}
|
|
|
|
if sc.vfs.Size() != 17 {
|
|
t.Errorf("Size failed: got %d, want %d", sc.vfs.Size(), 17)
|
|
}
|
|
|
|
sc.memory.Delete("key2")
|
|
os.Remove(filepath.Join(td, "key2"))
|
|
|
|
if _, err := sc.vfs.Open("key2"); err == nil {
|
|
t.Errorf("Open failed: got nil, want error")
|
|
}
|
|
}
|
|
|
|
func TestCacheMissAndHit(t *testing.T) {
|
|
sc := New("localhost:8080", "0", "1G", t.TempDir(), "", "lru", "lru")
|
|
|
|
key := "testkey"
|
|
value := []byte("testvalue")
|
|
|
|
// Simulate miss: but since no upstream, skip full ServeHTTP, test VFS
|
|
w, err := sc.vfs.Create(key, int64(len(value)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w.Write(value)
|
|
w.Close()
|
|
|
|
rc, err := sc.vfs.Open(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ := io.ReadAll(rc)
|
|
rc.Close()
|
|
|
|
if string(got) != string(value) {
|
|
t.Errorf("expected %s, got %s", value, got)
|
|
}
|
|
}
|
|
|
|
func TestHashExtraction(t *testing.T) {
|
|
// Test the specific key from the user's issue
|
|
testCases := []struct {
|
|
filename string
|
|
expectedHash string
|
|
shouldHaveHash bool
|
|
}{
|
|
{
|
|
filename: "e89c81a1a926eb4732e146bc806491da8a7d89ca",
|
|
expectedHash: "e89c81a1a926eb4732e146bc806491da8a7d89ca",
|
|
shouldHaveHash: true, // Now it should work with the new standalone hash pattern
|
|
},
|
|
{
|
|
filename: "chunk_e89c81a1a926eb4732e146bc806491da8a7d89ca",
|
|
expectedHash: "",
|
|
shouldHaveHash: false, // No longer supported with simplified patterns
|
|
},
|
|
{
|
|
filename: "file.e89c81a1a926eb4732e146bc806491da8a7d89ca.chunk",
|
|
expectedHash: "",
|
|
shouldHaveHash: false, // No longer supported with simplified patterns
|
|
},
|
|
{
|
|
filename: "chunk_abc123def456",
|
|
expectedHash: "",
|
|
shouldHaveHash: false, // Not 40 chars
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
hash, hasHash := extractHashFromFilename(tc.filename)
|
|
if hasHash != tc.shouldHaveHash {
|
|
t.Errorf("filename: %s, expected hasHash: %v, got: %v", tc.filename, tc.shouldHaveHash, hasHash)
|
|
}
|
|
if hasHash && hash != tc.expectedHash {
|
|
t.Errorf("filename: %s, expected hash: %s, got: %s", tc.filename, tc.expectedHash, hash)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHashCalculation(t *testing.T) {
|
|
// Test data
|
|
testData := []byte("Hello, World!")
|
|
|
|
// Calculate hash
|
|
hash := calculateFileHash(testData)
|
|
|
|
// Expected SHA1 hash of "Hello, World!"
|
|
expectedHash := "0a0a9f2a6772942557ab5355d76af442f8f65e01"
|
|
|
|
if hash != expectedHash {
|
|
t.Errorf("Hash calculation failed: expected %s, got %s", expectedHash, hash)
|
|
}
|
|
|
|
// Test verification
|
|
if !verifyFileHash(testData, expectedHash) {
|
|
t.Error("Hash verification failed for correct hash")
|
|
}
|
|
|
|
if verifyFileHash(testData, "wronghash") {
|
|
t.Error("Hash verification passed for wrong hash")
|
|
}
|
|
}
|
|
|
|
func TestHashVerificationWithRealData(t *testing.T) {
|
|
// Test with some real data to ensure our hash calculation is correct
|
|
testCases := []struct {
|
|
data string
|
|
expected string
|
|
}{
|
|
{"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"}, // SHA1 of empty string
|
|
{"test", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"}, // SHA1 of "test"
|
|
{"Hello, World!", "0a0a9f2a6772942557ab5355d76af442f8f65e01"}, // SHA1 of "Hello, World!"
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
data := []byte(tc.data)
|
|
hash := calculateFileHash(data)
|
|
if hash != tc.expected {
|
|
t.Errorf("Hash calculation failed for '%s': expected %s, got %s", tc.data, tc.expected, hash)
|
|
}
|
|
|
|
if !verifyFileHash(data, tc.expected) {
|
|
t.Errorf("Hash verification failed for '%s'", tc.data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResponseHashCalculation(t *testing.T) {
|
|
// Create a mock HTTP response
|
|
resp := &http.Response{
|
|
StatusCode: 200,
|
|
Status: "200 OK",
|
|
Header: http.Header{
|
|
"Content-Type": []string{"application/octet-stream"},
|
|
"Content-Length": []string{"13"},
|
|
"Cache-Control": []string{"public, max-age=3600"},
|
|
},
|
|
}
|
|
|
|
bodyData := []byte("Hello, World!")
|
|
|
|
// Calculate response hash
|
|
responseHash := calculateResponseHash(resp, bodyData)
|
|
|
|
// The hash should be different from just the body hash
|
|
bodyHash := calculateFileHash(bodyData)
|
|
|
|
if responseHash == bodyHash {
|
|
t.Error("Response hash should be different from body hash when headers are present")
|
|
}
|
|
|
|
// Test that the same response produces the same hash
|
|
responseHash2 := calculateResponseHash(resp, bodyData)
|
|
if responseHash != responseHash2 {
|
|
t.Error("Response hash should be consistent for the same response")
|
|
}
|
|
|
|
// Test with different headers
|
|
resp2 := &http.Response{
|
|
StatusCode: 200,
|
|
Status: "200 OK",
|
|
Header: http.Header{
|
|
"Content-Type": []string{"text/plain"},
|
|
"Content-Length": []string{"13"},
|
|
},
|
|
}
|
|
|
|
responseHash3 := calculateResponseHash(resp2, bodyData)
|
|
if responseHash == responseHash3 {
|
|
t.Error("Response hash should be different for different headers")
|
|
}
|
|
}
|