release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m28s
release / go (push) Failing after 1m31s
release / integration (push) Successful in 5m15s
release / android (push) Successful in 5m23s
release / Build signed APK (releases and dev) (push) Successful in 5m37s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Shnabubula's whole album, one 45-minute file, was filed as both Fading Light and Looking for Answers. Neither slot fits, so wrongSlots had nothing to keep and held the group. Every copy now goes when each is clearly another song's; a copy near its slot still holds the group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
5.8 KiB
Go
116 lines
5.8 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|