mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 18:19:11 +00:00
scan_test now checks title and face groups explicitly instead of only lengths. (#1180)
This commit is contained in:
@@ -117,132 +117,13 @@ linters:
|
||||
#disable-all: true
|
||||
# Enable specific linter
|
||||
# https://golangci-lint.run/usage/linters/#enabled-by-default
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- canonicalheader
|
||||
- containedctx
|
||||
- contextcheck
|
||||
- copyloopvar
|
||||
- cyclop
|
||||
- decorder
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- dupword
|
||||
- durationcheck
|
||||
- err113
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
- execinquery
|
||||
- exhaustive
|
||||
- exhaustruct
|
||||
- exportloopref
|
||||
- fatcontext
|
||||
- forbidigo
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gci
|
||||
- ginkgolinter
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gochecksumtype
|
||||
- gocognit
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godot
|
||||
- godox
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goheader
|
||||
- goimports
|
||||
- gomoddirectives
|
||||
- gomodguard
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- gosmopolitan
|
||||
- govet
|
||||
- grouper
|
||||
- importas
|
||||
- inamedparam
|
||||
- ineffassign
|
||||
- interfacebloat
|
||||
- intrange
|
||||
- ireturn
|
||||
- lll
|
||||
- loggercheck
|
||||
- maintidx
|
||||
- makezero
|
||||
- mirror
|
||||
- misspell
|
||||
- mnd
|
||||
- musttag
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- nilnil
|
||||
- nlreturn
|
||||
- noctx
|
||||
- nolintlint
|
||||
- nonamedreturns
|
||||
- nosprintfhostport
|
||||
- paralleltest
|
||||
- perfsprint
|
||||
- prealloc
|
||||
- predeclared
|
||||
- promlinter
|
||||
- protogetter
|
||||
- reassign
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sloglint
|
||||
- spancheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- tagalign
|
||||
- tagliatelle
|
||||
- tenv
|
||||
- testableexamples
|
||||
- testifylint
|
||||
- testpackage
|
||||
- thelper
|
||||
- tparallel
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- varnamelen
|
||||
- wastedassign
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
- zerologlint
|
||||
#enable: {}
|
||||
# Enable all available linters.
|
||||
# Default: false
|
||||
#enable-all: true
|
||||
# Disable specific linter
|
||||
# https://golangci-lint.run/usage/linters/#disabled-by-default
|
||||
disable:
|
||||
- deadcode # Deprecated
|
||||
- exhaustivestruct # Deprecated
|
||||
- golint # Deprecated
|
||||
- ifshort # Deprecated
|
||||
- interfacer # Deprecated
|
||||
- maligned # Deprecated
|
||||
- gomnd # Deprecated
|
||||
- nosnakecase # Deprecated
|
||||
- scopelint # Deprecated
|
||||
- structcheck # Deprecated
|
||||
- varcheck # Deprecated
|
||||
#disable: {}
|
||||
# Enable presets.
|
||||
# https://golangci-lint.run/usage/linters
|
||||
# Default: []
|
||||
|
||||
@@ -9,6 +9,7 @@ require (
|
||||
github.com/buckket/go-blurhash v1.1.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/go-sql-driver/mysql v1.9.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package scanner_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/photoview/photoview/api/scanner/face_detection"
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
scanner_utils "github.com/photoview/photoview/api/test_utils/scanner"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -22,8 +27,8 @@ func TestFullScan(t *testing.T) {
|
||||
|
||||
pass := "1234"
|
||||
user, err := models.RegisterUser(db, "test_user", &pass, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
if err != nil {
|
||||
t.Fatal("register user error:", err)
|
||||
}
|
||||
|
||||
rootAlbum := models.Album{
|
||||
@@ -31,52 +36,182 @@ func TestFullScan(t *testing.T) {
|
||||
Path: "./test_media/library",
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&rootAlbum).Error) {
|
||||
return
|
||||
wantImages := []string{
|
||||
"buttercup_close_summer_yellow.jpg",
|
||||
"lilac_lilac_bush_lilac.jpg",
|
||||
"mount_merapi_volcano_indonesia.jpg",
|
||||
"boy1.jpg",
|
||||
"boy2.jpg",
|
||||
"girl_black_hair2.jpg",
|
||||
"girl_blond1.jpg",
|
||||
"girl_blond2.jpg",
|
||||
"girl_blond3.jpg",
|
||||
}
|
||||
wantFaceGroups := [][]string{
|
||||
{"boy1.jpg", "boy2.jpg"},
|
||||
{"girl_black_hair2.jpg"},
|
||||
{"girl_blond1.jpg", "girl_blond2.jpg", "girl_blond3.jpg"},
|
||||
}
|
||||
|
||||
err = db.Model(user).Association("Albums").Append(&rootAlbum)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
for i := range wantFaceGroups {
|
||||
slices.Sort(wantFaceGroups[i])
|
||||
}
|
||||
slices.SortFunc(wantFaceGroups, func(a, b []string) int {
|
||||
return strings.Compare(fmt.Sprint(a), fmt.Sprint(b))
|
||||
})
|
||||
|
||||
if err := db.Save(&rootAlbum).Error; err != nil {
|
||||
t.Fatal("create root album error:", err)
|
||||
}
|
||||
|
||||
if !assert.NoError(t, face_detection.InitializeFaceDetector(db)) {
|
||||
return
|
||||
if err := db.Model(user).Association("Albums").Append(&rootAlbum); err != nil {
|
||||
t.Fatal("bind root album error:", err)
|
||||
}
|
||||
|
||||
if err := face_detection.InitializeFaceDetector(db); err != nil {
|
||||
t.Fatal("initalize face detector error:", err)
|
||||
}
|
||||
|
||||
scanner_utils.RunScannerOnUser(t, db, user)
|
||||
|
||||
var allMedia []*models.Media
|
||||
if !assert.NoError(t, db.Find(&allMedia).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, 9, len(allMedia))
|
||||
|
||||
var allMediaURL []*models.MediaURL
|
||||
if !assert.NoError(t, db.Find(&allMediaURL).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, 18, len(allMediaURL))
|
||||
|
||||
// Verify that faces was recognized
|
||||
assert.Eventually(t, func() bool {
|
||||
var allFaceGroups []*models.FaceGroup
|
||||
if !assert.NoError(t, db.Find(&allFaceGroups).Error) {
|
||||
return false
|
||||
t.Run("CheckMedia", func(t *testing.T) {
|
||||
var allMedia []*models.Media
|
||||
if err := db.Find(&allMedia).Error; err != nil {
|
||||
t.Fatal("get all media error:", err)
|
||||
}
|
||||
|
||||
return len(allFaceGroups) == 3
|
||||
}, time.Second*5, time.Millisecond*500)
|
||||
want := slices.Clone(wantImages)
|
||||
slices.Sort(want)
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
got := make([]string, len(allMedia))
|
||||
for i, media := range allMedia {
|
||||
got[i] = media.Title
|
||||
}
|
||||
slices.Sort(got)
|
||||
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Errorf("all media diff:\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CheckMediaURL", func(t *testing.T) {
|
||||
var allMediaURL []*models.MediaURL
|
||||
if err := db.Find(&allMediaURL).Error; err != nil {
|
||||
t.Fatal("get all media url error:", err)
|
||||
}
|
||||
|
||||
if got, want := len(allMediaURL), 18; got != want {
|
||||
t.Errorf("got = %d, want: %v", got, want)
|
||||
}
|
||||
|
||||
want := slices.Clone(wantImages)
|
||||
wantThumbs := slices.Clone(wantImages)
|
||||
for _, thumb := range wantThumbs {
|
||||
want = append(want, "thumbnail_"+thumb)
|
||||
}
|
||||
slices.Sort(want)
|
||||
|
||||
got := make([]string, len(allMediaURL))
|
||||
for i, media := range allMediaURL {
|
||||
got[i] = media.MediaName
|
||||
}
|
||||
slices.Sort(got)
|
||||
|
||||
if diff := cmp.Diff(got, want, cmp.Comparer(equalNameWithoutSuffix)); diff != "" {
|
||||
t.Errorf("all media diff:\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CheckFaceGroup", func(t *testing.T) {
|
||||
ctx, done := context.WithTimeout(t.Context(), time.Second*5)
|
||||
defer done()
|
||||
|
||||
waitFor(ctx, t, time.Second/2, func() bool {
|
||||
var allFaceGroups []*models.FaceGroup
|
||||
if err := db.Find(&allFaceGroups).Error; err != nil {
|
||||
t.Fatal("get face groups error:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return len(allFaceGroups) == len(wantFaceGroups)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("CheckFaces", func(t *testing.T) {
|
||||
var allImageFaces []*models.ImageFace
|
||||
if !assert.NoError(t, db.Find(&allImageFaces).Error) {
|
||||
return false
|
||||
if err := db.Find(&allImageFaces).Error; err != nil {
|
||||
t.Fatal("get face images error:", err)
|
||||
}
|
||||
|
||||
return len(allImageFaces) == 6
|
||||
}, time.Second*5, time.Millisecond*500)
|
||||
for _, face := range allImageFaces {
|
||||
if err := face.FillMedia(db); err != nil {
|
||||
t.Fatalf("fill media for face %v error: %v", face, err)
|
||||
}
|
||||
}
|
||||
|
||||
got := groupMediaWithFaces(allImageFaces)
|
||||
|
||||
if diff := cmp.Diff(got, wantFaceGroups); diff != "" {
|
||||
t.Errorf("all media diff:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func equalNameWithoutSuffix(a, b string) bool {
|
||||
extA := filepath.Ext(a)
|
||||
mainA := strings.TrimRight(a, extA)
|
||||
extB := filepath.Ext(b)
|
||||
mainB := strings.TrimRight(b, extB)
|
||||
|
||||
// ext names are not same
|
||||
if extA != extB {
|
||||
return false
|
||||
}
|
||||
|
||||
// a is not part of b and b is not part of a
|
||||
if strings.Index(mainA, mainB) < 0 && strings.Index(mainB, mainA) < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func waitFor(ctx context.Context, t *testing.T, interval time.Duration, checkFn func() bool) {
|
||||
t.Helper()
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("check timeout")
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
if checkFn() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func groupMediaWithFaces(medias []*models.ImageFace) [][]string {
|
||||
grouped := make(map[int][]string)
|
||||
|
||||
for _, media := range medias {
|
||||
group := grouped[media.FaceGroupID]
|
||||
group = append(group, media.Media.Title)
|
||||
grouped[media.FaceGroupID] = group
|
||||
}
|
||||
|
||||
ret := make([][]string, 0, len(grouped))
|
||||
for _, medias := range grouped {
|
||||
slices.Sort(medias)
|
||||
ret = append(ret, medias)
|
||||
}
|
||||
|
||||
slices.SortFunc(ret, func(a, b []string) int {
|
||||
return strings.Compare(fmt.Sprint(a), fmt.Sprint(b))
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user