Files
minstrel/internal/library/duplicate_classify_test.go
T
bvandeusenandClaude Opus 5.5 f32abc974f
release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m31s
release / android (push) Successful in 4m58s
release / integration (push) Successful in 5m0s
release / Build signed APK (releases and dev) (push) Successful in 5m7s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 15s
release / Verify release artifacts (tag releases only) (push) Skipped
fix: a duplicate fold never deletes a lossless copy for a lossy one, nor folds two versions (M498)
The first album-copy fold pass on 2026-10-20 removed five FLACs (Måneskin
Rush!, Weezer 2016) in favour of mp3s whose only edge was a recording MBID,
and an explicit "Suite-Pee" for its clean version. ProposeSurvivor now ranks
lossless before tag fit; tag fit still outranks file size, which is what the
Humanz rips needed. The fold keys copies by their version words (title and
file name), so a clean, live or instrumental copy holding the same title tag
is linked, not folded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:36:55 -04:00

191 lines
9.5 KiB
Go

package library
import (
"fmt"
"testing"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
// The shapes measured on the operator's library (milestone 498's body).
func TestClassifyDuplicateGroup(t *testing.T) {
m := func(album, title string) ClassifyMember {
return ClassifyMember{AlbumID: album, Title: title, ArtistName: "Gorillaz"}
}
cases := []struct {
name string
tier string
members []ClassifyMember
want DuplicateClass
}{
{"Humanz: one album, one song twice", "exact",
[]ClassifyMember{m("humanz", "Saturnz Barz"), m("humanz", "Saturnz Barz (Official Video)")}, ClassSameRelease},
{"one album, video upload title with artist prefix", "acoustic",
[]ClassifyMember{m("humanz", "Andromeda"), m("humanz", "Gorillaz - Andromeda (Official Audio)")}, ClassSameRelease},
{"one album, identical bytes under a version title: one file in two slots", "exact",
[]ClassifyMember{m("www", "WWW"), m("www", "WWW (instrumental)")}, ClassMismatch},
{"one album, acoustic match, two versions named", "acoustic",
[]ClassifyMember{m("moe", "Lovesick"), m("moe", "Lovesick (Instrumental)")}, ClassDistinct},
{"Void: one album, one audio under three song titles", "acoustic",
[]ClassifyMember{m("void", "Slimewar (into the void)"), m("void", "Void"), m("void", "Reach")}, ClassMismatch},
{"a version named after a dash", "acoustic",
[]ClassifyMember{m("andora", "Goodbye Yesterday"), m("andora", "Goodbye Yesterday - Instrumental Version")}, ClassDistinct},
{"a dash that names no version", "acoustic",
[]ClassifyMember{m("a", "Monday Chill - Part One"), m("a", "Monday Chill - Part Two")}, ClassMismatch},
{"Slasher and Slasher II are not versions of one song", "acoustic",
[]ClassifyMember{m("slasher", "Slasher"), m("slasher", "Slasher II")}, ClassMismatch},
{"Big Me: the single and the album", "exact",
[]ClassifyMember{m("single", "Big Me"), m("album", "Big Me")}, ClassCrossRelease},
{"remaster and featuring credits are one song", "acoustic",
[]ClassifyMember{m("a", "Feel Good Inc. (2017 Remaster)"), m("b", "Feel Good Inc feat. De La Soul")}, ClassCrossRelease},
{"a later release retitles the recording", "acoustic",
[]ClassifyMember{m("coco", "Ragtime Cat"), m("single", "The Ragtime Cat")}, ClassCrossRelease},
{"identical bytes on two albums under two titles", "exact",
[]ClassifyMember{m("dookie", "F.O.D."), m("live", "F.O.D. (live)")}, ClassCrossRelease},
{"a lone copy", "exact", []ClassifyMember{m("a", "Song")}, ClassReview},
}
for _, tc := range cases {
if got := ClassifyDuplicateGroup(tc.tier, tc.members); got != tc.want {
t.Errorf("%s: got %s, want %s", tc.name, got, tc.want)
}
}
}
func TestMatchTitleKey(t *testing.T) {
cases := []struct{ title, artist, want string }{
{"Saturnz Barz (Official Video)", "Gorillaz", "saturnzbarz"},
{"Gorillaz - Saturnz Barz", "Gorillaz", "saturnzbarz"},
{"Doin' It Right (Music Video) ft. Panda Bear", "Daft Punk", "doinitright"},
{"Song [feat. X]", "", "song"},
{"Song - Remastered 2009", "", "song"},
{"Rock & Roll", "", "rockandroll"},
// What names a different recording stays.
{"Song (live)", "", "songlive"},
{"Song (demo)", "", "songdemo"},
{"Song (Remix)", "", "songremix"},
// A title that is nothing but a marker keeps its words rather than vanishing.
{"(Official Video)", "", "officialvideo"},
}
for _, tc := range cases {
if got := MatchTitleKey(tc.title, tc.artist); got != tc.want {
t.Errorf("MatchTitleKey(%q, %q) = %q, want %q", tc.title, tc.artist, got, tc.want)
}
}
}
// Copies the fold of 2026-10-20 merged although their file names said they
// were different versions; each pair must disagree. And copies of one version
// named differently must agree.
func TestVersionWords(t *testing.T) {
differ := [][2][2]string{
{{"Suite-Pee", "/m/SOAD/Sugar/System of a Down - Sugar - 02 - Suite-Pee.mp3"},
{"Suite-Pee", "/m/SOAD/Sugar/System of a Down - Sugar - 02 - Suite-Pee (Clean Version).mp3"}},
{{"Luv (sic) Grand Finale", "/m/Nujabes/Hexalogy/Nujabes - Luv(sic) Hexalogy - 06 - Luv (sic) Grand Finale Instrumentals.mp3"},
{"Luv (sic) Grand Finale", "/m/Nujabes/Hexalogy/0106 - Luvsic Grand Finale.m4a"}},
{{"Charger", "/m/Gorillaz/Humanz/Gorillaz - Humanz - 08 - Charger - live in Cologne 2017.mp3"},
{"Charger", "/m/Gorillaz/Humanz/CD 01/08 Charger.flac"}},
}
for _, p := range differ {
a, b := versionWords(p[0][0], p[0][1]), versionWords(p[1][0], p[1][1])
if a == b {
t.Errorf("versionWords agree (%q) on %s and %s", a, p[0][1], p[1][1])
}
}
agree := [][2][2]string{
{{"Heat Waves (Shakur Ahmad Remix)", "/m/GA/Glass Animals - Dreamland Deluxe - 02 - Heat Waves (Shakur Ahmad Remix).mp3"},
{"Heat Waves (Shakur Ahmad Remix)", "/m/GA/02. Heat Waves (Shakur Ahmad Remix).flac"}},
{{"Luv (sic) Instrumentals", "/m/Nujabes/0201 - Luvsic instrumentals.m4a"},
{"Luv (sic) Instrumentals", "/m/Nujabes/Nujabes - Luv(sic) Hexalogy - 01 - Luv (sic) Instrumentals.mp3"}},
{{"Henrietta", "/m/The Fratellis - Costello Music - 01 - Henrietta.mp3"},
{"Henrietta", "/m/The Fratellis - Costello Music - 01 - Henrietta.flac"}},
}
for _, p := range agree {
a, b := versionWords(p[0][0], p[0][1]), versionWords(p[1][0], p[1][1])
if a != b {
t.Errorf("versionWords %q vs %q on copies of one version: %s, %s", a, b, p[0][1], p[1][1])
}
}
}
func TestWrongSlots(t *testing.T) {
track := func(title string, ms int32) dbq.MbReleaseTrack { return dbq.MbReleaseTrack{Title: title, LengthMs: ms} }
copies := func(titles ...string) []slotCopy {
out := make([]slotCopy, len(titles))
for i, title := range titles {
out[i] = slotCopy{Title: title, Artist: "Savant", DurationMs: 220000}
}
return out
}
void := []dbq.MbReleaseTrack{track("Slimewar (Into the Void)", 300000), track("Reach", 281000), track("Void", 220500)}
cases := []struct {
name string
copies []slotCopy
tracks []dbq.MbReleaseTrack
keep int
losers []int
ok bool
}{
{"Void: the file is Void's; the other two slots are other songs", copies("Slimewar (into the void)", "Void", "Reach"), void, 1, []int{0, 2}, true},
{"no track list", copies("Void", "Reach"), nil, -1, nil, false},
{"a title the track list does not hold", copies("Void", "Bonus"), void, -1, nil, false},
{"Killjoy held twice beside Dreamland's slot", []slotCopy{
{Title: "Dreamland", Artist: "Fox Stevenson", DurationMs: 167000},
{Title: "Killjoy", Artist: "Fox Stevenson", DurationMs: 167000},
{Title: "Killjoy", Artist: "Fox Stevenson", DurationMs: 166000},
}, []dbq.MbReleaseTrack{track("Killjoy", 166500), track("Dreamland", 214000)}, 1, []int{0}, true},
{"two slots fit the file", copies("Void", "Reach"), []dbq.MbReleaseTrack{track("Void", 220000), track("Reach", 221500)}, -1, nil, false},
{"a slot near but not within the fit", copies("Void", "Reach"), []dbq.MbReleaseTrack{track("Void", 220000), track("Reach", 226000)}, -1, nil, false},
{"no slot fits, and every slot is clearly another song's", copies("Reach", "Slimewar (into the void)"), void, -1, []int{0, 1}, true},
{"no slot fits, but one is near", copies("Void", "Reach"), []dbq.MbReleaseTrack{track("Void", 226000), track("Reach", 281000)}, -1, nil, false},
{"an unknown length counts for nothing", copies("Void", "Reach"), []dbq.MbReleaseTrack{track("Void", 220000), track("Reach", 0)}, -1, nil, false},
}
for _, tc := range cases {
keep, losers, ok := wrongSlots(tc.copies, tc.tracks)
if keep != tc.keep || ok != tc.ok || fmt.Sprint(losers) != fmt.Sprint(tc.losers) {
t.Errorf("%s: got keep=%d losers=%v ok=%v, want keep=%d losers=%v ok=%v",
tc.name, keep, losers, ok, tc.keep, tc.losers, tc.ok)
}
}
}
// The lengths cannot place a recording filed as several songs (Savant's
// Mortals, cut to each slot's length); what AcoustID calls it can (#6169).
func TestNamedCopies(t *testing.T) {
track := func(title string, ms int32, rec string) dbq.MbReleaseTrack {
return dbq.MbReleaseTrack{Title: title, LengthMs: ms, RecordingMbid: rec}
}
copies := []slotCopy{
{Title: "Tormentor", Artist: "The Wombats", DurationMs: 220000},
{Title: "Record Shop", Artist: "The Wombats", DurationMs: 181000},
}
album := []dbq.MbReleaseTrack{
track("Tormentor", 221000, "rec-tormentor"), track("Record Shop", 180000, "rec-record-shop"), track("Ice Cream", 200000, "rec-ice"),
}
cases := []struct {
name string
copies []slotCopy
tracks []dbq.MbReleaseTrack
recordings []string
keep int
losers []int
ok bool
}{
{"AcoustID names Tormentor", copies, album, []string{"rec-tormentor"}, 0, []int{1}, true},
{"one of several candidates is on the album", copies, album, []string{"rec-elsewhere", "rec-record-shop"}, 1, []int{0}, true},
{"AcoustID knows none", copies, album, nil, -1, []int{0, 1}, true},
{"the recording is another song on the album", copies, album, []string{"rec-ice"}, -1, []int{0, 1}, true},
{"candidates in both copies' slots", copies, album, []string{"rec-tormentor", "rec-record-shop"}, -1, nil, false},
{"the track list names no recording for a slot", copies, []dbq.MbReleaseTrack{
track("Tormentor", 221000, "rec-tormentor"), track("Record Shop", 180000, ""),
}, []string{"rec-tormentor"}, -1, nil, false},
{"every copy filed as the one song named", []slotCopy{copies[0], copies[0]}, album, []string{"rec-tormentor"}, -1, nil, false},
}
for _, tc := range cases {
keep, losers, ok := namedCopies(tc.copies, tc.tracks, tc.recordings)
if keep != tc.keep || ok != tc.ok || fmt.Sprint(losers) != fmt.Sprint(tc.losers) {
t.Errorf("%s: got keep=%d losers=%v ok=%v, want keep=%d losers=%v ok=%v",
tc.name, keep, losers, ok, tc.keep, tc.losers, tc.ok)
}
}
}