From b9090f8b5fa8d88abaa725cc6c5c5ba23f3392c3 Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Sat, 4 Nov 2023 22:25:09 +0100 Subject: [PATCH] implement feature add unit tests --- fs.go | 54 +++++++++++++++++++------ fs_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 148 insertions(+), 19 deletions(-) diff --git a/fs.go b/fs.go index fd181b0d73..941d085deb 100644 --- a/fs.go +++ b/fs.go @@ -296,6 +296,11 @@ type FS struct { // "Cannot open requested path" PathNotFound RequestHandler + // SkipCache if true, will cache no file handler. + // + // By default is false. + SkipCache bool + // Expiration duration for inactive file handlers. // // FSHandlerCacheDuration is used by default. @@ -448,7 +453,7 @@ func (fs *FS) initRequestHandler() { } { - h.cacheManager = newInMemoryCacheManager(fs) + h.cacheManager = newCacheManager(fs) } fs.h = h.handleRequest @@ -719,15 +724,10 @@ type cacheManager interface { SetFileToCache(cacheKind CacheKind, path string, ff *fsFile) *fsFile } -var _ cacheManager = (*inMemoryCacheManager)(nil) - -type inMemoryCacheManager struct { - cacheDuration time.Duration - cache map[string]*fsFile - cacheBrotli map[string]*fsFile - cacheGzip map[string]*fsFile - cacheLock sync.Mutex -} +var ( + _ cacheManager = (*inMemoryCacheManager)(nil) + _ cacheManager = (*noopCacheManager)(nil) +) type CacheKind uint8 @@ -737,7 +737,11 @@ const ( gzipCacheKind ) -func newInMemoryCacheManager(fs *FS) *inMemoryCacheManager { +func newCacheManager(fs *FS) cacheManager { + if fs.SkipCache { + return &noopCacheManager{} + } + cacheDuration := fs.CacheDuration if cacheDuration <= 0 { cacheDuration = FSHandlerCacheDuration @@ -755,6 +759,34 @@ func newInMemoryCacheManager(fs *FS) *inMemoryCacheManager { return instance } +type noopCacheManager struct { + cacheLock sync.Mutex +} + +func (n *noopCacheManager) WithLock(work func()) { + n.cacheLock.Lock() + + work() + + n.cacheLock.Unlock() +} + +func (*noopCacheManager) GetFileFromCache(cacheKind CacheKind, path string) (*fsFile, bool) { + return nil, false +} + +func (*noopCacheManager) SetFileToCache(cacheKind CacheKind, path string, ff *fsFile) *fsFile { + return ff +} + +type inMemoryCacheManager struct { + cacheDuration time.Duration + cache map[string]*fsFile + cacheBrotli map[string]*fsFile + cacheGzip map[string]*fsFile + cacheLock sync.Mutex +} + func (cm *inMemoryCacheManager) WithLock(work func()) { cm.cacheLock.Lock() diff --git a/fs_test.go b/fs_test.go index e38b1401eb..12cf8a956c 100644 --- a/fs_test.go +++ b/fs_test.go @@ -66,6 +66,8 @@ func TestNewVHostPathRewriterMaliciousHost(t *testing.T) { } func testPathNotFound(t *testing.T, pathNotFoundFunc RequestHandler) { + t.Helper() + var ctx RequestCtx var req Request req.SetRequestURI("http//some.url/file") @@ -302,11 +304,30 @@ func TestFSByteRangeConcurrent(t *testing.T) { stop := make(chan struct{}) defer close(stop) - fs := &FS{ + runFSByteRangeConcurrent(t, &FS{ Root: ".", AcceptByteRange: true, CleanStop: stop, - } + }) +} + +func TestFSByteRangeConcurrentSkipCache(t *testing.T) { + // This test can't run parallel as files in / might be changed by other tests. + + stop := make(chan struct{}) + defer close(stop) + + runFSByteRangeConcurrent(t, &FS{ + Root: ".", + SkipCache: true, + AcceptByteRange: true, + CleanStop: stop, + }) +} + +func runFSByteRangeConcurrent(t *testing.T, fs *FS) { + t.Helper() + h := fs.NewRequestHandler() concurrency := 10 @@ -336,11 +357,30 @@ func TestFSByteRangeSingleThread(t *testing.T) { stop := make(chan struct{}) defer close(stop) - fs := &FS{ + runFSByteRangeSingleThread(t, &FS{ Root: ".", AcceptByteRange: true, CleanStop: stop, - } + }) +} + +func TestFSByteRangeSingleThreadSkipCache(t *testing.T) { + // This test can't run parallel as files in / might be changed by other tests. + + stop := make(chan struct{}) + defer close(stop) + + runFSByteRangeSingleThread(t, &FS{ + Root: ".", + AcceptByteRange: true, + SkipCache: true, + CleanStop: stop, + }) +} + +func runFSByteRangeSingleThread(t *testing.T, fs *FS) { + t.Helper() + h := fs.NewRequestHandler() testFSByteRange(t, h, "/fs.go") @@ -348,6 +388,8 @@ func TestFSByteRangeSingleThread(t *testing.T) { } func testFSByteRange(t *testing.T, h RequestHandler, filePath string) { + t.Helper() + var ctx RequestCtx ctx.Init(&Request{}, nil, nil) @@ -427,6 +469,8 @@ func TestParseByteRangeSuccess(t *testing.T) { } func testParseByteRangeSuccess(t *testing.T, v string, contentLength, startPos, endPos int) { + t.Helper() + startPos1, endPos1, err := ParseByteRange([]byte(v), contentLength) if err != nil { t.Fatalf("unexpected error: %v. v=%q, contentLength=%d", err, v, contentLength) @@ -467,6 +511,8 @@ func TestParseByteRangeError(t *testing.T) { } func testParseByteRangeError(t *testing.T, v string, contentLength int) { + t.Helper() + _, _, err := ParseByteRange([]byte(v), contentLength) if err == nil { t.Fatalf("expecting error when parsing byte range %q", v) @@ -480,17 +526,41 @@ func TestFSCompressConcurrent(t *testing.T) { } // This test can't run parallel as files in / might be changed by other tests. - stop := make(chan struct{}) defer close(stop) - fs := &FS{ + runFSCompressConcurrent(t, &FS{ Root: ".", GenerateIndexPages: true, Compress: true, CompressBrotli: true, CleanStop: stop, + }) +} + +func TestFSCompressConcurrentSkipCache(t *testing.T) { + // Don't run this test on Windows, the Windows GitHub actions are too slow and timeout too often. + if runtime.GOOS == "windows" { + t.SkipNow() } + + // This test can't run parallel as files in / might be changed by other tests. + stop := make(chan struct{}) + defer close(stop) + + runFSCompressConcurrent(t, &FS{ + Root: ".", + GenerateIndexPages: true, + SkipCache: true, + Compress: true, + CompressBrotli: true, + CleanStop: stop, + }) +} + +func runFSCompressConcurrent(t *testing.T, fs *FS) { + t.Helper() + h := fs.NewRequestHandler() concurrency := 4 @@ -521,13 +591,34 @@ func TestFSCompressSingleThread(t *testing.T) { stop := make(chan struct{}) defer close(stop) - fs := &FS{ + runFSCompressSingleThread(t, &FS{ Root: ".", GenerateIndexPages: true, Compress: true, CompressBrotli: true, CleanStop: stop, - } + }) +} + +func TestFSCompressSingleThreadSkipCache(t *testing.T) { + // This test can't run parallel as files in / might be changed by other tests. + + stop := make(chan struct{}) + defer close(stop) + + runFSCompressSingleThread(t, &FS{ + Root: ".", + GenerateIndexPages: true, + SkipCache: true, + Compress: true, + CompressBrotli: true, + CleanStop: stop, + }) +} + +func runFSCompressSingleThread(t *testing.T, fs *FS) { + t.Helper() + h := fs.NewRequestHandler() testFSCompress(t, h, "/fs.go") @@ -536,6 +627,8 @@ func TestFSCompressSingleThread(t *testing.T) { } func testFSCompress(t *testing.T, h RequestHandler, filePath string) { + t.Helper() + // File locking is flaky on Windows. if runtime.GOOS == "windows" { t.SkipNow() @@ -755,6 +848,8 @@ func TestStripPathSlashes(t *testing.T) { } func testStripPathSlashes(t *testing.T, path string, stripSlashes int, expectedPath string) { + t.Helper() + s := stripLeadingSlashes([]byte(path), stripSlashes) s = stripTrailingSlashes(s) if string(s) != expectedPath { @@ -779,6 +874,8 @@ func TestFileExtension(t *testing.T) { } func testFileExtension(t *testing.T, path string, compressed bool, compressedFileSuffix, expectedExt string) { + t.Helper() + ext := fileExtension(path, compressed, compressedFileSuffix) if ext != expectedExt { t.Fatalf("unexpected file extension for file %q: %q. Expecting %q", path, ext, expectedExt)