155 lines
4.1 KiB
Go
155 lines
4.1 KiB
Go
|
|
package engine
|
||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestProbeCache_LookupMissNonexistent(t *testing.T) {
|
||
|
|
ResetProbeCache()
|
||
|
|
t.Cleanup(ResetProbeCache)
|
||
|
|
|
||
|
|
if _, ok := lookupProbeCache("/path/that/does/not/exist"); ok {
|
||
|
|
t.Fatal("expected MISS for non-existent path")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeCache_StoreThenLookupHit(t *testing.T) {
|
||
|
|
ResetProbeCache()
|
||
|
|
t.Cleanup(ResetProbeCache)
|
||
|
|
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "movie.mkv")
|
||
|
|
if err := os.WriteFile(path, []byte("fake content"), 0o644); err != nil {
|
||
|
|
t.Fatalf("write tmp file: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
probe := &StreamProbe{VideoCodec: "h264", Width: 1920, Height: 1080, DurationSec: 5400}
|
||
|
|
storeProbeCache(path, probe)
|
||
|
|
|
||
|
|
got, ok := lookupProbeCache(path)
|
||
|
|
if !ok {
|
||
|
|
t.Fatal("expected HIT after store")
|
||
|
|
}
|
||
|
|
if got != probe {
|
||
|
|
t.Fatalf("expected pointer-identical probe; got different")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeCache_MtimeChangeInvalidates(t *testing.T) {
|
||
|
|
ResetProbeCache()
|
||
|
|
t.Cleanup(ResetProbeCache)
|
||
|
|
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "movie.mkv")
|
||
|
|
if err := os.WriteFile(path, []byte("original"), 0o644); err != nil {
|
||
|
|
t.Fatalf("write: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
probe := &StreamProbe{VideoCodec: "h264", DurationSec: 100}
|
||
|
|
storeProbeCache(path, probe)
|
||
|
|
|
||
|
|
// Force mtime change. WriteFile doesn't guarantee a different mtime if
|
||
|
|
// the filesystem timestamp resolution is coarse, so set it explicitly
|
||
|
|
// to a value 1 hour in the future.
|
||
|
|
future := time.Now().Add(1 * time.Hour)
|
||
|
|
if err := os.Chtimes(path, future, future); err != nil {
|
||
|
|
t.Fatalf("chtimes: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, ok := lookupProbeCache(path); ok {
|
||
|
|
t.Fatal("expected MISS after mtime change")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeCache_SizeChangeInvalidates(t *testing.T) {
|
||
|
|
ResetProbeCache()
|
||
|
|
t.Cleanup(ResetProbeCache)
|
||
|
|
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "movie.mkv")
|
||
|
|
if err := os.WriteFile(path, []byte("aaaaa"), 0o644); err != nil {
|
||
|
|
t.Fatalf("write: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
probe := &StreamProbe{VideoCodec: "h264", DurationSec: 100}
|
||
|
|
storeProbeCache(path, probe)
|
||
|
|
|
||
|
|
// Truncate file to a different size + reset mtime to the original (to
|
||
|
|
// isolate the size-check path). Stat picks up new size immediately.
|
||
|
|
if err := os.WriteFile(path, []byte("a"), 0o644); err != nil {
|
||
|
|
t.Fatalf("rewrite: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, ok := lookupProbeCache(path); ok {
|
||
|
|
t.Fatal("expected MISS after size change")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeCache_ExpiryDropsEntry(t *testing.T) {
|
||
|
|
ResetProbeCache()
|
||
|
|
t.Cleanup(ResetProbeCache)
|
||
|
|
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "movie.mkv")
|
||
|
|
if err := os.WriteFile(path, []byte("content"), 0o644); err != nil {
|
||
|
|
t.Fatalf("write: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stash an entry whose expires is already in the past — simulates TTL
|
||
|
|
// having elapsed without sleeping for 30 min.
|
||
|
|
fi, err := os.Stat(path)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("stat: %v", err)
|
||
|
|
}
|
||
|
|
key := probeCacheKey{path: path, mtime: fi.ModTime().UnixNano(), size: fi.Size()}
|
||
|
|
probeCacheMu.Lock()
|
||
|
|
probeCache[key] = probeCacheEntry{
|
||
|
|
probe: &StreamProbe{VideoCodec: "h264"},
|
||
|
|
expires: time.Now().Add(-1 * time.Minute),
|
||
|
|
}
|
||
|
|
probeCacheMu.Unlock()
|
||
|
|
|
||
|
|
if _, ok := lookupProbeCache(path); ok {
|
||
|
|
t.Fatal("expected MISS for expired entry")
|
||
|
|
}
|
||
|
|
// Side-effect: lookup should have evicted the stale entry.
|
||
|
|
if ProbeCacheSize() != 0 {
|
||
|
|
t.Fatalf("expected cache size 0 after expiry eviction; got %d", ProbeCacheSize())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeCache_ResetClears(t *testing.T) {
|
||
|
|
ResetProbeCache()
|
||
|
|
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "movie.mkv")
|
||
|
|
if err := os.WriteFile(path, []byte("x"), 0o644); err != nil {
|
||
|
|
t.Fatalf("write: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
storeProbeCache(path, &StreamProbe{VideoCodec: "h264"})
|
||
|
|
if ProbeCacheSize() != 1 {
|
||
|
|
t.Fatalf("expected size 1 after store; got %d", ProbeCacheSize())
|
||
|
|
}
|
||
|
|
|
||
|
|
ResetProbeCache()
|
||
|
|
if ProbeCacheSize() != 0 {
|
||
|
|
t.Fatalf("expected size 0 after reset; got %d", ProbeCacheSize())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeCache_StoreNonexistentNoOp(t *testing.T) {
|
||
|
|
ResetProbeCache()
|
||
|
|
t.Cleanup(ResetProbeCache)
|
||
|
|
|
||
|
|
// Store on a non-existent path should silently do nothing (stat fails),
|
||
|
|
// not panic, and not poison the cache with a zero key.
|
||
|
|
storeProbeCache("/nope/never/exists.mkv", &StreamProbe{VideoCodec: "h264"})
|
||
|
|
if ProbeCacheSize() != 0 {
|
||
|
|
t.Fatalf("expected 0 entries; got %d", ProbeCacheSize())
|
||
|
|
}
|
||
|
|
}
|