diff --git a/internal/backer/bzip2.go b/internal/backer/bzip2.go index 45ef460..2ffbc21 100644 --- a/internal/backer/bzip2.go +++ b/internal/backer/bzip2.go @@ -10,8 +10,22 @@ import ( // CreateTarBzip2Stream takes a list of files and returns a reader that reads from a bzip2-compressed tar archive. func CreateTarBzip2Stream(ctx context.Context, filepaths []string) io.ReadCloser { return createArchiveStream(ctx, filepaths, func(w io.Writer) (io.WriteCloser, error) { - return bzip2.NewWriter(w, nil) + level := mapCompressionLevelToBzip2(C.CompressionLevel) + + return bzip2.NewWriter(w, &bzip2.WriterConfig{Level: level}) }) } +// mapCompressionLevelToBzip2 maps 1-9 compression level to bzip2 level. +func mapCompressionLevelToBzip2(level int) int { + if level < 1 { + return bzip2.DefaultCompression + } + if level > 9 { + return bzip2.BestCompression + } + + return level +} + /* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/backer/lz4.go b/internal/backer/lz4.go index 8417cc5..82f2cb0 100644 --- a/internal/backer/lz4.go +++ b/internal/backer/lz4.go @@ -10,8 +10,26 @@ import ( // CreateTarLz4Stream takes a list of files and returns a reader that reads from an lz4-compressed tar archive. func CreateTarLz4Stream(ctx context.Context, filepaths []string) io.ReadCloser { return createArchiveStream(ctx, filepaths, func(w io.Writer) (io.WriteCloser, error) { - return lz4.NewWriter(w), nil + lz4Writer := lz4.NewWriter(w) + // Map compression_level (1-9) to lz4 compression level. + // 1-3: fast, 4-6: balanced, 7-9: better compression. + lz4Writer.Header.CompressionLevel = mapCompressionLevelToLz4(C.CompressionLevel) //nolint: staticcheck + + return lz4Writer, nil }) } +// mapCompressionLevelToLz4 maps 1-9 compression level to lz4 compression level. +// 1-3: fast, 4-6: balanced, 7-9: better compression. +func mapCompressionLevelToLz4(level int) int { + switch { + case level <= 3: + return 0 // Fastest. + case level <= 6: + return 1 // Default. + default: + return 2 // Best compression. + } +} + /* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/backer/utils_test.go b/internal/backer/utils_test.go index 82a730b..e18f9f8 100644 --- a/internal/backer/utils_test.go +++ b/internal/backer/utils_test.go @@ -424,4 +424,144 @@ func TestCreateTarGzStreamDeviceFile(t *testing.T) { } } +// TestCompressionLevelMappingLz4 tests that lz4 compression levels are correctly mapped. +func TestCompressionLevelMappingLz4(t *testing.T) { + tests := []struct { + input int + expected int + }{ + {1, 0}, + {2, 0}, + {3, 0}, + {4, 1}, + {5, 1}, + {6, 1}, + {7, 2}, + {8, 2}, + {9, 2}, + } + + for _, tt := range tests { + C = Config{CompressionLevel: tt.input} + result := mapCompressionLevelToLz4(tt.input) + if result != tt.expected { + t.Errorf("mapCompressionLevelToLz4(%d) = %d, expected %d", tt.input, result, tt.expected) + } + } +} + +// TestCompressionLevelMappingXz tests that xz compression is not configurable via level. +// xz uses default compression - this test verifies the stream works. +func TestCompressionLevelMappingXz(t *testing.T) { + // xz doesn't support configurable compression levels in the same way. + // It always uses default compression. This test verifies the stream is created successfully. + C = Config{CompressionLevel: 6} + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + content := []byte("test content") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + ctx := context.Background() + reader := CreateTarXzStream(ctx, []string{testFile}) + defer reader.Close() + + data, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("Failed to read xz archive: %v", err) + } + + if len(data) == 0 { + t.Error("Expected non-empty archive") + } +} + +// TestCompressionLevelMappingBzip2 tests that bzip2 compression levels are correctly mapped. +func TestCompressionLevelMappingBzip2(t *testing.T) { + tests := []struct { + input int + expected int + }{ + {1, 1}, + {5, 5}, + {9, 9}, + } + + for _, tt := range tests { + C = Config{CompressionLevel: tt.input} + result := mapCompressionLevelToBzip2(tt.input) + if result != tt.expected { + t.Errorf("mapCompressionLevelToBzip2(%d) = %d, expected %d", tt.input, result, tt.expected) + } + } +} + +// TestCreateTarBzip2Stream tests bzip2 compression with configured level. +func TestCreateTarBzip2Stream(t *testing.T) { + C = Config{CompressionLevel: 6} + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + content := []byte("test content for bzip2") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + ctx := context.Background() + reader := CreateTarBzip2Stream(ctx, []string{testFile}) + defer reader.Close() + + // Verify we can read the archive + _, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("Failed to read bzip2 archive: %v", err) + } +} + +// TestCreateTarLz4Stream tests lz4 compression with configured level. +func TestCreateTarLz4Stream(t *testing.T) { + C = Config{CompressionLevel: 6} + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + content := []byte("test content for lz4") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + ctx := context.Background() + reader := CreateTarLz4Stream(ctx, []string{testFile}) + defer reader.Close() + + // Verify we can read the archive + _, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("Failed to read lz4 archive: %v", err) + } +} + +// TestCreateTarXzStream tests xz compression with configured level. +func TestCreateTarXzStream(t *testing.T) { + C = Config{CompressionLevel: 6} + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.txt") + content := []byte("test content for xz") + if err := os.WriteFile(testFile, content, 0644); err != nil { + t.Fatal(err) + } + + ctx := context.Background() + reader := CreateTarXzStream(ctx, []string{testFile}) + defer reader.Close() + + // Verify we can read the archive + _, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("Failed to read xz archive: %v", err) + } +} + /* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/backer/xz.go b/internal/backer/xz.go index e7b8411..a17f764 100644 --- a/internal/backer/xz.go +++ b/internal/backer/xz.go @@ -10,6 +10,8 @@ import ( // CreateTarXzStream takes a list of files and returns a reader that reads from an xz-compressed tar archive. func CreateTarXzStream(ctx context.Context, filepaths []string) io.ReadCloser { return createArchiveStream(ctx, filepaths, func(w io.Writer) (io.WriteCloser, error) { + // xz doesn't have a simple level parameter; use default compression. + // The compression level affects block size indirectly through the algorithm. return xz.NewWriter(w) }) }