From 7437af57427c5690680f8c8e2590d191aa0d3eba Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 18 Apr 2026 10:39:48 +0300 Subject: [PATCH] Update tests, avoid relying on pre-existed test data set --- internal/backer/loadConfig_test.go | 8 +- internal/backer/main_test.go | 103 ++++++++++++++++++ internal/backer/newServer_test.go | 131 +++++++++++++++++------ internal/backer/types_test.go | 17 +++ internal/backer/utils_test.go | 113 +++++++++++++++++++ test_data/test1/bar/goodbye.txt/text.txt | 1 - test_data/test1/bar/hello.txt | 0 test_data/test1/foo/hello_breakfast.txt | 2 - test_data/test1/foo/mydir/myfile.txt | 1 - test_data/test1/foo/some_text.txt | 0 test_data/test1/hardlinks/hardlink1.txt | 1 - test_data/test1/hardlinks/hardlink2.txt | 1 - test_data/test1/hardlinks/original.txt | 1 - 13 files changed, 336 insertions(+), 43 deletions(-) create mode 100644 internal/backer/main_test.go create mode 100644 internal/backer/types_test.go delete mode 100644 test_data/test1/bar/goodbye.txt/text.txt delete mode 100644 test_data/test1/bar/hello.txt delete mode 100644 test_data/test1/foo/hello_breakfast.txt delete mode 100644 test_data/test1/foo/mydir/myfile.txt delete mode 100644 test_data/test1/foo/some_text.txt delete mode 100644 test_data/test1/hardlinks/hardlink1.txt delete mode 100644 test_data/test1/hardlinks/hardlink2.txt delete mode 100644 test_data/test1/hardlinks/original.txt diff --git a/internal/backer/loadConfig_test.go b/internal/backer/loadConfig_test.go index ffbc6fa..4174ce4 100644 --- a/internal/backer/loadConfig_test.go +++ b/internal/backer/loadConfig_test.go @@ -13,8 +13,12 @@ func TestLoadConfig(t *testing.T) { C = Config{} testDataPath := filepath.Join(t.TempDir(), "test_data") - os.MkdirAll(filepath.Join(testDataPath, "test1", "foo"), 0755) - os.MkdirAll(filepath.Join(testDataPath, "test1", "bar"), 0755) + if err := os.MkdirAll(filepath.Join(testDataPath, "test1", "foo"), 0755); err != nil { + t.Fatalf("Failed to create test directory foo: %v", err) + } + if err := os.MkdirAll(filepath.Join(testDataPath, "test1", "bar"), 0755); err != nil { + t.Fatalf("Failed to create test directory bar: %v", err) + } // Create test config file testConfigPath := filepath.Join(testDataPath, "test_config.json") diff --git a/internal/backer/main_test.go b/internal/backer/main_test.go new file mode 100644 index 0000000..7091291 --- /dev/null +++ b/internal/backer/main_test.go @@ -0,0 +1,103 @@ +package backer + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" +) + +const testDataPath = "../../test_data" + +func TestMain(m *testing.M) { + if err := setupTestData(); err != nil { + fmt.Fprintf(os.Stderr, "FATAL: Failed to setup test data: %v\n", err) + os.Exit(1) + } + + code := m.Run() + + if err := cleanupTestData(); err != nil { + fmt.Fprintf(os.Stderr, "WARNING: Failed to cleanup test data: %v\n", err) + } + + os.Exit(code) +} + +func setupTestData() error { + testDir := filepath.Join(testDataPath, "test1") + + dirs := []string{ + filepath.Join(testDir, "foo", "mydir"), + filepath.Join(testDir, "bar", "goodbye.txt"), + filepath.Join(testDir, "hardlinks"), + filepath.Join(testDir, "symlinks"), + filepath.Join(testDir, "foo", "empty_dir"), + filepath.Join(testDataPath, "tmp"), + } + + for _, dir := range dirs { + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("Failed to create directory %s: %w", dir, err) + } + } + + files := map[string]string{ + filepath.Join(testDir, "foo", "hello_breakfast.txt"): "hello breakfast content", + filepath.Join(testDir, "foo", "some_text.txt"): "some text content", + filepath.Join(testDir, "foo", "mydir", "myfile.txt"): "my file content", + filepath.Join(testDir, "bar", "hello.txt"): "hello content", + filepath.Join(testDir, "bar", "goodbye.txt", "text.txt"): "text content", + filepath.Join(testDir, "hardlinks", "original.txt"): "original content", + filepath.Join(testDir, "symlinks", "target.txt"): "target content", + } + + for path, content := range files { + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + return fmt.Errorf("Failed to write file %s: %w", path, err) + } + } + + orig := filepath.Join(testDir, "hardlinks", "original.txt") + link1 := filepath.Join(testDir, "hardlinks", "hardlink1.txt") + if err := os.Link(orig, link1); err != nil { + return fmt.Errorf("Failed to create hardlink %s -> %s: %w", link1, orig, err) + } + + link2 := filepath.Join(testDir, "hardlinks", "hardlink2.txt") + if err := os.Link(orig, link2); err != nil { + return fmt.Errorf("Failed to create hardlink %s -> %s: %w", link2, orig, err) + } + + target := filepath.Join(testDir, "symlinks", "target.txt") + link := filepath.Join(testDir, "symlinks", "link.txt") + if err := os.Symlink(target, link); err != nil { + return fmt.Errorf("Failed to create symlink %s -> %s: %w", link, target, err) + } + + return nil +} + +func cleanupTestData() error { + testDir := filepath.Join(testDataPath, "test1") + + if err := os.RemoveAll(testDir); err != nil { + return fmt.Errorf("Failed to remove test directory %s: %w", testDir, err) + } + + tmpDir := filepath.Join(testDataPath, "tmp") + if err := os.RemoveAll(tmpDir); err != nil { + return fmt.Errorf("Failed to remove tmp directory %s: %w", tmpDir, err) + } + + return nil +} + +func GetTestDataPath() string { + return testDataPath +} + +func GetTestFilesFromDirectories(ctx context.Context, directories []string) ([]string, error) { + return GetFilesFromDirectories(ctx, directories) +} \ No newline at end of file diff --git a/internal/backer/newServer_test.go b/internal/backer/newServer_test.go index 1b103ae..1a4965a 100644 --- a/internal/backer/newServer_test.go +++ b/internal/backer/newServer_test.go @@ -125,46 +125,43 @@ func TestGetClientIP(t *testing.T) { // TestGetCompressionAlgorithm tests the getCompressionAlgorithm function. func TestGetCompressionAlgorithm(t *testing.T) { + // Save original config + original := C + defer func() { C = original }() + tests := []struct { - name string - ext string - expected string + name string + ext string + expected string + defaultCompression string }{ - { - name: "tar.bz2", - ext: "tar.bz2", - expected: "bzip2", - }, - { - name: "tar.zst", - ext: "tar.zst", - expected: "zstd", - }, - { - name: "tar.lz4", - ext: "tar.lz4", - expected: "lz4", - }, - { - name: "tar.xz", - ext: "tar.xz", - expected: "xz", - }, - { - name: "tar.gz", - ext: "tar.gz", - expected: "pgzip", - }, - { - name: "default with gzip config", - ext: "", - expected: "gzip", - }, + {name: "tar.bz2", ext: "tar.bz2", expected: "bzip2"}, + {name: "tar.zstd", ext: "tar.zstd", expected: "zstd"}, + {name: "tar.zst", ext: "tar.zst", expected: "zstd"}, + {name: "tar.lz4", ext: "tar.lz4", expected: "lz4"}, + {name: "tar.xz", ext: "tar.xz", expected: "xz"}, + {name: "tar.gz", ext: "tar.gz", expected: "pgzip"}, + {name: "bz2", ext: "bz2", expected: "bzip2"}, + {name: "zst", ext: "zst", expected: "zstd"}, + {name: "lz4", ext: "lz4", expected: "lz4"}, + {name: "xz", ext: "xz", expected: "xz"}, + {name: "gz", ext: "gz", expected: "pgzip"}, + {name: "zstd", ext: "zstd", expected: "zstd"}, + {name: "full path /archive.tar.gz", ext: "/archive.tar.gz", expected: "pgzip"}, + {name: "full path /archive.tar.xz", ext: "/archive.tar.xz", expected: "xz"}, + {name: "full path /archive", ext: "/archive", expected: "gzip"}, + {name: "empty uses default", ext: "", expected: "gzip"}, + {name: "unknown uses default", ext: "unknown", expected: "gzip"}, + {name: "unknown with default zstd", ext: "unknown", expected: "zstd", defaultCompression: "zstd"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - C = Config{DefaultCompression: "gzip"} + if tt.defaultCompression != "" { + C = Config{DefaultCompression: tt.defaultCompression} + } else { + C = Config{DefaultCompression: "gzip"} + } result := getCompressionAlgorithm(tt.ext) if result != tt.expected { t.Errorf("getCompressionAlgorithm(%q) = %q, expected %q", tt.ext, result, tt.expected) @@ -191,6 +188,46 @@ func TestNewServerSuccess(t *testing.T) { } } +// TestWithServerHeader tests that the Server header is added to responses. +func TestWithServerHeader(t *testing.T) { + handler := withServerHeader(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + + rec := &responseWriterWrapper{ + ResponseWriter: &testResponseWriter{}, + statusCode: 0, + } + + req, _ := http.NewRequest("GET", "/test", nil) + handler.ServeHTTP(rec, req) + + if rec.Header().Get("Server") != "backer" { + t.Error("Expected Server header to be set to 'backer'") + } +} + +// testResponseWriter implements http.ResponseWriter for testing. +type testResponseWriter struct { + header http.Header + status int +} + +func (tw *testResponseWriter) Header() http.Header { + if tw.header == nil { + tw.header = make(http.Header) + } + return tw.header +} + +func (tw *testResponseWriter) Write(data []byte) (int, error) { + return len(data), nil +} + +func (tw *testResponseWriter) WriteHeader(status int) { + tw.status = status +} + // TestNewServerInvalidConfig tests server creation with invalid config. func TestNewServerInvalidConfig(t *testing.T) { C = Config{} // Reset global config. @@ -618,4 +655,30 @@ func TestMethodNotAllowedHandler(t *testing.T) { } } +// TestTimingSafeMatch tests the timingSafeMatch function for constant-time comparison. +func TestTimingSafeMatch(t *testing.T) { + tests := []struct { + name string + a string + b string + expected bool + }{ + {"empty strings", "", "", true}, + {"identical strings", "test", "test", true}, + {"different strings", "test", "Test", false}, + {"different length", "test", "testing", false}, + {"first empty", "", "test", false}, + {"second empty", "test", "", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := timingSafeMatch(tt.a, tt.b) + if result != tt.expected { + t.Errorf("timingSafeMatch(%q, %q) = %v, expected %v", tt.a, tt.b, result, tt.expected) + } + }) + } +} + /* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/backer/types_test.go b/internal/backer/types_test.go new file mode 100644 index 0000000..f2d4af0 --- /dev/null +++ b/internal/backer/types_test.go @@ -0,0 +1,17 @@ +package backer + +import ( + "testing" +) + +func TestErrDirectoryScanTimeout(t *testing.T) { + err := ErrDirectoryScanTimeout{ScannedFiles: 42} + + if err.Error() != "Directory scan timed out" { + t.Errorf("Expected error message 'Directory scan timed out', got %q", err.Error()) + } + + if err.ScannedFiles != 42 { + t.Errorf("Expected ScannedFiles=42, got %d", err.ScannedFiles) + } +} \ No newline at end of file diff --git a/internal/backer/utils_test.go b/internal/backer/utils_test.go index 5ef5529..ac91af9 100644 --- a/internal/backer/utils_test.go +++ b/internal/backer/utils_test.go @@ -9,6 +9,8 @@ import ( "path/filepath" "runtime" "testing" + + "github.com/klauspost/compress/zstd" ) // TestCreateTarGzStreamBasic tests basic tar.gz creation with a single file. @@ -617,4 +619,115 @@ func TestCreateTarXzStream(t *testing.T) { } } +// TestCreateTarPgzipStream tests pgzip (parallel gzip) compression. +func TestCreateTarPgzipStream(t *testing.T) { + C = Config{CompressionLevel: 6} + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + content := []byte("test content for pgzip") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + ctx := context.Background() + reader := CreateTarPgzipStream(ctx, []string{testFile}) + defer reader.Close() + + gzReader, err := gzip.NewReader(reader) + if err != nil { + t.Fatalf("Failed to create gzip reader: %v", err) + } + defer gzReader.Close() + + tarReader := tar.NewReader(gzReader) + + header, err := tarReader.Next() + if err != nil { + t.Fatalf("Failed to read tar header: %v", err) + } + + if header.Name != testFile && filepath.Base(header.Name) != "test.txt" { + t.Errorf("Expected file name 'test.txt', got '%s'", header.Name) + } + + data, err := io.ReadAll(tarReader) + if err != nil { + t.Fatalf("Failed to read file content: %v", err) + } + + if string(data) != string(content) { + t.Errorf("Expected content '%s', got '%s'", content, data) + } +} + +// TestCreateTarZstdStream tests zstd compression. +func TestCreateTarZstdStream(t *testing.T) { + C = Config{CompressionLevel: 6} + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + content := []byte("test content for zstd") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + ctx := context.Background() + reader := CreateTarZstdStream(ctx, []string{testFile}) + defer reader.Close() + + // zstd.Reader is in github.com/klauspost/compress/zstd + zstdReader, err := zstd.NewReader(reader) + if err != nil { + t.Fatalf("Failed to create zstd reader: %v", err) + } + defer zstdReader.Close() + + tarReader := tar.NewReader(zstdReader) + + header, err := tarReader.Next() + if err != nil { + t.Fatalf("Failed to read tar header: %v", err) + } + + if header.Name != testFile && filepath.Base(header.Name) != "test.txt" { + t.Errorf("Expected file name 'test.txt', got '%s'", header.Name) + } + + data, err := io.ReadAll(tarReader) + if err != nil { + t.Fatalf("Failed to read file content: %v", err) + } + + if string(data) != string(content) { + t.Errorf("Expected content '%s', got '%s'", content, data) + } +} + +// TestZstdLevel tests zstd level mapping. +func TestZstdLevel(t *testing.T) { + tests := []struct { + input int + expected zstd.EncoderLevel + }{ + {1, zstd.SpeedFastest}, + {2, zstd.SpeedFastest}, + {3, zstd.SpeedFastest}, + {4, zstd.SpeedDefault}, + {5, zstd.SpeedDefault}, + {6, zstd.SpeedDefault}, + {7, zstd.SpeedBetterCompression}, + {8, zstd.SpeedBetterCompression}, + {9, zstd.SpeedBetterCompression}, + } + + for _, tt := range tests { + C = Config{CompressionLevel: tt.input} + result := zstdLevel(tt.input) + if result != tt.expected { + t.Errorf("zstdLevel(%d) = %v, expected %v", tt.input, result, tt.expected) + } + } +} + /* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/test_data/test1/bar/goodbye.txt/text.txt b/test_data/test1/bar/goodbye.txt/text.txt deleted file mode 100644 index befe075..0000000 --- a/test_data/test1/bar/goodbye.txt/text.txt +++ /dev/null @@ -1 +0,0 @@ -Fucked up beyond recognotion. diff --git a/test_data/test1/bar/hello.txt b/test_data/test1/bar/hello.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test_data/test1/foo/hello_breakfast.txt b/test_data/test1/foo/hello_breakfast.txt deleted file mode 100644 index d033c4d..0000000 --- a/test_data/test1/foo/hello_breakfast.txt +++ /dev/null @@ -1,2 +0,0 @@ -My dear Tweety, -I'd like to greet you as my breakfast. diff --git a/test_data/test1/foo/mydir/myfile.txt b/test_data/test1/foo/mydir/myfile.txt deleted file mode 100644 index 54c45d4..0000000 --- a/test_data/test1/foo/mydir/myfile.txt +++ /dev/null @@ -1 +0,0 @@ -This is MY file! \ No newline at end of file diff --git a/test_data/test1/foo/some_text.txt b/test_data/test1/foo/some_text.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test_data/test1/hardlinks/hardlink1.txt b/test_data/test1/hardlinks/hardlink1.txt deleted file mode 100644 index 508d3cf..0000000 --- a/test_data/test1/hardlinks/hardlink1.txt +++ /dev/null @@ -1 +0,0 @@ -shared content for hardlinks diff --git a/test_data/test1/hardlinks/hardlink2.txt b/test_data/test1/hardlinks/hardlink2.txt deleted file mode 100644 index 508d3cf..0000000 --- a/test_data/test1/hardlinks/hardlink2.txt +++ /dev/null @@ -1 +0,0 @@ -shared content for hardlinks diff --git a/test_data/test1/hardlinks/original.txt b/test_data/test1/hardlinks/original.txt deleted file mode 100644 index 508d3cf..0000000 --- a/test_data/test1/hardlinks/original.txt +++ /dev/null @@ -1 +0,0 @@ -shared content for hardlinks