Skip to content

Commit 648921b

Browse files
committed
Refactoring
1 parent bb6022c commit 648921b

File tree

8 files changed

+110
-96
lines changed

8 files changed

+110
-96
lines changed

src/main/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/EmbeddedRedisSpringExtension.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ internal class EmbeddedRedisSpringExtension : BeforeAllCallback, AfterEachCallba
4040

4141
private fun flushAll(extensionContext: ExtensionContext?) {
4242
val applicationContext = SpringExtension.getApplicationContext(extensionContext!!)
43-
JedisConnectionStore.getOrCreate(applicationContext).flushAll()
43+
RedisStore.client(applicationContext)!!.flushAll()
4444
}
4545
}

src/main/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/JedisConnectionStore.kt

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.github.tobi.laa.spring.boot.embedded.redis
22

3+
import io.github.tobi.laa.spring.boot.embedded.redis.conf.RedisConf
34
import org.springframework.context.ApplicationContext
5+
import redis.clients.jedis.JedisPooled
46
import redis.embedded.Redis
57
import java.util.concurrent.ConcurrentHashMap
68

@@ -9,13 +11,24 @@ import java.util.concurrent.ConcurrentHashMap
911
*/
1012
internal object RedisStore {
1113

12-
private val internalStore = ConcurrentHashMap<ApplicationContext, Redis>()
14+
private val internalStore = ConcurrentHashMap<ApplicationContext, Triple<Redis, RedisConf, JedisPooled>>()
1315

14-
internal fun computeIfAbsent(context: ApplicationContext, redis: () -> Redis): Redis {
15-
return internalStore.computeIfAbsent(context) { _ -> redis.invoke() }
16+
internal fun computeIfAbsent(
17+
context: ApplicationContext,
18+
supplier: () -> Triple<Redis, RedisConf, JedisPooled>
19+
) {
20+
internalStore.computeIfAbsent(context) { _ -> supplier.invoke() }
1621
}
1722

18-
internal fun get(context: ApplicationContext): Redis? {
19-
return internalStore[context]
23+
internal fun server(context: ApplicationContext): Redis? {
24+
return internalStore[context]?.first
25+
}
26+
27+
internal fun conf(context: ApplicationContext): RedisConf? {
28+
return internalStore[context]?.second
29+
}
30+
31+
internal fun client(context: ApplicationContext): JedisPooled? {
32+
return internalStore[context]?.third
2033
}
2134
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.github.tobi.laa.spring.boot.embedded.redis.conf
2+
3+
import redis.embedded.Redis
4+
import redis.embedded.RedisInstance
5+
import java.nio.file.Path
6+
import java.nio.file.Paths
7+
8+
private val ARGS_PROP = RedisInstance::class.java.declaredFields
9+
.filter { it.name == "args" }
10+
.map { field -> field.isAccessible = true; field }
11+
.first()
12+
13+
/**
14+
* Locates the temporary Redis configuration file created by an embedded Redis server.
15+
*/
16+
internal object RedisConfLocator {
17+
18+
@Suppress("UNCHECKED_CAST")
19+
internal fun locate(server: Redis): Path {
20+
val args = ARGS_PROP[server as RedisInstance] as List<String>?
21+
return args?.find { it.endsWith(".conf") }?.let { Paths.get(it) }
22+
?: throw IllegalStateException("No config file found for embedded Redis server: $server")
23+
}
24+
}

src/main/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/server/RedisServerContextCustomizer.kt

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,50 @@ package io.github.tobi.laa.spring.boot.embedded.redis.server
22

33
import io.github.tobi.laa.spring.boot.embedded.redis.PortProvider
44
import io.github.tobi.laa.spring.boot.embedded.redis.RedisStore
5+
import io.github.tobi.laa.spring.boot.embedded.redis.conf.RedisConf
6+
import io.github.tobi.laa.spring.boot.embedded.redis.conf.RedisConfLocator
7+
import io.github.tobi.laa.spring.boot.embedded.redis.conf.RedisConfParser
58
import org.springframework.boot.test.util.TestPropertyValues
69
import org.springframework.context.ConfigurableApplicationContext
710
import org.springframework.context.event.ContextClosedEvent
811
import org.springframework.test.context.ContextCustomizer
912
import org.springframework.test.context.MergedContextConfiguration
13+
import redis.clients.jedis.JedisPooled
1014
import redis.embedded.Redis
11-
import redis.embedded.RedisInstance
1215
import redis.embedded.RedisServer
1316
import redis.embedded.RedisServer.newRedisServer
1417
import redis.embedded.core.ExecutableProvider.newJarResourceProvider
1518
import java.io.File
1619
import kotlin.reflect.full.createInstance
17-
import kotlin.reflect.full.memberProperties
18-
import kotlin.reflect.jvm.isAccessible
1920

2021
internal class RedisServerContextCustomizer(
21-
val config: EmbeddedRedisServer,
22-
portProvider: PortProvider = PortProvider()
22+
private val config: EmbeddedRedisServer,
23+
private val portProvider: PortProvider = PortProvider()
2324
) : ContextCustomizer {
2425

25-
val port = if (config.port != 0) config.port else portProvider.next()
26-
val host = config.bind.ifEmpty { "127.0.0.1" }
27-
2826
override fun customizeContext(context: ConfigurableApplicationContext, mergedConfig: MergedContextConfiguration) {
29-
val redis = createAndStartServer(context)
30-
setSpringProperties(context, redis)
31-
addShutdownListener(context, redis)
27+
RedisStore.computeIfAbsent(context) {
28+
val server = createAndStartServer()
29+
val conf = parseConf(server)
30+
val client = createClient(server, conf)
31+
setSpringProperties(context, server, conf)
32+
addShutdownListener(context, server, client)
33+
Triple(server, conf, client)
34+
}
3235
}
3336

34-
private fun createAndStartServer(context: ConfigurableApplicationContext): Redis {
35-
val redis = RedisStore.computeIfAbsent(context) { createServer() }
36-
redis.start()
37-
return redis
37+
private fun createAndStartServer(): Redis {
38+
val server = createServer()
39+
server.start()
40+
return server
3841
}
3942

4043
private fun createServer(): RedisServer {
4144
val builder = newRedisServer()
42-
.port(port)
43-
.bind(host)
45+
.port(if (config.port != 0) config.port else portProvider.next())
46+
if (config.bind.isNotEmpty()) {
47+
builder.bind(config.bind)
48+
}
4449
if (config.configFile.isNotEmpty()) {
4550
builder.configFile(config.configFile)
4651
} else {
@@ -53,30 +58,27 @@ internal class RedisServerContextCustomizer(
5358
return builder.build()
5459
}
5560

56-
private fun setSpringProperties(context: ConfigurableApplicationContext, redis: Redis) {
61+
private fun parseConf(server: Redis): RedisConf =
62+
RedisConfLocator.locate(server).let { return RedisConfParser.parse(it) }
63+
64+
private fun createClient(server: Redis, conf: RedisConf): JedisPooled {
65+
return JedisPooled(conf.getBinds().first(), server.ports().first())
66+
}
67+
68+
private fun setSpringProperties(context: ConfigurableApplicationContext, server: Redis, conf: RedisConf) {
5769
TestPropertyValues.of(
5870
mapOf(
59-
"spring.data.redis.port" to redis.ports().first().toString(),
60-
"spring.data.redis.host" to host
71+
"spring.data.redis.port" to server.ports().first().toString(),
72+
"spring.data.redis.host" to conf.getBinds().first()
6173
)
6274
).applyTo(context.environment)
6375
}
6476

65-
@Suppress("UNCHECKED_CAST")
66-
private fun host(redis: Redis): String {
67-
val argsProperty = RedisInstance::class.memberProperties.firstOrNull { it.name == "args" }
68-
val args = argsProperty?.let {
69-
it.isAccessible = true
70-
it.get(redis as RedisInstance)
71-
} as List<String>
72-
println(args)
73-
return args.findLast { it.startsWith("bind") }?.split(" ")?.getOrNull(1) ?: "localhost"
74-
}
75-
76-
private fun addShutdownListener(context: ConfigurableApplicationContext, redis: Redis) {
77+
private fun addShutdownListener(context: ConfigurableApplicationContext, server: Redis, client: JedisPooled) {
7778
context.addApplicationListener { event ->
7879
if (event is ContextClosedEvent) {
79-
redis.stop()
80+
client.close()
81+
server.stop()
8082
}
8183
}
8284
}

src/test/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/RedisTests.kt

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package io.github.tobi.laa.spring.boot.embedded.redis
22

3+
import io.github.tobi.laa.spring.boot.embedded.redis.conf.RedisConf
34
import org.assertj.core.api.Assertions.assertThat
45
import org.assertj.core.api.ObjectAssert
56
import org.springframework.boot.autoconfigure.data.redis.RedisProperties
67
import org.springframework.context.ApplicationContext
78
import org.springframework.context.ApplicationContextAware
89
import org.springframework.data.redis.core.RedisTemplate
910
import org.springframework.stereotype.Component
10-
import redis.embedded.RedisInstance
11-
import java.io.File
1211
import java.util.*
1312
import kotlin.random.Random
1413

@@ -135,38 +134,28 @@ internal open class RedisTests(
135134
}
136135

137136
@Suppress("UNCHECKED_CAST")
138-
fun shouldHaveConfigFile(): ConfigFile {
139-
val redis = context?.let { RedisStore.get(it) }
140-
assertThat(redis).isNotNull
141-
142-
val argsProp = RedisInstance::class.java.declaredFields.firstOrNull { it.name == "args" }
143-
assertThat(argsProp).isNotNull
144-
145-
argsProp!!.isAccessible = true
146-
val args = argsProp.get(redis) as List<String>?
147-
assertThat(args).isNotNull
148-
149-
val configFile = args!!.find { it.endsWith(".conf") }?.let { File(it) }
150-
assertThat(configFile).isNotNull().exists()
151-
152-
return ConfigFile(configFile!!)
137+
fun shouldHaveConfig(): RedisConfAssertion {
138+
val conf = context?.let { RedisStore.conf(it) }!!
139+
return RedisConfAssertion(conf)
153140
}
154141
}
155142

156-
inner class ConfigFile(file: File) {
143+
inner class RedisConfAssertion(val conf: RedisConf) {
157144

158-
val settings = file.readLines()
159-
160-
fun and(): ConfigFile {
145+
fun and(): RedisConfAssertion {
161146
return this
162147
}
163148

164149
fun andAlso(): RedisTests {
165150
return this@RedisTests
166151
}
167152

168-
fun thatContainsSetting(setting: String): ConfigFile {
169-
assertThat(settings).contains(setting)
153+
fun thatContainsDirective(keyword: String, vararg arguments: String): RedisConfAssertion {
154+
return thatContainsDirective(RedisConf.Directive(keyword, *arguments))
155+
}
156+
157+
fun thatContainsDirective(directive: RedisConf.Directive): RedisConfAssertion {
158+
assertThat(conf.directives).contains(directive)
170159
return this
171160
}
172161
}

src/test/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/server/CustomizerTest.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import redis.embedded.core.RedisServerBuilder
1212
private const val TEST_PORT = 11111
1313

1414
@IntegrationTest
15-
@EmbeddedRedisServer(customizer = [WillBeIgnored::class, SetsPort::class, SetsProtectedMode::class])
15+
@EmbeddedRedisServer(
16+
customizer = [
17+
WillBeIgnored::class,
18+
SetsPortToTestPort::class,
19+
SetsBindToLoopbackIpv4::class,
20+
SetsProtectedMode::class]
21+
)
1622
@DisplayName("Using @EmbeddedRedisServer with several customizers")
1723
internal open class CustomizerTest {
1824

@@ -27,7 +33,7 @@ internal open class CustomizerTest {
2733
.whenDoingNothing()
2834
.then().redisProperties()
2935
.shouldBeStandalone().and()
30-
.shouldHaveHost("localhost").and()
36+
.shouldHaveHost("127.0.0.1").and()
3137
.shouldHavePort(TEST_PORT)
3238
}
3339

@@ -56,21 +62,28 @@ internal open class CustomizerTest {
5662
given.nothing()
5763
.whenDoingNothing()
5864
.then().embeddedRedis()
59-
.shouldHaveConfigFile().thatContainsSetting("protected-mode yes")
65+
.shouldHaveConfig().thatContainsDirective("protected-mode", "yes")
6066
}
6167

6268
class WillBeIgnored : RedisServerCustomizer {
6369
override fun accept(builder: RedisServerBuilder, config: EmbeddedRedisServer) {
6470
builder.port(1)
71+
builder.bind("zombo.com")
6572
}
6673
}
6774

68-
class SetsPort : RedisServerCustomizer {
75+
class SetsPortToTestPort : RedisServerCustomizer {
6976
override fun accept(builder: RedisServerBuilder, config: EmbeddedRedisServer) {
7077
builder.port(TEST_PORT)
7178
}
7279
}
7380

81+
class SetsBindToLoopbackIpv4 : RedisServerCustomizer {
82+
override fun accept(builder: RedisServerBuilder, config: EmbeddedRedisServer) {
83+
builder.bind("127.0.0.1")
84+
}
85+
}
86+
7487
class SetsProtectedMode : RedisServerCustomizer {
7588
override fun accept(builder: RedisServerBuilder, config: EmbeddedRedisServer) {
7689
builder.setting("protected-mode yes")

src/test/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/server/RedisConfFileTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ internal class RedisConfFileTest {
5555
given.nothing()
5656
.whenDoingNothing()
5757
.then().embeddedRedis()
58-
.shouldHaveConfigFile().thatContainsSetting("appendonly no")
59-
.and().thatContainsSetting("protected-mode yes")
60-
.and().thatContainsSetting("appendfsync everysec")
58+
.shouldHaveConfig().thatContainsDirective("appendonly", "no")
59+
.and().thatContainsDirective("protected-mode", "yes")
60+
.and().thatContainsDirective("appendfsync", "everysec")
6161
}
6262
}

0 commit comments

Comments
 (0)