Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ihostage committed Oct 1, 2018
1 parent f629c4e commit 60f60ae
Show file tree
Hide file tree
Showing 15 changed files with 395 additions and 41 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ cache:
- $HOME/.cache
- $HOME/.m2

before_install:
- cp .travis.settings.xml $HOME/.m2/settings.xml
- echo $GPG_SECRET_KEYS | base64 --decode | gpg2 --import

install: ./mvnw dependency:resolve dependency:resolve-plugins -B -V

script: ./mvnw verify -B -V

after_success:
- bash <(curl -s https://codecov.io/bash)

before_deploy:
- cp .travis.settings.xml $HOME/.m2/settings.xml
- echo $GPG_SECRET_KEYS | base64 --decode | gpg2 --import

deploy:
# Deploy releases
- provider: script
Expand Down
24 changes: 23 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<id>taymyr</id>
<name>Taymyr Contributors</name>
<url>https://github.com/taymyr</url>
<email>contributors@taymyr.org</email>
</developer>
</developers>
<scm>
Expand All @@ -39,9 +40,11 @@
<kotlintest.version>3.1.10</kotlintest.version>
<scala.binary.version>2.12</scala.binary.version>
<dokka.version>0.9.17</dokka.version>
<kotlin.version>1.2.70</kotlin.version>
<kotlin.version>1.2.71</kotlin.version>
<lagom.version>[1.4.0,)</lagom.version>
<akka.version>2.5.17</akka.version>
<ktlint.version>0.28.0</ktlint.version>
<mockito.kotlin.version>2.0.0-RC2</mockito.kotlin.version>
<antrun.plugin.version>1.8</antrun.plugin.version>
<source.plugin.version>3.0.1</source.plugin.version>
<jar.plugin.version>3.1.0</jar.plugin.version>
Expand Down Expand Up @@ -109,6 +112,24 @@
<version>${kotlintest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.nhaarman.mockitokotlin2</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>${mockito.kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-javadsl-testkit_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-stream-testkit_${scala.binary.version}</artifactId>
<version>${akka.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
Expand All @@ -121,6 +142,7 @@
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
<arg>-Xjvm-default=enable</arg>
</args>
</configuration>
<executions>
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/taymyr/lagom/metrics/Metrics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ class Metrics @Inject
constructor(conf: Config, val registry: MetricRegistry) {

private val config = conf.extract<MetricsConfig>("taymyr.lagom.metrics")
private val statusCircuitBreakers: ConcurrentHashMap<String, CircuitBreakerStatus> = ConcurrentHashMap()

@Inject
private fun registerCircuitBreaker(injector: Injector, mat: Materializer) {
if (config.enableCircuitBreaker) {
val metricsService = try { injector.getInstance(MetricsServiceImpl::class.java) } catch (e: Exception) { null }
val metricsService = try { injector.getInstance(MetricsServiceImpl::class.java) } catch (_: Throwable) { null }
metricsService ?: logger.error { "Only Lagom framework module support metrics for circuit breakers" }
metricsService?.let {
it.circuitBreakers().invoke().thenAccept { source ->
logger.info { "Metrics for circuit breakers enabled" }
val statusCircuitBreakers: ConcurrentHashMap<String, CircuitBreakerStatus> = ConcurrentHashMap()
source.runForeach({ statuses ->
statuses.forEach { status ->
statusCircuitBreakers.compute(status.id) { id, prev ->
Expand Down
10 changes: 7 additions & 3 deletions src/main/kotlin/org/taymyr/lagom/metrics/MetricsFilter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ import javax.inject.Inject
class MetricsFilter @Inject
constructor(mat: Materializer, private val metrics: Metrics) : Filter(mat) {

private fun normalize(path: String) = path.replace('?', '.').replace(Regex("[:&]"), "_")
private fun normalize(path: String) = path
.replaceFirst("/", "")
.replace(Regex("[?/]"), ".")
.replace(Regex("<.*>"), "")
.replace(Regex("[:&$\\s]"), "_")

private fun routeTimerContext(requestHeader: RequestHeader, handlerDef: Optional<HandlerDef>): Timer.Context? =
if (handlerDef.isPresent) metrics.routeTimer(normalize(handlerDef.get().path()), requestHeader.method()).time()
if (handlerDef.isPresent) metrics.routeTimer("root", normalize(handlerDef.get().path()), requestHeader.method()).time()
else null

private fun routeMeter(requestHeader: RequestHeader, handlerDef: Optional<HandlerDef>, result: Result): Meter? =
if (handlerDef.isPresent) metrics.routeMeter(normalize(handlerDef.get().path()), requestHeader.method(), "${result.status()}")
if (handlerDef.isPresent) metrics.routeMeter("root", normalize(handlerDef.get().path()), requestHeader.method(), "${result.status()}")
else null

override fun apply(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,6 @@ import java.util.concurrent.ConcurrentHashMap

class CircuitBreakersMetricSetTest : StringSpec({

val shareDataCircuitBreaker = CircuitBreakerStatus.builder()
.totalSuccessCount(2)
.totalFailureCount(3)
.throughputOneMinute(0.2)
.failedThroughputOneMinute(0.3)
.latencyMicros(Latency.builder()
.mean(111.0)
.median(222.0)
.percentile98th(333.0)
.percentile99th(444.0)
.percentile999th(555.0)
.min(666)
.max(777)
.build()
)

val openCircuitBreaker = shareDataCircuitBreaker.id("open").state("open").build()

val closedCircuitBreaker = shareDataCircuitBreaker.id("closed").state("closed").build()

val halfOpenCircuitBreaker = shareDataCircuitBreaker.id("half-open").state("half-open").build()

val unknownStateCircuitBreaker = shareDataCircuitBreaker.id("unknown").state("unknown").build()

val statusCircuitBreakers: ConcurrentHashMap<String, CircuitBreakerStatus> = ConcurrentHashMap()
statusCircuitBreakers[openCircuitBreaker.id] = openCircuitBreaker
statusCircuitBreakers[closedCircuitBreaker.id] = closedCircuitBreaker
statusCircuitBreakers[halfOpenCircuitBreaker.id] = halfOpenCircuitBreaker
statusCircuitBreakers[unknownStateCircuitBreaker.id] = unknownStateCircuitBreaker

"Metrics for open circuit breaker should be correct" {
val cbMetricSet = CircuitBreakersMetricSet(openCircuitBreaker.id, statusCircuitBreakers)
cbMetricSet.metrics.values.forAll { it shouldBe beInstanceOf(Gauge::class) }
Expand Down Expand Up @@ -114,4 +84,39 @@ class CircuitBreakersMetricSetTest : StringSpec({
cbMetricSet.metrics.values.forAll { it shouldBe beInstanceOf(Gauge::class) }
cbMetricSet.metrics.values.forAll { (it as Gauge<*>).value shouldBe null }
}
})
}) {
companion object {
private val shareDataCircuitBreaker: CircuitBreakerStatus.Builder = CircuitBreakerStatus.builder()
.totalSuccessCount(2)
.totalFailureCount(3)
.throughputOneMinute(0.2)
.failedThroughputOneMinute(0.3)
.latencyMicros(Latency.builder()
.mean(111.0)
.median(222.0)
.percentile98th(333.0)
.percentile99th(444.0)
.percentile999th(555.0)
.min(666)
.max(777)
.build()
)

val openCircuitBreaker: CircuitBreakerStatus = shareDataCircuitBreaker.id("open").state("open").build()

val closedCircuitBreaker: CircuitBreakerStatus = shareDataCircuitBreaker.id("closed").state("closed").build()

val halfOpenCircuitBreaker: CircuitBreakerStatus = shareDataCircuitBreaker.id("half-open").state("half-open").build()

val unknownStateCircuitBreaker: CircuitBreakerStatus = shareDataCircuitBreaker.id("unknown").state("unknown").build()

val statusCircuitBreakers: ConcurrentHashMap<String, CircuitBreakerStatus> = ConcurrentHashMap()

init {
statusCircuitBreakers[openCircuitBreaker.id] = openCircuitBreaker
statusCircuitBreakers[closedCircuitBreaker.id] = closedCircuitBreaker
statusCircuitBreakers[halfOpenCircuitBreaker.id] = halfOpenCircuitBreaker
statusCircuitBreakers[unknownStateCircuitBreaker.id] = unknownStateCircuitBreaker
}
}
}
76 changes: 76 additions & 0 deletions src/test/kotlin/org/taymyr/lagom/metrics/ConfigTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.taymyr.lagom.metrics

import com.typesafe.config.ConfigFactory
import io.github.config4k.extract
import io.kotlintest.matchers.string.shouldBeEmpty
import io.kotlintest.shouldBe
import io.kotlintest.shouldNotBe
import io.kotlintest.shouldThrow
import io.kotlintest.specs.WordSpec
import org.taymyr.lagom.metrics.GraphiteReporterType.PICKLE
import org.taymyr.lagom.metrics.GraphiteReporterType.TCP
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS

/**
* @author Sergey Morgunov
*/
class ConfigTest : WordSpec({

"MetricsConfig" should {

"be correct for reference.conf" {
val config = ConfigFactory.defaultReference().extract<MetricsConfig>("taymyr.lagom.metrics")
config.enableJVM shouldBe false
config.enableCircuitBreaker shouldBe false
config.prefix.shouldBeEmpty()
config.graphiteReporter shouldBe null
}

"be able to enable JVM metrics" {
val config = ConfigFactory.load("default.conf").extract<MetricsConfig>("taymyr.lagom.metrics")
config.enableJVM shouldBe true
}

"be able to enable circuit breakers metrics" {
val config = ConfigFactory.load("default.conf").extract<MetricsConfig>("taymyr.lagom.metrics")
config.enableCircuitBreaker shouldBe true
}

"be throw exception for incorrect settings graphite reporter" {
shouldThrow<Exception> {
ConfigFactory.load("bad_graphite.conf").extract<MetricsConfig>("taymyr.lagom.metrics")
}
}

"be correct for graphite reporter with minimal settings" {
val config = ConfigFactory.load("min_graphite.conf").extract<MetricsConfig>("taymyr.lagom.metrics")
config.graphiteReporter shouldNotBe null
config.graphiteReporter?.let {
it.type shouldBe PICKLE
it.host shouldBe "localhost"
it.port shouldBe 1000
it.batchSize shouldBe null
it.durationUnit shouldBe MILLISECONDS
it.rateUnit shouldBe SECONDS
it.period shouldBe 10
it.periodUnit shouldBe SECONDS
}
}

"be correct for graphite reporter with full settings" {
val config = ConfigFactory.load("full_graphite.conf").extract<MetricsConfig>("taymyr.lagom.metrics")
config.graphiteReporter shouldNotBe null
config.graphiteReporter?.let {
it.type shouldBe TCP
it.host shouldBe "localhost"
it.port shouldBe 1000
it.batchSize shouldBe 1000
it.durationUnit shouldBe SECONDS
it.rateUnit shouldBe MILLISECONDS
it.period shouldBe 60
it.periodUnit shouldBe MILLISECONDS
}
}
}
})
125 changes: 125 additions & 0 deletions src/test/kotlin/org/taymyr/lagom/metrics/MetricsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.taymyr.lagom.metrics

import akka.NotUsed
import akka.stream.javadsl.Source
import akka.stream.javadsl.Source.from
import akka.stream.javadsl.Source.maybe
import com.codahale.metrics.MetricFilter.ALL
import com.codahale.metrics.MetricRegistry
import com.codahale.metrics.jvm.GarbageCollectorMetricSet
import com.codahale.metrics.jvm.JvmAttributeGaugeSet
import com.codahale.metrics.jvm.MemoryUsageGaugeSet
import com.codahale.metrics.jvm.ThreadStatesGaugeSet
import com.lightbend.lagom.internal.server.status.MetricsServiceImpl
import com.lightbend.lagom.javadsl.api.ServiceCall
import com.lightbend.lagom.javadsl.api.transport.BadRequest
import com.lightbend.lagom.javadsl.api.transport.TransportErrorCode.InternalServerError
import com.lightbend.lagom.javadsl.api.transport.TransportException
import com.lightbend.lagom.javadsl.server.status.CircuitBreakerStatus
import com.lightbend.lagom.javadsl.testkit.ServiceTest.TestServer
import com.lightbend.lagom.javadsl.testkit.ServiceTest.defaultSetup
import com.lightbend.lagom.javadsl.testkit.ServiceTest.startServer
import com.nhaarman.mockitokotlin2.atLeast
import com.nhaarman.mockitokotlin2.clearInvocations
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.isA
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.verify
import io.kotlintest.matchers.beInstanceOf
import io.kotlintest.shouldBe
import io.kotlintest.shouldThrow
import io.kotlintest.specs.WordSpec
import org.taymyr.lagom.metrics.CircuitBreakersMetricSetTest.Companion.closedCircuitBreaker
import org.taymyr.lagom.metrics.CircuitBreakersMetricSetTest.Companion.halfOpenCircuitBreaker
import org.taymyr.lagom.metrics.CircuitBreakersMetricSetTest.Companion.openCircuitBreaker
import play.inject.Bindings.bind
import java.lang.Thread.sleep
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException

/**
* @author Sergey Morgunov
*/
class MetricsTest : WordSpec({

val registrySpy = spy(MetricRegistry())
val metricsServiceImpl = mock<MetricsServiceImpl> { _ ->
val cbStatusSource = from(listOf(listOf(openCircuitBreaker, closedCircuitBreaker, halfOpenCircuitBreaker))).concat(maybe())
val cbStatusCall = mock<ServiceCall<NotUsed, Source<List<CircuitBreakerStatus>, *>>> {
on { invoke() }.thenReturn(CompletableFuture.completedFuture(cbStatusSource))
}
on { circuitBreakers() }.thenReturn(cbStatusCall)
}
val server: TestServer = startServer(defaultSetup().withCluster(false).configureBuilder { b -> b
.overrides(
bind(MetricRegistry::class.java).toInstance(registrySpy),
bind(MetricsServiceImpl::class.java).toInstance(metricsServiceImpl)
)
})

"Initialization Metrics" should {

"initialize JVM metrics" {
verify(registrySpy).register(eq("prefix.jvm.attr"), isA<JvmAttributeGaugeSet>())
verify(registrySpy).register(eq("prefix.jvm.gc"), isA<GarbageCollectorMetricSet>())
verify(registrySpy).register(eq("prefix.jvm.memory"), isA<MemoryUsageGaugeSet>())
verify(registrySpy).register(eq("prefix.jvm.threads"), isA<ThreadStatesGaugeSet>())
}

"initialize CircuitBreakers metrics" {
verify(registrySpy).register(eq("prefix.cb.open"), isA<CircuitBreakersMetricSet>())
verify(registrySpy).register(eq("prefix.cb.closed"), isA<CircuitBreakersMetricSet>())
verify(registrySpy).register(eq("prefix.cb.half-open"), isA<CircuitBreakersMetricSet>())
}

"initialize Graphite reporter" {
clearInvocations(registrySpy)
sleep(1000) // Wait more then reporter period
verify(registrySpy, atLeast(1)).getGauges(ALL)
verify(registrySpy, atLeast(1)).getCounters(ALL)
verify(registrySpy, atLeast(1)).getHistograms(ALL)
verify(registrySpy, atLeast(1)).getMeters(ALL)
verify(registrySpy, atLeast(1)).getTimers(ALL)
}
}

"Metrics filter" should {
"correct register timer and meter for successful request" {
clearInvocations(registrySpy)
val testService = server.client(TestService::class.java)
testService.simpleMethod().invoke().toCompletableFuture().get()
verify(registrySpy).timer("prefix.routes.all.timer")
verify(registrySpy).meter("prefix.routes.all.meter")
verify(registrySpy).timer("prefix.routes.root.foo.bar.GET.timer")
verify(registrySpy).meter("prefix.routes.root.foo.bar.GET.200.meter")
}

"correct register timer and meter for bad request" {
clearInvocations(registrySpy)
val testService = server.client(TestService::class.java)
val badRequest = shouldThrow<ExecutionException> {
testService.methodWithPathParams(0, "0").invoke("").toCompletableFuture().get()
}
badRequest.cause shouldBe beInstanceOf(BadRequest::class)
verify(registrySpy).timer("prefix.routes.all.timer")
verify(registrySpy).meter("prefix.routes.all.meter")
verify(registrySpy).timer("prefix.routes.root.foo._firstId.bar._secondId.POST.timer")
verify(registrySpy).meter("prefix.routes.root.foo._firstId.bar._secondId.POST.400.meter")
}

"correct register timer and meter for unsuccessful request" {
clearInvocations(registrySpy)
val testService = server.client(TestService::class.java)
val internalError = shouldThrow<ExecutionException> {
testService.methodWithQueryParams(0, 0, 0).invoke().toCompletableFuture().get()
}
internalError.cause shouldBe beInstanceOf(TransportException::class)
(internalError.cause as TransportException).errorCode() shouldBe InternalServerError
verify(registrySpy).timer("prefix.routes.all.timer")
verify(registrySpy).meter("prefix.routes.all.meter")
verify(registrySpy).timer("prefix.routes.root.foo._firstId.bar.pageNo_pageSize.DELETE.timer")
verify(registrySpy).meter("prefix.routes.root.foo._firstId.bar.pageNo_pageSize.DELETE.500.meter")
}
}
})
Loading

0 comments on commit 60f60ae

Please sign in to comment.