diff --git a/.travis.yml b/.travis.yml index 1521776..6985902 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,5 +13,4 @@ script: after_success: - "chmod a+x automerger" - - "BRANCHES_TO_MERGE_REGEX='-dev$' BRANCH_TO_MERGE_INTO=master GITHUB_REPO=Dragas/bIRC" - ./automerger \ No newline at end of file diff --git a/async/build.gradle b/async/build.gradle new file mode 100644 index 0000000..906b052 --- /dev/null +++ b/async/build.gradle @@ -0,0 +1,41 @@ +group 'lt.saltyjuice.dragas' +version '3.0.1' + +buildscript { + ext.kotlin_version = '1.1.4-2' + + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'java' +apply plugin: 'kotlin' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://oss.sonatype.org/content/repositories/releases" } + jcenter() +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile "lt.saltyjuice.dragas:chatty-core:3.0.1" + compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.18' + testCompile group: 'junit', name: 'junit', version: '4.12' +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +//apply from: "../upload.gradle" \ No newline at end of file diff --git a/async/gradle.properties b/async/gradle.properties new file mode 100644 index 0000000..420eebe --- /dev/null +++ b/async/gradle.properties @@ -0,0 +1,3 @@ +mProjectName="chatty-async" +mArchiveBaseName=chatty-async +mProjectDescription="Chatty Async implementation" \ No newline at end of file diff --git a/async/readme.md b/async/readme.md new file mode 100644 index 0000000..18b87b5 --- /dev/null +++ b/async/readme.md @@ -0,0 +1,4 @@ +# Chatty/Async + +Package meant to implement asynchronous behavior for Chatty bots. Currently depends on +`chatty-core:3.0.0` \ No newline at end of file diff --git a/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/main/AsyncClient.kt b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/main/AsyncClient.kt new file mode 100644 index 0000000..c517c7d --- /dev/null +++ b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/main/AsyncClient.kt @@ -0,0 +1,50 @@ +package lt.saltyjuice.dragas.chatty.v3.async.main + +import kotlinx.coroutines.experimental.CommonPool +import kotlinx.coroutines.experimental.Job +import kotlinx.coroutines.experimental.channels.Channel +import kotlinx.coroutines.experimental.channels.consumeEach +import kotlinx.coroutines.experimental.launch +import lt.saltyjuice.dragas.chatty.v3.async.route.AsyncController +import lt.saltyjuice.dragas.chatty.v3.async.route.AsyncRouter +import lt.saltyjuice.dragas.chatty.v3.core.main.Client +import lt.saltyjuice.dragas.chatty.v3.core.route.Controller + +/** + * Asynchronous implementation of [Client]. + * + * Supports using both [Controller] and [AsyncController]. + * + * This implementation provides an additional field - [responseChannel], which is used to listen for responses + * generated by triggered routes. + */ +abstract class AsyncClient : Client() +{ + /** + * Used to build [AsyncRouter] + */ + protected open val responseChannel = Channel(Channel.UNLIMITED) + /** + * Holds reference to all listener jobs provided by this client. + */ + protected open val listenerJobs: ArrayList = ArrayList() + /** + * Holds the count of how many listeners for responses the should be at most. + */ + protected open val mostJobs: Int = Runtime.getRuntime().availableProcessors() + override abstract val router: AsyncRouter + + override fun onConnect() + { + repeat(Math.min(Runtime.getRuntime().availableProcessors(), mostJobs)) + { + listenerJobs.add(launch(CommonPool) { responseChannel.consumeEach(this@AsyncClient::writeResponse) }) + } + } + + override fun onDisconnect() + { + listenerJobs.forEach { it.cancel() } + listenerJobs.clear() + } +} \ No newline at end of file diff --git a/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncController.kt b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncController.kt new file mode 100644 index 0000000..4b8e7e2 --- /dev/null +++ b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncController.kt @@ -0,0 +1,36 @@ +package lt.saltyjuice.dragas.chatty.v3.async.route + +import kotlinx.coroutines.experimental.CommonPool +import kotlinx.coroutines.experimental.channels.SendChannel +import kotlinx.coroutines.experimental.launch +import lt.saltyjuice.dragas.chatty.v3.core.route.Controller + +open class AsyncController : Controller() +{ + private var listener: SendChannel? = null + + /** + * Equivalent to calling writeResponse(response, false) + */ + override fun writeResponse(response: Response) + { + writeResponse(response, false) + } + + /** + * Writes response to response listener, when [now] is true. Otherwise, response + * is written to internal buffer which is later consumed and cleaned out. + */ + open fun writeResponse(response: Response, now: Boolean) + { + if (now) + launch(CommonPool) { listener!!.send(response) } + else + super.writeResponse(response) + } + + open fun listen(listener: SendChannel) + { + this.listener = listener + } +} \ No newline at end of file diff --git a/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncRoute.kt b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncRoute.kt new file mode 100644 index 0000000..978b46d --- /dev/null +++ b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncRoute.kt @@ -0,0 +1,138 @@ +package lt.saltyjuice.dragas.chatty.v3.async.route + +import kotlinx.coroutines.experimental.CommonPool +import kotlinx.coroutines.experimental.channels.SendChannel +import kotlinx.coroutines.experimental.channels.actor +import kotlinx.coroutines.experimental.channels.consumeEach +import kotlinx.coroutines.experimental.launch +import lt.saltyjuice.dragas.chatty.v3.core.exception.RouteBuilderException +import lt.saltyjuice.dragas.chatty.v3.core.middleware.AfterMiddleware +import lt.saltyjuice.dragas.chatty.v3.core.middleware.BeforeMiddleware +import lt.saltyjuice.dragas.chatty.v3.core.route.Controller +import lt.saltyjuice.dragas.chatty.v3.core.route.Route +import java.lang.reflect.Method + +/** + * + * Asynchronous route, that implements techniques to asynchronously receive responses as well as + * + * @see Route + */ +open class AsyncRoute : Route() +{ + /** + * Provided by client while building the routes. This is how client listens to any responses generated by routes. + */ + protected open lateinit var responseChannel: SendChannel + /** + * + */ + protected open val listener = actor(CommonPool) + { + channel.consumeEach { this@AsyncRoute.attemptRespondSuspend(it) } + } + + override fun getCurrentControllerInstance(): AsyncController + { + return super.getCurrentControllerInstance() as AsyncController + } + + override fun getControllerInstance(): AsyncController + { + val controller = super.getControllerInstance() as AsyncController + controller.listen(listener) + return controller + //return super.getControllerInstance().apply { if (this is AsyncController) listen(listener) } + } + + /** + * Tests [response] if it can be responded with and pushes it to [responseChannel] + */ + override fun attemptRespond(response: Response) + { + launch(CommonPool) { attemptRespondSuspend(response) } + } + + /** + * Suspendable version of [attemptRespond] + */ + open suspend fun attemptRespondSuspend(response: Response) + { + if (canRespond(response)) responseChannel.send(response) + } + + /** + * In case of application shut down, this method should be called to clean this route's listener. + */ + open fun close() + { + listener.cancel() + } + + /** + * Returns an empty list, since it isn't necessary anymore. + */ + override fun getResponses(): List + { + return listOf() + } + + abstract class Builder : Route.Builder() + { + protected open var mChannel: SendChannel? = null + + abstract override fun returnableRoute(): AsyncRoute + + override fun adapt(route: Route): AsyncRoute + { + val adapted = super.adapt(route) as AsyncRoute + adapted.responseChannel = this.mChannel!! + return adapted + } + + override fun after(clazz: Class>): AsyncRoute.Builder + { + return super.after(clazz) as AsyncRoute.Builder + } + + override fun before(clazz: Class>): AsyncRoute.Builder + { + return super.before(clazz) as AsyncRoute.Builder + } + + override fun callback(callback: (Route, Request) -> Unit): AsyncRoute.Builder + { + return super.callback(callback) as AsyncRoute.Builder + } + + override fun consume(controller: Class>, method: Method): AsyncRoute.Builder + { + if (!AsyncController::class.java.isAssignableFrom(controller)) + throw RouteBuilderException("Async routes need to use async controllers.") + return super.consume(controller, method) as AsyncRoute.Builder + } + + override fun controller(clazz: Class>): AsyncRoute.Builder + { + if (!AsyncController::class.java.isAssignableFrom(clazz)) + throw RouteBuilderException("Async routes need to use async controllers.") + return super.controller(clazz) as AsyncRoute.Builder + } + + override fun description(string: String): AsyncRoute.Builder + { + return super.description(string) as AsyncRoute.Builder + } + + override fun testCallback(callback: (Route, Request) -> Boolean): AsyncRoute.Builder + { + return super.testCallback(callback) as AsyncRoute.Builder + } + + open fun responseChannel(channel: SendChannel): AsyncRoute.Builder + { + this.mChannel = channel + return this + } + } +} \ No newline at end of file diff --git a/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncRouter.kt b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncRouter.kt new file mode 100644 index 0000000..d5d6784 --- /dev/null +++ b/async/src/main/kotlin/lt/saltyjuice/dragas/chatty/v3/async/route/AsyncRouter.kt @@ -0,0 +1,26 @@ +package lt.saltyjuice.dragas.chatty.v3.async.route + +import kotlinx.coroutines.experimental.channels.SendChannel +import lt.saltyjuice.dragas.chatty.v3.core.route.Route +import lt.saltyjuice.dragas.chatty.v3.core.route.Router + +/** + * Asynchronous implementation of core router. The difference is that you need to provide a [responseChannel], + * which is provided to all [AsyncRoute] based routes, so that they could send back generated responses at any time. + * + * @see Router + */ +abstract class AsyncRouter(protected open val responseChannel: SendChannel) : Router() +{ + abstract override fun builder(): AsyncRoute.Builder + + /** + * Any routes being added should use this method instead of [add(Route)], as it appends [responseChannel] to that route. + */ + override fun add(route: Route.Builder) + { + if (route is AsyncRoute.Builder) + route.responseChannel(responseChannel) + super.add(route) + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/controller/MockController.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/controller/MockController.kt new file mode 100644 index 0000000..28d4844 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/controller/MockController.kt @@ -0,0 +1,33 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.controller + +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.async.route.AsyncController +import lt.saltyjuice.dragas.chatty.v3.core.route.On +import lt.saltyjuice.dragas.chatty.v3.core.route.When + +class MockController : AsyncController() +{ + @On(MockRequest::class) + @When("isEven") + fun provideModuloOfTwoWritesLater(request: MockRequest) + { + writeResponse((request % 2).toFloat()) + } + + fun isEven(request: MockRequest): Boolean + { + return request % 2 == 0 + } + + @On(MockRequest::class) + @When("isOdd") + fun providesModuloOfTwoWritesNow(request: MockRequest) + { + writeResponse((request % 2).toFloat(), true) + } + + fun isOdd(request: MockRequest): Boolean + { + return request % 2 != 0 + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/controller/NotAsyncController.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/controller/NotAsyncController.kt new file mode 100644 index 0000000..2584713 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/controller/NotAsyncController.kt @@ -0,0 +1,21 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.controller + +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.core.route.Controller +import lt.saltyjuice.dragas.chatty.v3.core.route.On +import lt.saltyjuice.dragas.chatty.v3.core.route.When + +class NotAsyncController : Controller() +{ + @On(Int::class) + @When("isEven") + fun onEven(request: MockRequest) + { + writeResponse(request.value.toFloat()) + } + + fun isEven(request: MockRequest): Boolean + { + return request % 2 == 0 + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/io/MockAdapter.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/io/MockAdapter.kt new file mode 100644 index 0000000..0e4e62e --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/io/MockAdapter.kt @@ -0,0 +1,18 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.io + +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.core.adapter.Deserializer +import lt.saltyjuice.dragas.chatty.v3.core.adapter.Serializer + +class MockAdapter : Serializer, Deserializer +{ + override fun serialize(any: Float): Float + { + return any + } + + override fun deserialize(block: Int): MockRequest + { + return MockRequest(block) + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/io/MockEndpoint.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/io/MockEndpoint.kt new file mode 100644 index 0000000..c5d2f6b --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/io/MockEndpoint.kt @@ -0,0 +1,22 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.io + +import kotlinx.coroutines.experimental.channels.Channel +import kotlinx.coroutines.experimental.runBlocking +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.core.io.Input +import lt.saltyjuice.dragas.chatty.v3.core.io.Output + +class MockEndpoint(private val requestChannel: Channel, private val responseChannel: Channel) : Input, Output +{ + override val adapter: MockAdapter = MockAdapter() + + override fun getRequest(): MockRequest = runBlocking() + { + requestChannel.receive() + } + + override fun writeResponse(response: Float) = runBlocking() + { + responseChannel.send(response) + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/main/MockClient.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/main/MockClient.kt new file mode 100644 index 0000000..b5c404d --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/main/MockClient.kt @@ -0,0 +1,36 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.main + +import lt.saltyjuice.dragas.chatty.v3.async.main.AsyncClient +import lt.saltyjuice.dragas.chatty.v3.async.mock.controller.MockController +import lt.saltyjuice.dragas.chatty.v3.async.mock.io.MockEndpoint +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.async.mock.route.MockRouter +import lt.saltyjuice.dragas.chatty.v3.core.route.UsesControllers + +@UsesControllers(MockController::class) +class MockClient(endpoint: MockEndpoint) : AsyncClient() +{ + override val sout: MockEndpoint = endpoint + override val router: MockRouter = MockRouter(responseChannel) + override val sin: MockEndpoint = endpoint + + override fun onConnect() + { + super.onConnect() + } + + /*override fun writeResponse(response: Float) + { + super.writeResponse(response) + }*/ + + override fun connect(): Boolean + { + return true + } + + override fun isConnected(): Boolean + { + return true + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/message/MockRequest.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/message/MockRequest.kt new file mode 100644 index 0000000..c213622 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/message/MockRequest.kt @@ -0,0 +1,9 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.message + +class MockRequest(val value: Int) +{ + operator fun rem(int: Int): Int + { + return value % int + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/route/MockRoute.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/route/MockRoute.kt new file mode 100644 index 0000000..9636e41 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/route/MockRoute.kt @@ -0,0 +1,66 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.route + +import kotlinx.coroutines.experimental.channels.SendChannel +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.async.route.AsyncRoute +import lt.saltyjuice.dragas.chatty.v3.core.middleware.AfterMiddleware +import lt.saltyjuice.dragas.chatty.v3.core.middleware.BeforeMiddleware +import lt.saltyjuice.dragas.chatty.v3.core.route.Controller +import lt.saltyjuice.dragas.chatty.v3.core.route.Route +import java.lang.reflect.Method + +open class MockRoute : AsyncRoute() +{ + open class Builder : AsyncRoute.Builder() + { + override fun returnableRoute(): MockRoute + { + return MockRoute() + } + + override fun adapt(route: Route): MockRoute + { + return super.adapt(route) as MockRoute + } + + override fun after(clazz: Class>): Builder + { + return super.after(clazz) as Builder + } + + override fun before(clazz: Class>): Builder + { + return super.before(clazz) as Builder + } + + override fun callback(callback: (Route, MockRequest) -> Unit): Builder + { + return super.callback(callback) as Builder + } + + override fun consume(controller: Class>, method: Method): Builder + { + return super.consume(controller, method) as Builder + } + + override fun controller(clazz: Class>): Builder + { + return super.controller(clazz) as Builder + } + + override fun description(string: String): Builder + { + return super.description(string) as Builder + } + + override fun testCallback(callback: (Route, MockRequest) -> Boolean): Builder + { + return super.testCallback(callback) as Builder + } + + override fun responseChannel(channel: SendChannel): Builder + { + return super.responseChannel(channel) as Builder + } + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/route/MockRouter.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/route/MockRouter.kt new file mode 100644 index 0000000..d395548 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/mock/route/MockRouter.kt @@ -0,0 +1,13 @@ +package lt.saltyjuice.dragas.chatty.v3.async.mock.route + +import kotlinx.coroutines.experimental.channels.SendChannel +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.async.route.AsyncRouter + +class MockRouter(channel: SendChannel) : AsyncRouter(channel) +{ + override fun builder(): MockRoute.Builder + { + return MockRoute.Builder() + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/ClientTest.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/ClientTest.kt new file mode 100644 index 0000000..e34575b --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/ClientTest.kt @@ -0,0 +1,46 @@ +package lt.saltyjuice.dragas.chatty.v3.async.unit + +import kotlinx.coroutines.experimental.channels.Channel +import kotlinx.coroutines.experimental.runBlocking +import lt.saltyjuice.dragas.chatty.v3.async.mock.io.MockEndpoint +import lt.saltyjuice.dragas.chatty.v3.async.mock.main.MockClient +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import org.junit.Assert +import org.junit.BeforeClass +import org.junit.Rule +import org.junit.Test +import org.junit.rules.Timeout +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import java.util.concurrent.TimeUnit + +@RunWith(JUnit4::class) +class ClientTest +{ + @Rule + @JvmField + val timeout = Timeout(1000, TimeUnit.MILLISECONDS) + + @Test + fun clientIsCapableOfConsumeAndResponding() = runBlocking() + { + requestChannel.send(MockRequest(5)) + client.run() + Assert.assertEquals(1.0f, responseChannel.receive()) + } + + companion object + { + private lateinit var client: MockClient + private val responseChannel: Channel = Channel(1) + private val requestChannel: Channel = Channel(1) + @BeforeClass + @JvmStatic + fun init() + { + client = MockClient(MockEndpoint(requestChannel, responseChannel)) + client.initialize() + client.onConnect() + } + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/ControllerTest.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/ControllerTest.kt new file mode 100644 index 0000000..4b05d43 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/ControllerTest.kt @@ -0,0 +1,49 @@ +package lt.saltyjuice.dragas.chatty.v3.async.unit + +import kotlinx.coroutines.experimental.channels.Channel +import kotlinx.coroutines.experimental.runBlocking +import lt.saltyjuice.dragas.chatty.v3.async.mock.controller.MockController +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import org.junit.* +import org.junit.rules.Timeout +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import java.util.concurrent.TimeUnit + +@RunWith(JUnit4::class) +open class ControllerTest +{ + @Rule + @JvmField + public val timeout = Timeout(1000, TimeUnit.MILLISECONDS) + + @Test + fun controllerWritesResponseNow() = runBlocking() + { + controller.providesModuloOfTwoWritesNow(MockRequest(2)) + Assert.assertEquals(0f, channel.receive()) + } + + companion object + { + @JvmStatic + private val controller: MockController = MockController() + + @JvmStatic + private val channel: Channel = Channel() + + @JvmStatic + @BeforeClass + fun init() + { + controller.listen(channel) + } + + @JvmStatic + @AfterClass + fun destroy() + { + channel.close() + } + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/RouteTest.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/RouteTest.kt new file mode 100644 index 0000000..21002b6 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/RouteTest.kt @@ -0,0 +1,97 @@ +package lt.saltyjuice.dragas.chatty.v3.async.unit + +import kotlinx.coroutines.experimental.channels.Channel +import kotlinx.coroutines.experimental.runBlocking +import lt.saltyjuice.dragas.chatty.v3.async.mock.controller.MockController +import lt.saltyjuice.dragas.chatty.v3.async.mock.message.MockRequest +import lt.saltyjuice.dragas.chatty.v3.async.mock.route.MockRoute +import org.junit.AfterClass +import org.junit.Assert +import org.junit.Rule +import org.junit.Test +import org.junit.rules.Timeout +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import java.util.concurrent.TimeUnit + +@RunWith(JUnit4::class) +class RouteTest +{ + @Rule + @JvmField + public val timeout = Timeout(1000, TimeUnit.MILLISECONDS) + + @Test + fun routeRespondsAsynchronously() = runBlocking() + { + buildAsyncRoute().attemptTrigger(MockRequest(3)) + Assert.assertEquals(1.0f, channel.receive()) + } + + @Test + fun routeRespondsSynchonously() = runBlocking() + { + buildSyncRoute().attemptTrigger(MockRequest(2)) + Assert.assertEquals(0.0f, channel.receive()) + } + + companion object + { + @JvmStatic + private val channel: Channel = Channel() + + @JvmStatic + private val builder: MockRoute.Builder by lazy() + { + MockRoute.Builder().apply() + { + controller(MockController::class.java) + responseChannel(channel) + singleton(false) + } + } + + @JvmStatic + fun buildAsyncRoute(): MockRoute + { + return builder.apply() + { + testCallback() + { route, i -> + val controller = route.getControllerInstance() as MockController + controller.isOdd(i) + } + callback() + { route, i -> + val controller = route.getControllerInstance() as MockController + controller.providesModuloOfTwoWritesNow(i) + } + }.build() as MockRoute + } + + @JvmStatic + fun buildSyncRoute(): MockRoute + { + return builder.apply() + { + testCallback() + { route, i -> + val controller = route.getControllerInstance() as MockController + controller.isEven(i) + } + callback() + { route, i -> + val controller = route.getControllerInstance() as MockController + controller.providesModuloOfTwoWritesNow(i) + } + }.build() as MockRoute + } + + @JvmStatic + @AfterClass + fun destroy() + { + channel.close() + } + } +} \ No newline at end of file diff --git a/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/RouterTest.kt b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/RouterTest.kt new file mode 100644 index 0000000..ca5ec48 --- /dev/null +++ b/async/src/test/kotlin/lt/saltyjuice/dragas/chatty/v3/async/unit/RouterTest.kt @@ -0,0 +1,52 @@ +package lt.saltyjuice.dragas.chatty.v3.async.unit + +import kotlinx.coroutines.experimental.channels.Channel +import lt.saltyjuice.dragas.chatty.v3.async.mock.controller.NotAsyncController +import lt.saltyjuice.dragas.chatty.v3.async.mock.route.MockRouter +import lt.saltyjuice.dragas.chatty.v3.core.exception.RouteBuilderException +import org.junit.Assert +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class RouterTest +{ + @Test + fun routerCantBuildNotAsyncRoute() + { + shouldThrow(RouteBuilderException::class.java) + { + router.consume(controller) + } + } + + fun shouldThrow(vararg clazz: Class, test: (() -> Unit)) + { + var threw = false + try + { + test() + } + catch (err: Throwable) + { + clazz.forEach { if (it.isAssignableFrom(err.javaClass)) threw = true else throw err } + } + + Assert.assertTrue(threw) + } + + companion object + { + private lateinit var router: MockRouter + private val channel = Channel() + private val controller = NotAsyncController::class.java + @BeforeClass + @JvmStatic + fun init() + { + router = MockRouter(channel) + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e04535b..67108e7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,4 +5,5 @@ include ':birc' include ":websocket" include ':discord' include ':biscord' +include ':async'