Skip to content

Commit 2892fcb

Browse files
authored
refactor(tests): share test containers across parallel JVM forks (#979)
Introduce SharedContainer, a file-based coordination mechanism that allows parallel jvm test forks to reuse a single Postgres, Elasticsearch, and Redis container via cross-process refcounting and file locks. - Add SharedContainer with acquire/release lifecycle, health checks, stale container cleanup, and shutdown hooks - Refactor DatabaseIntegrationSuite, ElasticsearchIntegrationSuite, and RedisIntegrationSuite to use SharedContainer instead of spawning individual containers per test suite - Append PID to search index names and database schema names to isolate parallel forks sharing the same container - Make search index properties lazy so they pick up test environment overrides
1 parent 56e0d34 commit 2892fcb

57 files changed

Lines changed: 775 additions & 570 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

article-api/src/test/scala/no/ndla/articleapi/UnitSuite.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ trait UnitSuite extends UnitTestSuite {
2727
setPropEnv("BRIGHTCOVE_PLAYER_ID", "some-player-id")
2828
setPropEnv("BRIGHTCOVE_API_CLIENT_ID", "some-client-id")
2929
setPropEnv("BRIGHTCOVE_API_CLIENT_SECRET", "some-secret")
30-
setPropEnv("SEARCH_INDEX_NAME", "article-integration-test-index")
30+
setPropEnv("SEARCH_INDEX_NAME", s"article-integration-test-index-${ProcessHandle.current().pid()}")
3131

3232
def toArticleRow(article: Article): ArticleRow = {
3333
ArticleRow(

article-api/src/test/scala/no/ndla/articleapi/service/search/ArticleSearchServiceTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ class ArticleSearchServiceTest extends ElasticsearchIntegrationSuite with UnitSu
250250
availability = Availability.everyone,
251251
)
252252

253-
override def beforeAll(): Unit = if (elasticSearchContainer.isSuccess) {
253+
override def beforeAll(): Unit = {
254254
super.beforeAll()
255255
articleIndexService.createIndexAndAlias().get
256256

audio-api/src/main/scala/no/ndla/audioapi/AudioApiProperties.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ class AudioApiProperties extends BaseProps with DatabaseProps with StrictLogging
4343

4444
val SearchServer: String = propOrElse("SEARCH_SERVER", "http://search-audio-api.ndla-local")
4545
val RunWithSignedSearchRequests: Boolean = propOrElse("RUN_WITH_SIGNED_SEARCH_REQUESTS", "true").toBoolean
46-
val SearchIndex: String = propOrElse("SEARCH_INDEX_NAME", "audios")
46+
lazy val SearchIndex: String = propOrElse("SEARCH_INDEX_NAME", "audios")
4747
val SearchDocument = "audio"
48-
val SeriesSearchIndex: String = propOrElse("SERIES_SEARCH_INDEX_NAME", "series")
48+
lazy val SeriesSearchIndex: String = propOrElse("SERIES_SEARCH_INDEX_NAME", "series")
4949
val SeriesSearchDocument = "series"
50-
val AudioTagSearchIndex: String = propOrElse("AUDIO_TAG_SEARCH_INDEX_NAME", "tags-audios")
50+
lazy val AudioTagSearchIndex: String = propOrElse("AUDIO_TAG_SEARCH_INDEX_NAME", "tags-audios")
5151
val AudioTagSearchDocument = "audio-tag"
5252
val DefaultPageSize = 10
5353
val MaxPageSize = 10000

audio-api/src/test/scala/no/ndla/audioapi/UnitSuite.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ trait UnitSuite extends UnitTestSuite with PrivateMethodTester {
2323
setPropEnv("SEARCH_SERVER", "search-server")
2424
setPropEnv("SEARCH_REGION", "some-region")
2525
setPropEnv("RUN_WITH_SIGNED_SEARCH_REQUESTS", "false")
26-
setPropEnv("SEARCH_INDEX_NAME", "audio-integration-test-index")
26+
private val pid = ProcessHandle.current().pid()
27+
setPropEnv("SEARCH_INDEX_NAME", s"audio-integration-test-index-$pid")
28+
setPropEnv("SERIES_SEARCH_INDEX_NAME", s"audio-series-$pid")
29+
setPropEnv("AUDIO_TAG_SEARCH_INDEX_NAME", s"audio-tags-$pid")
2730
setPropEnv("BRIGHTCOVE_API_CLIENT_ID", "client-id")
2831
setPropEnv("BRIGHTCOVE_API_CLIENT_SECRET", "client")
2932
setPropEnv("BRIGHTCOVE_ACCOUNT_ID", "312532")

audio-api/src/test/scala/no/ndla/audioapi/service/search/AudioSearchServiceTest.scala

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -237,19 +237,16 @@ class AudioSearchServiceTest extends ElasticsearchIntegrationSuite with UnitSuit
237237
when(converterService.toApiManuscript(any)).thenCallRealMethod()
238238
when(converterService.toApiCoverPhoto(any)).thenCallRealMethod()
239239
when(converterService.toApiPodcastMeta(any)).thenCallRealMethod()
240-
241-
if (elasticSearchContainer.isSuccess) {
242-
audioIndexService.createIndexAndAlias().get
243-
audioIndexService.indexDocument(audio1).get
244-
audioIndexService.indexDocument(audio2).get
245-
audioIndexService.indexDocument(audio3).get
246-
audioIndexService.indexDocument(audio4).get
247-
audioIndexService.indexDocument(audio5).get
248-
audioIndexService.indexDocument(audio6).get
249-
audioIndexService.indexDocument(audio7).get
250-
251-
blockUntil(() => audioSearchService.countDocuments == 7)
252-
}
240+
audioIndexService.createIndexAndAlias().get
241+
audioIndexService.indexDocument(audio1).get
242+
audioIndexService.indexDocument(audio2).get
243+
audioIndexService.indexDocument(audio3).get
244+
audioIndexService.indexDocument(audio4).get
245+
audioIndexService.indexDocument(audio5).get
246+
audioIndexService.indexDocument(audio6).get
247+
audioIndexService.indexDocument(audio7).get
248+
249+
blockUntil(() => audioSearchService.countDocuments == 7)
253250
}
254251

255252
test("That getStartAtAndNumResults returns default values for None-input") {

audio-api/src/test/scala/no/ndla/audioapi/service/search/SeriesSearchServiceTest.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,11 @@ class SeriesSearchServiceTest extends ElasticsearchIntegrationSuite with UnitSui
5252
)
5353

5454
override def beforeEach(): Unit = {
55-
if (elasticSearchContainer.isSuccess) {
56-
seriesIndexService.createIndexAndAlias()
57-
}
55+
seriesIndexService.createIndexAndAlias()
5856
}
5957

6058
override def afterEach(): Unit = {
61-
if (elasticSearchContainer.isSuccess) {
62-
seriesIndexService.deleteIndexAndAlias()
63-
}
59+
seriesIndexService.deleteIndexAndAlias()
6460
}
6561

6662
def indexAndWait(series: Seq[domain.Series]): Unit = {

audio-api/src/test/scala/no/ndla/audioapi/service/search/TagSearchServiceTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class TagSearchServiceTest extends ElasticsearchIntegrationSuite with TestEnviro
4747

4848
val audiosToIndex: Seq[AudioMetaInformation] = Seq(audio1, audio2, audio3, audio4)
4949

50-
override def beforeAll(): Unit = if (elasticSearchContainer.isSuccess) {
50+
override def beforeAll(): Unit = {
5151
super.beforeAll()
5252
tagIndexService.createIndexAndAlias().get
5353

common/src/test/scala/no/ndla/common/model/NDLADateTest.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ class NDLADateTest extends DatabaseIntegrationSuite, UnitTestSuite, TestEnvironm
2626
override def beforeAll(): Unit = {
2727
super.beforeAll()
2828
dataSource.connectToDatabase()
29+
val schemaSql = SQLSyntax.createUnsafely(schemaName)
2930
DB.autoCommit { implicit session =>
3031
sql"""
31-
create schema if not exists testschema;
32-
create table test_tz (id int primary key, data timestamptz);
33-
create table test_without_tz (id int primary key, data timestamp);""".execute()
32+
create schema if not exists $schemaSql;
33+
create table if not exists test_tz (id int primary key, data timestamptz);
34+
create table if not exists test_without_tz (id int primary key, data timestamp);""".execute()
3435
}
3536
}
3637

concept-api/src/test/scala/no/ndla/conceptapi/UnitSuite.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ trait UnitSuite extends UnitTestSuite {
1717
setPropEnv("SEARCH_SERVER", "some-server")
1818
setPropEnv("SEARCH_REGION", "some-region")
1919
setPropEnv("RUN_WITH_SIGNED_SEARCH_REQUESTS", "false")
20-
setPropEnv("SEARCH_INDEX_NAME", "draft-integration-test-index")
21-
setPropEnv("CONCEPT_SEARCH_INDEX_NAME", "concept-integration-test-index")
20+
private val pid = ProcessHandle.current().pid()
21+
setPropEnv("SEARCH_INDEX_NAME", s"draft-integration-test-index-$pid")
22+
setPropEnv("CONCEPT_SEARCH_INDEX_NAME", s"concept-integration-test-index-$pid")
2223

2324
setPropEnv("AUDIO_API_URL", "localhost:30014")
2425
setPropEnv("IMAGE_API_URL", "localhost:30001")

concept-api/src/test/scala/no/ndla/conceptapi/service/search/DraftConceptSearchServiceTest.scala

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -265,27 +265,25 @@ class DraftConceptSearchServiceTest extends ElasticsearchIntegrationSuite with T
265265

266266
override def beforeAll(): Unit = {
267267
super.beforeAll()
268-
if (elasticSearchContainer.isSuccess) {
269-
draftConceptIndexService.createIndexAndAlias().get
270-
271-
draftConceptIndexService.indexDocument(concept1).get
272-
draftConceptIndexService.indexDocument(concept2).get
273-
draftConceptIndexService.indexDocument(concept3).get
274-
draftConceptIndexService.indexDocument(concept4).get
275-
draftConceptIndexService.indexDocument(concept5).get
276-
draftConceptIndexService.indexDocument(concept6).get
277-
draftConceptIndexService.indexDocument(concept7).get
278-
draftConceptIndexService.indexDocument(concept8).get
279-
draftConceptIndexService.indexDocument(concept9).get
280-
draftConceptIndexService.indexDocument(concept10).get
281-
draftConceptIndexService.indexDocument(concept11).get
282-
draftConceptIndexService.indexDocument(concept12).get
283-
draftConceptIndexService.indexDocument(concept13).get
284-
285-
blockUntil(() => {
286-
draftConceptSearchService.countDocuments == 13
287-
})
288-
}
268+
draftConceptIndexService.createIndexAndAlias().get
269+
270+
draftConceptIndexService.indexDocument(concept1).get
271+
draftConceptIndexService.indexDocument(concept2).get
272+
draftConceptIndexService.indexDocument(concept3).get
273+
draftConceptIndexService.indexDocument(concept4).get
274+
draftConceptIndexService.indexDocument(concept5).get
275+
draftConceptIndexService.indexDocument(concept6).get
276+
draftConceptIndexService.indexDocument(concept7).get
277+
draftConceptIndexService.indexDocument(concept8).get
278+
draftConceptIndexService.indexDocument(concept9).get
279+
draftConceptIndexService.indexDocument(concept10).get
280+
draftConceptIndexService.indexDocument(concept11).get
281+
draftConceptIndexService.indexDocument(concept12).get
282+
draftConceptIndexService.indexDocument(concept13).get
283+
284+
blockUntil(() => {
285+
draftConceptSearchService.countDocuments == 13
286+
})
289287
}
290288

291289
test("That getStartAtAndNumResults returns SEARCH_MAX_PAGE_SIZE for value greater than SEARCH_MAX_PAGE_SIZE") {

0 commit comments

Comments
 (0)