Browse Source

Add initial integration tests/framework

tags/v0.1.0
Chris Smith 5 years ago
parent
commit
fb6a84184f

+ 22
- 3
build.gradle.kts View File

@@ -4,6 +4,11 @@ plugins {
4 4
     kotlin("jvm") version "1.3.0-rc-190"
5 5
 }
6 6
 
7
+configurations {
8
+    create("itestImplementation") { extendsFrom(getByName("testImplementation")) }
9
+    create("itestRuntime") { extendsFrom(getByName("testRuntime")) }
10
+}
11
+
7 12
 repositories {
8 13
     jcenter()
9 14
     mavenCentral()
@@ -16,15 +21,29 @@ dependencies {
16 21
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.2-eap13")
17 22
     implementation("io.ktor:ktor-network:1.0.0-beta-1")
18 23
 
19
-    testCompile("org.junit.jupiter:junit-jupiter-api:5.3.1")
20
-    testCompile("org.junit.jupiter:junit-jupiter-params:5.3.1")
21
-    testRuntime("org.junit.jupiter:junit-jupiter-engine:5.3.1")
22 24
     testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0-RC3")
25
+    testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
26
+    testImplementation("org.junit.jupiter:junit-jupiter-params:5.3.1")
27
+    testRuntime("org.junit.jupiter:junit-jupiter-engine:5.3.1")
23 28
 }
24 29
 
25 30
 java {
26 31
     sourceCompatibility = JavaVersion.VERSION_1_8
27 32
     targetCompatibility = JavaVersion.VERSION_1_8
33
+
34
+    sourceSets {
35
+        create("itest") {
36
+            compileClasspath += getByName("main").output
37
+            runtimeClasspath += getByName("main").output
38
+            java.srcDirs("src/itest/kotlin")
39
+        }
40
+    }
41
+}
42
+
43
+tasks.create<Test>("itest") {
44
+    group = "verification"
45
+    testClassesDirs = java.sourceSets.getByName("itest").output.classesDirs
46
+    classpath = java.sourceSets.getByName("itest").runtimeClasspath
28 47
 }
29 48
 
30 49
 tasks.withType<KotlinCompile> {

+ 79
- 0
src/itest/kotlin/com/dmdirc/irctest/Dsl.kt View File

@@ -0,0 +1,79 @@
1
+package com.dmdirc.irctest
2
+
3
+@DslMarker
4
+annotation class TestDslMarker
5
+
6
+fun testCase(name: String, data: TestCaseContext.() -> Unit) = TestCaseContext(name).apply(data)
7
+
8
+@TestDslMarker
9
+class TestCaseContext(val name: String) {
10
+
11
+    internal val config = ConfigContext()
12
+    internal val steps = StepsContext()
13
+
14
+    fun config(data: ConfigContext.() -> Unit) = config.data()
15
+    fun steps(data: StepsContext.() -> Unit) = steps.data()
16
+
17
+}
18
+
19
+@TestDslMarker
20
+class ConfigContext {
21
+
22
+    var nick = "nick"
23
+    var user = "ident"
24
+    var realName = "some name"
25
+    var password: String? = null
26
+
27
+}
28
+
29
+@TestDslMarker
30
+class StepsContext {
31
+
32
+    private val steps = mutableListOf<Step>()
33
+
34
+    fun send(line: String) { steps += SendStep(line) }
35
+    fun expect(line: String) { steps += SimpleExpectStep(line) }
36
+
37
+    fun expect(data: ExpectContext.() -> Unit) {
38
+        ExpectContext().data()
39
+    }
40
+
41
+    operator fun iterator() = steps.iterator()
42
+
43
+}
44
+
45
+open class Step
46
+class SendStep(val line: String) : Step()
47
+class SimpleExpectStep(val line: String): Step()
48
+
49
+@TestDslMarker
50
+class ExpectContext {
51
+
52
+    private var commandMatcher: ((String) -> Boolean)? = null
53
+    private var paramMatchers = HashMap<Int, (String) -> Boolean>()
54
+
55
+    val command: String = "COMMAND"
56
+    val parameters = ParameterProvider()
57
+
58
+    infix fun String.toMatch(regex: String) {
59
+        val matcher = { it: String -> it.matches(regex.toRegex()) }
60
+        when (this) {
61
+            command -> commandMatcher = matcher
62
+            else -> paramMatchers[this.toInt()] = matcher
63
+        }
64
+    }
65
+
66
+    infix fun String.toEqual(value: String) {
67
+        val matcher = { it: String -> it == value }
68
+        when (this) {
69
+            command -> commandMatcher = matcher
70
+            else -> paramMatchers[this.toInt()] = matcher
71
+        }
72
+    }
73
+}
74
+
75
+class ParameterProvider {
76
+    val count = "count"
77
+    operator fun get(index: Int) = "$index"
78
+}
79
+

+ 55
- 0
src/itest/kotlin/com/dmdirc/irctest/TestRunner.kt View File

@@ -0,0 +1,55 @@
1
+package com.dmdirc.irctest
2
+
3
+import com.dmdirc.irctest.cases.testCases
4
+import org.junit.jupiter.api.DynamicTest
5
+import java.io.BufferedReader
6
+import java.io.BufferedWriter
7
+import java.io.InputStreamReader
8
+import java.io.OutputStreamWriter
9
+import java.net.ServerSocket
10
+
11
+class IrcLibraryTests {
12
+
13
+    interface IrcLibrary {
14
+        fun connect(nick: String, ident: String, realName: String, password: String?)
15
+        fun terminate()
16
+    }
17
+
18
+    fun getTests(library: IrcLibrary, names: List<TestCaseContext> = testCases) = names.map { getTest(library, it) }
19
+
20
+    fun getTest(library: IrcLibrary, test: TestCaseContext): DynamicTest = DynamicTest.dynamicTest(test.name) {
21
+        ServerSocket(12321).use { serverSocket ->
22
+            library.connect(
23
+                    test.config.nick,
24
+                    test.config.user,
25
+                    test.config.realName,
26
+                    test.config.password
27
+            )
28
+
29
+            val clientSocket = serverSocket.accept()
30
+            val clientInput = BufferedReader(InputStreamReader(clientSocket.getInputStream()))
31
+            val clientOutput = BufferedWriter(OutputStreamWriter(clientSocket.getOutputStream()))
32
+            for (step in test.steps) {
33
+                when (step) {
34
+                    is SimpleExpectStep -> {
35
+                        while (true) {
36
+                            val read = clientInput.readLine()
37
+                            if (read == step.line) {
38
+                                println("     MATCH: $read")
39
+                                break
40
+                            } else {
41
+                                println("UNEXPECTED: $read")
42
+                            }
43
+                        }
44
+                    }
45
+                    is SendStep -> {
46
+                        println("      SENT: ${step.line}")
47
+                        clientOutput.write("${step.line}\r\n")
48
+                        clientOutput.flush()
49
+                    }
50
+                }
51
+            }
52
+        }
53
+    }
54
+
55
+}

+ 7
- 0
src/itest/kotlin/com/dmdirc/irctest/cases/TestCases.kt View File

@@ -0,0 +1,7 @@
1
+package com.dmdirc.irctest.cases
2
+
3
+val testCases = listOf(
4
+        com.dmdirc.irctest.cases.connection.password,
5
+        com.dmdirc.irctest.cases.connection.capabilities.v32.emptyList,
6
+        com.dmdirc.irctest.cases.connection.capabilities.v32.serverTime
7
+)

+ 14
- 0
src/itest/kotlin/com/dmdirc/irctest/cases/connection/Password.kt View File

@@ -0,0 +1,14 @@
1
+package com.dmdirc.irctest.cases.connection
2
+
3
+import com.dmdirc.irctest.testCase
4
+
5
+val password = testCase("connection.password") {
6
+    config {
7
+        password = "This is a test"
8
+        nick = "test"
9
+    }
10
+
11
+    steps {
12
+        expect("PASS :This is a test")
13
+    }
14
+}

+ 11
- 0
src/itest/kotlin/com/dmdirc/irctest/cases/connection/capabilities/v32/EmptyList.kt View File

@@ -0,0 +1,11 @@
1
+package com.dmdirc.irctest.cases.connection.capabilities.v32
2
+
3
+import com.dmdirc.irctest.testCase
4
+
5
+val emptyList = testCase("connection.capabilities.302.empty-list") {
6
+    steps {
7
+        expect("CAP LS 302")
8
+        send("CAP * LS :")
9
+        expect("CAP END")
10
+    }
11
+}

+ 13
- 0
src/itest/kotlin/com/dmdirc/irctest/cases/connection/capabilities/v32/ServerTime.kt View File

@@ -0,0 +1,13 @@
1
+package com.dmdirc.irctest.cases.connection.capabilities.v32
2
+
3
+import com.dmdirc.irctest.testCase
4
+
5
+val serverTime = testCase("connection.capabilities.302.server-time") {
6
+    steps {
7
+        expect("CAP LS 302")
8
+        send("CAP * LS :server-time")
9
+        expect("CAP REQ :server-time")
10
+        send("CAP * ACK :server-time")
11
+        expect("CAP END")
12
+    }
13
+}

+ 31
- 0
src/itest/kotlin/com/dmdirc/ktirc/KtIrcIntegrationTest.kt View File

@@ -0,0 +1,31 @@
1
+package com.dmdirc.ktirc
2
+
3
+import com.dmdirc.irctest.IrcLibraryTests
4
+import com.dmdirc.ktirc.model.Profile
5
+import com.dmdirc.ktirc.model.Server
6
+import kotlinx.coroutines.Dispatchers
7
+import kotlinx.coroutines.GlobalScope
8
+import kotlinx.coroutines.launch
9
+import org.junit.jupiter.api.TestFactory
10
+
11
+class KtIrcIntegrationTest {
12
+
13
+    @TestFactory
14
+    fun dynamicTests() = IrcLibraryTests().getTests(object : IrcLibraryTests.IrcLibrary {
15
+
16
+        private lateinit var ircClient : IrcClientImpl
17
+
18
+        override fun connect(nick: String, ident: String, realName: String, password: String?) {
19
+            ircClient = IrcClientImpl(Server("localhost", 12321, password = password), Profile(nick, ident, realName))
20
+            GlobalScope.launch(Dispatchers.IO) {
21
+                ircClient.connect()
22
+            }
23
+        }
24
+
25
+        override fun terminate() {
26
+            ircClient.disconnect()
27
+        }
28
+
29
+    })
30
+
31
+}

+ 4
- 0
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt View File

@@ -73,6 +73,10 @@ class IrcClientImpl(private val server: Server, private val profile: Profile) :
73 73
         }
74 74
     }
75 75
 
76
+    fun disconnect() {
77
+        socket?.disconnect()
78
+    }
79
+
76 80
     companion object {
77 81
         @JvmStatic
78 82
         fun main(args: Array<String>) {

+ 9
- 4
src/main/kotlin/com/dmdirc/ktirc/events/CapabilitiesHandler.kt View File

@@ -28,10 +28,15 @@ class CapabilitiesHandler : EventHandler {
28 28
         // TODO: We probably need to split the outgoing REQ lines if there are lots of caps
29 29
         // TODO: For caps with values we'll need to decide which value to use/whether to enable them/etc
30 30
         with (client.serverState.capabilities) {
31
-            negotiationState = CapabilitiesNegotiationState.AWAITING_ACK
32
-            advertisedCapabilities.keys.map { it.name }.let {
33
-                log.info { "Requesting capabilities: ${it.toList()}" }
34
-                client.send(capabilityRequestMessage(it))
31
+            if (advertisedCapabilities.keys.isEmpty()) {
32
+                negotiationState = CapabilitiesNegotiationState.FINISHED
33
+                client.send(capabilityEndMessage())
34
+            } else {
35
+                negotiationState = CapabilitiesNegotiationState.AWAITING_ACK
36
+                advertisedCapabilities.keys.map { it.name }.let {
37
+                    log.info { "Requesting capabilities: ${it.toList()}" }
38
+                    client.send(capabilityRequestMessage(it))
39
+                }
35 40
             }
36 41
         }
37 42
     }

+ 1
- 1
src/main/kotlin/com/dmdirc/ktirc/messages/CapabilityProcessor.kt View File

@@ -34,7 +34,7 @@ class CapabilityProcessor : MessageProcessor {
34 34
         get() = params.slice(1 until params.size)
35 35
 
36 36
     private val List<ByteArray>.capabilities
37
-        get() = String(last()).split(' ').toCapabilities()
37
+        get() = with (String(last())) { if (isEmpty()) emptyMap() else split(' ').toCapabilities() }
38 38
 
39 39
     private fun List<String>.toCapabilities() = sequence {
40 40
         forEach { cap ->

+ 20
- 0
src/test/kotlin/com/dmdirc/ktirc/events/CapabilitiesHandlerTest.kt View File

@@ -38,6 +38,8 @@ internal class CapabilitiesHandlerTest {
38 38
     @Test
39 39
     fun `CapabilitiesHandler updates negotiation state when capabilities finished`() {
40 40
         runBlocking {
41
+            serverState.capabilities.advertisedCapabilities[Capability.EchoMessages] = ""
42
+
41 43
             handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
42 44
 
43 45
             assertEquals(CapabilitiesNegotiationState.AWAITING_ACK, serverState.capabilities.negotiationState)
@@ -56,6 +58,24 @@ internal class CapabilitiesHandlerTest {
56 58
         }
57 59
     }
58 60
 
61
+    @Test
62
+    fun `CapabilitiesHandler sends END when blank capabilities received`() {
63
+        runBlocking {
64
+            handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
65
+
66
+            verify(ircClient).send("CAP END")
67
+        }
68
+    }
69
+
70
+    @Test
71
+    fun `CapabilitiesHandler updates negotiation when blank capabilities received`() {
72
+        runBlocking {
73
+            handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
74
+
75
+            assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
76
+        }
77
+    }
78
+
59 79
     @Test
60 80
     fun `CapabilitiesHandler sends END when capabilities acknowledged`() {
61 81
         runBlocking {

+ 9
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/CapabilityProcessorTest.kt View File

@@ -20,6 +20,15 @@ internal class CapabilityProcessorTest {
20 20
         assertTrue(events.isEmpty())
21 21
     }
22 22
 
23
+    @Test
24
+    fun `CapabilityProcessor raises ServerCapabilitiesReceived event given no capabilities`() {
25
+        val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", listOf("*", "LS", "").map { it.toByteArray() }))
26
+
27
+        val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
28
+        assertEquals(0, receivedEvent.capabilities.size)
29
+    }
30
+
31
+
23 32
     @Test
24 33
     fun `CapabilityProcessor raises ServerCapabilitiesReceived event with known capabilities`() {
25 34
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", listOf("*", "LS", "chghost extended-join invalid").map { it.toByteArray() }))

Loading…
Cancel
Save