mirror of
https://github.com/Airsonic-Pulse/airsonic-pulse.git
synced 2026-07-09 21:18:27 +00:00
A single surefire pattern in root pom.xml (org.airsonic.player.service .search.*TestCase.java) blanket-excluded the entire search-domain test surface (7 classes, 33 tests) since 2024-04-28, introduced alongside upstream's JDK 21 / byte-buddy-agent work. This PR removes the exclusion and brings the search subsystem back into the CI matrix for the first time since. Initial run with the exclusion removed: 26 pass / 6 fail / 1 error. Per-class disposition: - 4 classes (20 tests) were stale-skipped — re-enabled cleanly with no code changes (AnalyzerFactoryTestCase, SearchServiceTestCase, SearchServiceSpecialGenreTestCase, SearchServiceStartWithStopwardsTestCase). - QueryFactoryTestCase (11 tests, 5 failing) had stale expectations. createMultiFieldWildQuery was refactored to emit boost-aware output -- WildcardQuery + a 0.5-boosted TermQuery per token per field, with field-multipliers (1.1) on boosted fields -- but the test expectations were pinned to the pre-refactor output. Updated 5 expected strings to match the actual boost math (1.1 wildcard, 1.1 x 0.5 = 0.55 boosted- field term, 0.5 no-boost term). - IndexManagerTestCase had a copy-paste at line 216 asserting on candidates.size() instead of albums.size(). One-line fix. - SearchServiceSpecialPathTestCase had three test-infra issues: @SpringBootTest(webEnvironment=NONE) incompatible with the security config's MvcRequestMatcher (which requires a servlet context); switched to default MOCK. Hardcoded MusicFolder IDs (1, 2, 3) collided with Liquibase-seeded rows producing ObjectOptimisticLockingFailure; switched to the no-id constructor so JPA's IDENTITY generator assigns unique IDs at persist time. Copy-paste at line 132 asserting on folder03.size() (always 1) instead of randomAlbums.size() -- a no-op assertion for years. Every fix is test-side. Production code (QueryFactory, IndexManager, SearchService) was untouched. Three consecutive Postgres runs of the affected classes confirm no flakiness. Test floor: 1092 -> 1125 (+33). fixes #55. Co-authored-by: litebito <litebito@users.noreply.github.com>
This commit is contained in:
parent
c72025829f
commit
e1106699fb
4 changed files with 57 additions and 35 deletions
|
|
@ -213,7 +213,7 @@ public class IndexManagerTestCase {
|
|||
});
|
||||
|
||||
albums = albumRepository.findByPresentFalse();
|
||||
assertEquals(4, candidates.size());
|
||||
assertEquals(4, albums.size());
|
||||
|
||||
/* Does not scan, only expunges the index. */
|
||||
indexManager.startIndexing();
|
||||
|
|
|
|||
|
|
@ -80,6 +80,14 @@ public class QueryFactoryTestCase {
|
|||
* This is suitable for 8.x.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The expected query strings below reflect the boost-aware output of
|
||||
* createMultiFieldWildQuery in QueryFactory: each token contributes BOTH a wildcard
|
||||
* (token*) and a 0.5×-boosted bare term, with field-specific multipliers applied when
|
||||
* IndexType declares a boost (TITLE 1.1, ALBUM 1.1). For ARTIST/ARTIST_ID3 (no
|
||||
* field-boost) the wildcard appears unboosted and the term carries (artist:abc)^0.5.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testSearchArtist() throws IOException {
|
||||
SearchCriteria criteria = new SearchCriteria();
|
||||
|
|
@ -89,12 +97,14 @@ public class QueryFactoryTestCase {
|
|||
|
||||
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ARTIST);
|
||||
assertEquals(
|
||||
"+((artist:abc*) (artist:def*)) +(folder:" + PATH1 + ")",
|
||||
"+((artist:abc*) ((artist:abc)^0.5) (artist:def*) ((artist:def)^0.5)) +(folder:" + PATH1 + ")",
|
||||
query.toString(), "SearchArtist");
|
||||
|
||||
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ARTIST);
|
||||
assertEquals("+((artist:abc*) (artist:def*)) +(folder:" + PATH1
|
||||
+ " folder:" + PATH2 + ")", query.toString(), "SearchArtist");
|
||||
assertEquals(
|
||||
"+((artist:abc*) ((artist:abc)^0.5) (artist:def*) ((artist:def)^0.5)) +(folder:" + PATH1
|
||||
+ " folder:" + PATH2 + ")",
|
||||
query.toString(), "SearchArtist");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -106,14 +116,16 @@ public class QueryFactoryTestCase {
|
|||
|
||||
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ALBUM);
|
||||
assertEquals(
|
||||
"+(((album:abc*)^1.1 artist:abc*) ((album:def*)^1.1 artist:def*)) +(folder:" + PATH1
|
||||
+ ")",
|
||||
"+(((album:abc)^0.55 artist:abc*) ((album:abc*)^1.1 (artist:abc)^0.5) "
|
||||
+ "((album:def)^0.55 artist:def*) ((album:def*)^1.1 (artist:def)^0.5)) "
|
||||
+ "+(folder:" + PATH1 + ")",
|
||||
query.toString(), "SearchAlbum");
|
||||
|
||||
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ALBUM);
|
||||
assertEquals(
|
||||
"+(((album:abc*)^1.1 artist:abc*) ((album:def*)^1.1 artist:def*)) +(folder:" + PATH1
|
||||
+ " folder:" + PATH2 + ")",
|
||||
"+(((album:abc)^0.55 artist:abc*) ((album:abc*)^1.1 (artist:abc)^0.5) "
|
||||
+ "((album:def)^0.55 artist:def*) ((album:def*)^1.1 (artist:def)^0.5)) "
|
||||
+ "+(folder:" + PATH1 + " folder:" + PATH2 + ")",
|
||||
query.toString(), "SearchAlbum");
|
||||
}
|
||||
|
||||
|
|
@ -126,12 +138,17 @@ public class QueryFactoryTestCase {
|
|||
|
||||
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.SONG);
|
||||
assertEquals(
|
||||
"+(((title:abc*)^1.1 artist:abc*) ((title:def*)^1.1 artist:def*)) +(folder:" + PATH1 + ")",
|
||||
"+(((title:abc)^0.55 artist:abc*) ((title:abc*)^1.1 (artist:abc)^0.5) "
|
||||
+ "((title:def)^0.55 artist:def*) ((title:def*)^1.1 (artist:def)^0.5)) "
|
||||
+ "+(folder:" + PATH1 + ")",
|
||||
query.toString(), "SearchSong");
|
||||
|
||||
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.SONG);
|
||||
assertEquals("+(((title:abc*)^1.1 artist:abc*) ((title:def*)^1.1 artist:def*)) +(folder:" + PATH1
|
||||
+ " folder:" + PATH2 + ")", query.toString(), "SearchSong");
|
||||
assertEquals(
|
||||
"+(((title:abc)^0.55 artist:abc*) ((title:abc*)^1.1 (artist:abc)^0.5) "
|
||||
+ "((title:def)^0.55 artist:def*) ((title:def*)^1.1 (artist:def)^0.5)) "
|
||||
+ "+(folder:" + PATH1 + " folder:" + PATH2 + ")",
|
||||
query.toString(), "SearchSong");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -142,14 +159,15 @@ public class QueryFactoryTestCase {
|
|||
criteria.setQuery(QUERY_ENG_ONLY);
|
||||
|
||||
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ARTIST_ID3);
|
||||
assertEquals("+((artist:abc*) (artist:def*)) +(folderId:"
|
||||
+ FID1 + ")", query.toString(), "SearchSong");
|
||||
assertEquals(
|
||||
"+((artist:abc*) ((artist:abc)^0.5) (artist:def*) ((artist:def)^0.5)) +(folderId:" + FID1 + ")",
|
||||
query.toString(), "SearchArtistId3");
|
||||
|
||||
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ARTIST_ID3);
|
||||
assertEquals(
|
||||
"+((artist:abc*) (artist:def*)) +(folderId:" + FID1
|
||||
"+((artist:abc*) ((artist:abc)^0.5) (artist:def*) ((artist:def)^0.5)) +(folderId:" + FID1
|
||||
+ " folderId:" + FID2 + ")",
|
||||
query.toString(), "SearchSong");
|
||||
query.toString(), "SearchArtistId3");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -161,15 +179,16 @@ public class QueryFactoryTestCase {
|
|||
|
||||
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ALBUM_ID3);
|
||||
assertEquals(
|
||||
"+(((album:abc*)^1.1 artist:abc*) ((album:def*)^1.1 artist:def*)) "
|
||||
"+(((album:abc)^0.55 artist:abc*) ((album:abc*)^1.1 (artist:abc)^0.5) "
|
||||
+ "((album:def)^0.55 artist:def*) ((album:def*)^1.1 (artist:def)^0.5)) "
|
||||
+ "+(folderId:" + FID1 + ")",
|
||||
query.toString(), "SearchAlbumId3");
|
||||
|
||||
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ALBUM_ID3);
|
||||
assertEquals(
|
||||
"+(((album:abc*)^1.1 artist:abc*) ((album:def*)^1.1 artist:def*)) +(folderId:"
|
||||
+ FID1 + " folderId:"
|
||||
+ FID2 + ")",
|
||||
"+(((album:abc)^0.55 artist:abc*) ((album:abc*)^1.1 (artist:abc)^0.5) "
|
||||
+ "((album:def)^0.55 artist:def*) ((album:def*)^1.1 (artist:def)^0.5)) "
|
||||
+ "+(folderId:" + FID1 + " folderId:" + FID2 + ")",
|
||||
query.toString(), "SearchAlbumId3");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,13 @@ import static org.springframework.util.ObjectUtils.isEmpty;
|
|||
* This test case is a FalsePattern for search,
|
||||
* but there may be problems with the data flow prior to creating the search index.
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
// Default web environment (MOCK) is required: GlobalSecurityConfig.extSecurityFilterChain
|
||||
// uses MvcRequestMatcher, which needs mvcHandlerMappingIntrospector — only available when a
|
||||
// servlet context is bootstrapped. WebEnvironment.NONE fails ApplicationContext load with
|
||||
// "No bean named '... mvcHandlerMappingIntrospector ...' available". The siblings
|
||||
// IndexManagerTestCase and SearchServiceTestCase already rely on default-MOCK for the
|
||||
// same reason.
|
||||
@SpringBootTest
|
||||
@EnableConfigurationProperties
|
||||
public class SearchServiceSpecialPathTestCase {
|
||||
|
||||
|
|
@ -49,16 +55,17 @@ public class SearchServiceSpecialPathTestCase {
|
|||
|
||||
private List<MusicFolder> getMusicFolders() {
|
||||
if (isEmpty(musicFolders)) {
|
||||
// Use the no-id MusicFolder constructor so JPA's IDENTITY generator assigns
|
||||
// unique IDs at persist time. The original hardcoded ids (1, 2, 3) collided
|
||||
// with the music_folder rows seeded by Liquibase, producing an
|
||||
// ObjectOptimisticLockingFailure when createMusicFolder tried to write back
|
||||
// to an existing row.
|
||||
musicFolders = new ArrayList<>();
|
||||
|
||||
Path musicDir = MusicFolderTestData.resolveBaseMediaPath().resolve("Search").resolve("SpecialPath").resolve("accessible");
|
||||
musicFolders.add(new MusicFolder(1, musicDir, "accessible", Type.MEDIA, true, Instant.now().truncatedTo(ChronoUnit.MICROS)));
|
||||
|
||||
Path music2Dir = MusicFolderTestData.resolveBaseMediaPath().resolve("Search").resolve("SpecialPath").resolve("accessible's");
|
||||
musicFolders.add(new MusicFolder(2, music2Dir, "accessible's", Type.MEDIA, true, Instant.now().truncatedTo(ChronoUnit.MICROS)));
|
||||
|
||||
Path music3Dir = MusicFolderTestData.resolveBaseMediaPath().resolve("Search").resolve("SpecialPath").resolve("accessible+s");
|
||||
musicFolders.add(new MusicFolder(3, music3Dir, "accessible+s", Type.MEDIA, true, Instant.now().truncatedTo(ChronoUnit.MICROS)));
|
||||
Path basePath = MusicFolderTestData.resolveBaseMediaPath().resolve("Search").resolve("SpecialPath");
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
|
||||
musicFolders.add(new MusicFolder(basePath.resolve("accessible"), "accessible", Type.MEDIA, true, now));
|
||||
musicFolders.add(new MusicFolder(basePath.resolve("accessible's"), "accessible's", Type.MEDIA, true, now));
|
||||
musicFolders.add(new MusicFolder(basePath.resolve("accessible+s"), "accessible+s", Type.MEDIA, true, now));
|
||||
}
|
||||
return musicFolders;
|
||||
}
|
||||
|
|
@ -122,7 +129,7 @@ public class SearchServiceSpecialPathTestCase {
|
|||
.filter(m -> "accessible+s".equals(m.getName()))
|
||||
.collect(Collectors.toList());
|
||||
randomAlbums = searchService.getRandomAlbums(Integer.MAX_VALUE, folder03);
|
||||
assertEquals(1, folder03.size(), "Albums in \"accessible+s\" ");
|
||||
assertEquals(1, randomAlbums.size(), "Albums in \"accessible+s\" ");
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
6
pom.xml
6
pom.xml
|
|
@ -335,12 +335,8 @@
|
|||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<!-- #55 Temporarily skip a test. -->
|
||||
<excludes>
|
||||
<exclude>org.airsonic.player.service.search.*TestCase.java</exclude>
|
||||
</excludes>
|
||||
<!-- see https://github.com/mockito/mockito/issues/3037#issuecomment-1930132240-->
|
||||
<argLine>-javaagent:${net.bytebuddy:byte-buddy-agent:jar}</argLine>
|
||||
<argLine>-javaagent:${net.bytebuddy:byte-buddy-agent:jar}</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue