mirror of
https://github.com/Belphemur/CBZOptimizer.git
synced 2026-07-21 19:05:39 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
650623b340 | ||
|
|
4b961f18e3 | ||
|
|
2dfc39505c | ||
|
|
e596a026dd | ||
|
|
9423cde9f7 |
@@ -1 +0,0 @@
|
|||||||
testdata/large/*.cbz filter=lfs diff=lfs merge=lfs -text
|
|
||||||
@@ -95,15 +95,6 @@ go test -v ./pkg/converter/...
|
|||||||
go test -v ./internal/utils/...
|
go test -v ./internal/utils/...
|
||||||
```
|
```
|
||||||
|
|
||||||
A large-file integration test (`TestOptimizeIntegration_LargeFile`) exercises the
|
|
||||||
optimize pipeline against a ~1GB CBZ fixture stored via Git LFS
|
|
||||||
(`testdata/large/large_chapter.cbz`, tracked in `.gitattributes`). It is skipped
|
|
||||||
by default; fetch the fixture with `git lfs pull` and opt in with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
CBZ_RUN_LARGE_FILE_TEST=1 go test -v ./internal/utils/... -run TestOptimizeIntegration_LargeFile
|
|
||||||
```
|
|
||||||
|
|
||||||
### Linting
|
### Linting
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
/cbzoptimizer
|
||||||
|
/encoder-setup
|
||||||
|
|
||||||
test/
|
test/
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ require (
|
|||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/thediveo/enumflag/v2 v2.2.1
|
github.com/thediveo/enumflag/v2 v2.2.1
|
||||||
golang.org/x/image v0.43.0
|
golang.org/x/image v0.44.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -50,7 +50,7 @@ require (
|
|||||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/net v0.56.0 // indirect
|
golang.org/x/net v0.56.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sys v0.46.0 // indirect
|
||||||
golang.org/x/text v0.38.0 // indirect
|
golang.org/x/text v0.40.0 // indirect
|
||||||
golang.org/x/tools v0.46.0 // indirect
|
golang.org/x/tools v0.47.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -217,6 +217,8 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
|
|||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
|
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
|
||||||
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||||
|
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
|
||||||
|
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -299,6 +301,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||||
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -328,6 +332,7 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
|
|||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
|
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
|
||||||
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
|
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
|
||||||
|
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -51,13 +51,6 @@ func TestOptimizeIntegration(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Skip the "large" fixtures directory: it holds the Git LFS-tracked
|
|
||||||
// fixture used exclusively by TestOptimizeIntegration_LargeFile,
|
|
||||||
// which may only be a small LFS pointer file if the content wasn't
|
|
||||||
// fetched, and shouldn't be exercised by this generic test.
|
|
||||||
if info.IsDir() && filepath.Base(path) == "large" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
fileName := strings.ToLower(info.Name())
|
fileName := strings.ToLower(info.Name())
|
||||||
if (strings.HasSuffix(fileName, ".cbz") || strings.HasSuffix(fileName, ".cbr")) && !strings.Contains(fileName, "converted") {
|
if (strings.HasSuffix(fileName, ".cbz") || strings.HasSuffix(fileName, ".cbr")) && !strings.Contains(fileName, "converted") {
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/belphemur/CBZOptimizer/v2/internal/utils/errs"
|
|
||||||
"github.com/belphemur/CBZOptimizer/v2/pkg/converter"
|
|
||||||
"github.com/belphemur/CBZOptimizer/v2/pkg/converter/constant"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// largeTestFile is a ~1GB synthetic CBZ fixture stored via Git LFS (see
|
|
||||||
// .gitattributes). It is used to exercise the optimize pipeline with a
|
|
||||||
// chapter large enough to make in-memory-only handling impractical, and to
|
|
||||||
// validate that converted pages are streamed to/from a staging temp folder
|
|
||||||
// (see manga.Page.TempFilePath / manga.Chapter.TempDir) instead of blowing
|
|
||||||
// up memory usage.
|
|
||||||
const largeTestFile = "../../testdata/large/large_chapter.cbz"
|
|
||||||
|
|
||||||
// TestOptimizeIntegration_LargeFile is opt-in (set CBZ_RUN_LARGE_FILE_TEST=1)
|
|
||||||
// since it processes a ~1GB fixture and can take a while to run. It is
|
|
||||||
// automatically skipped if the fixture is unavailable (e.g. Git LFS content
|
|
||||||
// wasn't fetched, leaving only a pointer file) or in short mode.
|
|
||||||
func TestOptimizeIntegration_LargeFile(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("Skipping large file integration test in short mode")
|
|
||||||
}
|
|
||||||
if os.Getenv("CBZ_RUN_LARGE_FILE_TEST") == "" {
|
|
||||||
t.Skip("Skipping large file integration test; set CBZ_RUN_LARGE_FILE_TEST=1 to run it")
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := os.Stat(largeTestFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Skipf("large test fixture not found: %v", err)
|
|
||||||
}
|
|
||||||
// If Git LFS content wasn't fetched (e.g. `actions/checkout` without
|
|
||||||
// `lfs: true`), the file on disk is just a small pointer text file
|
|
||||||
// rather than the real ~1GB fixture. Detect and skip gracefully instead
|
|
||||||
// of failing the whole suite.
|
|
||||||
const minExpectedSize = 500 * 1024 * 1024 // 500MB
|
|
||||||
if info.Size() < minExpectedSize {
|
|
||||||
t.Skipf("large test fixture looks like a Git LFS pointer (size=%d), skipping; run `git lfs pull`", info.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir, err := os.MkdirTemp("", "test_optimize_large_file")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer errs.CaptureGeneric(&err, os.RemoveAll, tempDir, "failed to remove temporary directory")
|
|
||||||
|
|
||||||
converterInstance, err := converter.Get(constant.WebP)
|
|
||||||
if err != nil {
|
|
||||||
t.Skip("WebP converter not available, skipping large file integration test")
|
|
||||||
}
|
|
||||||
if err := converterInstance.PrepareConverter(); err != nil {
|
|
||||||
t.Skip("Failed to prepare WebP converter, skipping large file integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
cbzFile := filepath.Join(tempDir, "large_chapter.cbz")
|
|
||||||
if err := copyFile(largeTestFile, cbzFile); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var memBefore, memAfter runtime.MemStats
|
|
||||||
runtime.GC()
|
|
||||||
runtime.ReadMemStats(&memBefore)
|
|
||||||
|
|
||||||
options := &OptimizeOptions{
|
|
||||||
ChapterConverter: converterInstance,
|
|
||||||
Path: cbzFile,
|
|
||||||
Quality: 85,
|
|
||||||
Override: false,
|
|
||||||
Split: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Optimize(options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to optimize large chapter: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.GC()
|
|
||||||
runtime.ReadMemStats(&memAfter)
|
|
||||||
log.Info().
|
|
||||||
Uint64("heap_alloc_before", memBefore.HeapAlloc).
|
|
||||||
Uint64("heap_alloc_after", memAfter.HeapAlloc).
|
|
||||||
Int64("input_size", info.Size()).
|
|
||||||
Msg("Large file integration test memory usage")
|
|
||||||
|
|
||||||
outputFile := strings.TrimSuffix(cbzFile, ".cbz") + "_converted.cbz"
|
|
||||||
if _, err := os.Stat(outputFile); err != nil {
|
|
||||||
t.Fatalf("expected converted output file %s to exist: %v", outputFile, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyFile copies src to dst using streaming file I/O so that the whole
|
|
||||||
// file content is never held in memory at once, which matters for the
|
|
||||||
// large fixture used by this test.
|
|
||||||
func copyFile(src, dst string) (err error) {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open source file: %w", err)
|
|
||||||
}
|
|
||||||
defer errs.Capture(&err, in.Close, "failed to close source file")
|
|
||||||
|
|
||||||
out, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create destination file: %w", err)
|
|
||||||
}
|
|
||||||
defer errs.Capture(&err, out.Close, "failed to close destination file")
|
|
||||||
|
|
||||||
if _, err := io.Copy(out, in); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy file contents: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:8880c8909521c3716a339a39a29a4bf1114921a8b4651ff8ca04da7ce3a54b6a
|
|
||||||
size 1001668690
|
|
||||||
Reference in New Issue
Block a user