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) } } } // 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) } } }