Juanjo Aguililla 9 жил өмнө
parent
commit
7462722d58

+ 1 - 0
.travis.yml

@@ -116,6 +116,7 @@ env:
     - "TESTDIR=JavaScript/ringojs"
     - "TESTDIR=JavaScript/ringojs-convenient"
     - "TESTDIR=JavaScript/sailsjs"
+    - "TESTDIR=Kotlin/hexagon"
     - "TESTDIR=Lua/lapis"
     - "TESTDIR=Lua/openresty"
     - "TESTDIR=Nim/jester"

+ 5 - 0
frameworks/Kotlin/hexagon/.gitignore

@@ -0,0 +1,5 @@
+
+build/
+.gradle/
+
+log/

+ 31 - 0
frameworks/Kotlin/hexagon/benchmark_config.json

@@ -0,0 +1,31 @@
+{
+    "framework" : "hexagon",
+    "tests" : [
+        {
+            "default" : {
+                "json_url" : "/json",
+                "db_url" : "/db",
+                "query_url" : "/query?queries=",
+                "fortune_url" : "/fortune",
+                "update_url" : "/update?queries=",
+                "plaintext_url" : "/plaintext",
+
+                "port" : 9090,
+                "setup_file" : "setup",
+                "approach" : "Realistic",
+                "classification" : "Micro",
+                "database" : "MongoDB",
+                "framework" : "Hexagon",
+                "language" : "Kotlin",
+                "orm" : "Raw",
+                "platform" : "Netty",
+                "webserver" : "None",
+                "os" : "Linux",
+                "database_os" : "Linux",
+                "display_name" : "Hexagon MongoDB",
+                "notes" : "http://there4.co/hexagon",
+                "versus" : "servlet"
+            }
+        }
+    ]
+}

+ 23 - 0
frameworks/Kotlin/hexagon/build.gradle

@@ -0,0 +1,23 @@
+
+buildscript {
+    repositories {
+        jcenter ()
+    }
+
+    dependencies {
+        classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
+    }
+}
+
+apply from: "$gradleScripts/hexagon_service.gradle"
+
+mainClassName = "co.there4.hexagon.BenchmarkKt"
+applicationDefaultJvmArgs = [
+    '-Xms64M',
+    '-Xmx64M',
+    '-server',
+    '-XX:+UseNUMA',
+    '-XX:+UseParallelGC',
+    '-XX:+AggressiveOpts'
+]

+ 12 - 0
frameworks/Kotlin/hexagon/gradle.properties

@@ -0,0 +1,12 @@
+
+version=1.0.0
+group=co.there4.hexagon
+description=Hexagon web framework's benchmark
+
+wrapperGradleVersion=2.14.1
+gradleScripts=https://raw.githubusercontent.com/jaguililla/hexagon/master/gradle
+org.gradle.daemon=true
+
+dokkaVersion=0.9.+
+kotlinVersion=1.0.3
+hexagonVersion=0.8.2

BIN
frameworks/Kotlin/hexagon/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
frameworks/Kotlin/hexagon/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Mon Aug 01 16:02:25 CEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip

+ 160 - 0
frameworks/Kotlin/hexagon/gradlew

@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90 - 0
frameworks/Kotlin/hexagon/gradlew.bat

@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 55 - 0
frameworks/Kotlin/hexagon/readme.md

@@ -0,0 +1,55 @@
+
+# Hexagon Benchmarking Test
+
+This is the Hexagon portion of a [benchmarking test suite](../) comparing a variety of web
+development platforms. The test utilizes Hexagon routes, serialization and database access.
+
+
+## Local setup
+
+    tar -Jxvf db.txz && \
+    mongorestore dump/ && \
+    rm -rf dump
+
+
+## Tests
+
+* [Hexagon application](/src/main/java/co/there4/hexagon/Benchmark.kt)
+
+
+## Infrastructure Software Versions
+
+* [Hexagon 0.3.2](http://there4.co/hexagon)
+
+
+## Test URLs
+
+* JSON Encoding Test: http://localhost:5050/json
+* Data-Store/Database Mapping Test: http://localhost:5050/db?queries=5 
+* Plain Text Test: http://localhost:5050/plaintext 
+* Fortunes: http://localhost:5050/fortune 
+* Database updates: http://localhost:5050/update
+
+## Run on OpenShift
+
+https://blog.openshift.com/run-gradle-builds-on-openshift/
+
+
+## Copy to TFB
+
+    rm -f db.txz
+    
+## Run inside vagrant
+
+    toolset/run-tests.py --install server --mode verify --test hexagon
+    
+## Clear
+    
+
+## TODO
+
+* Remove `benchmark_config` optional parameters. Check:
+  //frameworkbenchmarks.readthedocs.io/en/latest/Codebase/Framework-Files/#benchmark-config-file
+
+* Document common commands to test the framework inside Vagrant's development machine
+

+ 6 - 0
frameworks/Kotlin/hexagon/setup.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+fw_depends java
+
+./gradlew
+nohup build/hexagon/bin/hexagon &

+ 8 - 0
frameworks/Kotlin/hexagon/source_code

@@ -0,0 +1,8 @@
+./hexagon/src/main/kotlin/co/there4/hexagon/Benchmark.kt
+./hexagon/src/main/resources/templates/fortunes.html
+./hexagon/src/main/resources/service.properties
+./hexagon/src/main/resources/logback.xml
+./hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt
+./hexagon/src/test/resources/logback-test.xml
+./hexagon/build.gradle
+./hexagon/gradle.properties

+ 115 - 0
frameworks/Kotlin/hexagon/src/main/kotlin/co/there4/hexagon/Benchmark.kt

@@ -0,0 +1,115 @@
+package co.there4.hexagon
+
+import co.there4.hexagon.settings.SettingsManager.setting
+import java.util.concurrent.ThreadLocalRandom
+
+import co.there4.hexagon.serialization.serialize
+import co.there4.hexagon.repository.MongoIdRepository
+import co.there4.hexagon.repository.mongoCollection
+import co.there4.hexagon.repository.mongoDatabase
+import co.there4.hexagon.web.*
+import co.there4.hexagon.web.jetty.JettyServer
+
+import java.lang.System.getenv
+import java.net.InetAddress.getByName as address
+import java.time.LocalDateTime.now
+import kotlin.reflect.KClass
+
+internal data class Message (val message: String = "Hello, World!")
+internal data class Fortune (val _id: Int, val message: String)
+internal data class World (val id: Int, val randomNumber: Int)
+
+private val BIND = setting<String>("bindAddress") ?: "localhost"
+private val BIND_ADDRESS = address(BIND)
+private val BIND_PORT = setting<Int>("bindPort") ?: 9090
+
+private val DB_ROWS = 10000
+private val CONTENT_TYPE_JSON = "application/json"
+private val QUERIES_PARAM = "queries"
+
+private val DB = setting<String>("database") ?: "hello_world"
+private val WORLD: String = setting<String>("worldCollection") ?: "world"
+private val FORTUNE: String = setting<String>("fortuneCollection") ?: "fortune"
+
+private val DB_HOST = getenv("DBHOST") ?: "localhost"
+private val DB_PORT = 27017
+
+private val database = mongoDatabase("mongodb://$DB_HOST:$DB_PORT/$DB")
+
+private val worldRepository = repository(World::class, WORLD, { it.id })
+private val fortuneRepository = repository(Fortune::class, FORTUNE, { it._id })
+
+private fun <T : Any> repository(type: KClass<T>, name: String, keySupplier: (T) -> Int) =
+    MongoIdRepository(type, mongoCollection(name, database), keySupplier, Int::class, "_id")
+
+private fun rnd () = ThreadLocalRandom.current ().nextInt (DB_ROWS) + 1
+
+private fun Exchange.hasQueryCount() = request[QUERIES_PARAM] == null
+
+private fun Exchange.getDb () {
+    val worlds = (1..getQueries()).map { worldRepository.find(rnd ()) }
+
+    response.contentType = CONTENT_TYPE_JSON
+    ok (if (hasQueryCount()) worlds[0].serialize() else worlds.serialize())
+}
+
+private fun Exchange.getFortunes () {
+    val fortune = Fortune (0, "Additional fortune added at request time.")
+    val fortunes = fortuneRepository.findObjects ().toList() + fortune
+
+    response.contentType = "text/html; charset=utf-8"
+    template ("fortunes.html", mapOf ("fortunes" to fortunes.sortedBy { it.message }))
+}
+
+private fun Exchange.getUpdates () {
+    val worlds =  (1..getQueries()).map {
+        val newWorld = World (rnd (), rnd())
+        worldRepository.replaceObject (newWorld)
+        newWorld
+    }
+
+    response.contentType = CONTENT_TYPE_JSON
+    ok (if (hasQueryCount()) worlds[0].serialize() else worlds.serialize())
+}
+
+private fun Exchange.getQueries () =
+    try {
+        val queries = request[QUERIES_PARAM]?.toInt() ?: 1
+        when {
+            queries < 1 -> 1
+            queries > 500 -> 500
+            else -> queries
+        }
+    }
+    catch (ex: NumberFormatException) {
+        1
+    }
+
+private fun Exchange.getPlaintext () {
+    response.contentType = "text/plain"
+    ok("Hello, World!")
+}
+
+private fun Exchange.getJson () {
+    response.contentType = CONTENT_TYPE_JSON
+    ok(Message ().serialize())
+}
+
+fun main(args: Array<String>) {
+    server = JettyServer (bindAddress = BIND_ADDRESS, bindPort = BIND_PORT)
+
+    before {
+        response.addHeader("Server", "Servlet/3.1")
+        response.addHeader("Transfer-Encoding", "chunked")
+        response.addHeader("Date", httpDate (now()))
+    }
+
+    get ("/json") { getJson() }
+    get ("/db") { getDb() }
+    get ("/query") { getDb() }
+    get ("/fortune") { getFortunes() }
+    get ("/update") { getUpdates() }
+    get ("/plaintext") { getPlaintext() }
+
+    run ()
+}

+ 32 - 0
frameworks/Kotlin/hexagon/src/main/resources/logback.xml

@@ -0,0 +1,32 @@
+<!--
+ | Logback configuration for tests
+ !-->
+<configuration>
+  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <Pattern>
+        %d{HH:mm:ss.SSS} %highlight(%-5p) %magenta([%-15.15thread]) %-30logger{30} %cyan(%X{jvmId}) | %m%n
+      </Pattern>
+    </encoder>
+  </appender>
+
+  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+      <FileNamePattern>log/%d{yyyy-MM-dd}.log</FileNamePattern>
+      <MaxHistory>5</MaxHistory>
+    </rollingPolicy>
+    <encoder>
+      <Pattern>%d{HH:mm:ss.SSS} %-5p [%-15.15thread] %-30logger{30} %X{jvmId} | %m%n</Pattern>
+    </encoder>
+  </appender>
+
+  <root>
+    <level value="off" />
+    <appender-ref ref="console" />
+    <appender-ref ref="file" />
+  </root>
+
+  <logger name="co.there4.hexagon">
+    <level value="off" />
+  </logger>
+</configuration>

+ 7 - 0
frameworks/Kotlin/hexagon/src/main/resources/service.yaml

@@ -0,0 +1,7 @@
+
+bindPort : 9090
+bindAddress : 0.0.0.0
+
+database : hello_world
+worldCollection : world
+fortuneCollection : fortune

+ 21 - 0
frameworks/Kotlin/hexagon/src/main/resources/templates/fortunes.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <title>Fortunes</title>
+</head>
+<body>
+<table>
+  <tr>
+    <th>id</th>
+    <th>message</th>
+  </tr>
+  {% for fortune in fortunes %}
+  <tr>
+    <td>{{ fortune._id }}</td>
+    <td>{{ fortune.message }}</td>
+  </tr>
+  {% endfor %}
+</table>
+</body>
+</html>

+ 133 - 0
frameworks/Kotlin/hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt

@@ -0,0 +1,133 @@
+package co.there4.hexagon
+
+import co.there4.hexagon.serialization.parse
+import co.there4.hexagon.web.Client
+import org.asynchttpclient.Response
+import org.testng.annotations.BeforeClass
+import org.testng.annotations.Test
+import java.net.URL
+
+internal const val THREADS = 4
+internal const val TIMES = 16
+
+@Test (threadPoolSize = THREADS, invocationCount = TIMES)
+class BenchmarkTest {
+    private val client = Client(URL("http://localhost:9090"))
+
+    @BeforeClass fun warmup() {
+        main(arrayOf())
+
+        val warmupRounds = if (THREADS > 1) 5 else 0
+        (1 ..warmupRounds).forEach {
+            json ()
+            plaintext ()
+            no_query_parameter ()
+            empty_query_parameter ()
+            text_query_parameter ()
+            zero_queries ()
+            one_thousand_queries ()
+            one_query ()
+            ten_queries ()
+            one_hundred_queries ()
+            five_hundred_queries ()
+            fortunes ()
+            no_updates_parameter ()
+            empty_updates_parameter ()
+            text_updates_parameter ()
+            zero_updates ()
+            one_thousand_updates ()
+            one_update ()
+            ten_updates ()
+            one_hundred_updates ()
+            five_hundred_updates ()
+        }
+    }
+
+    fun json () {
+        val response = client.get ("/json")
+        val content = response.responseBody
+
+        checkResponse (response, "application/json")
+        assert ("Hello, World!" == content.parse(Map::class)["message"])
+    }
+
+    fun plaintext () {
+        val response = client.get ("/plaintext")
+        val content = response.responseBody
+
+        checkResponse (response, "text/plain")
+        assert ("Hello, World!" == content)
+    }
+
+    fun no_query_parameter () {
+        val response = client.get ("/db")
+        val content = response.responseBody
+
+        checkResponse (response, "application/json")
+        val resultsMap = content.parse(Map::class)
+        assert (resultsMap.containsKey ("id") && resultsMap.containsKey ("randomNumber"))
+    }
+
+    fun fortunes () {
+        val response = client.get ("/fortune")
+        val content = response.responseBody
+        val contentType = response.headers ["Content-Type"]
+
+        assert (response.headers ["Server"] != null)
+        assert (response.headers ["Date"] != null)
+        assert (content.contains ("&lt;script&gt;alert(&quot;This should not be displayed"))
+        assert (content.contains ("フレームワークのベンチマーク"))
+        assert (contentType.toLowerCase ().contains ("text/html"))
+    }
+
+    fun no_updates_parameter () {
+        val response = client.get ("/update")
+        val content = response.responseBody
+
+        checkResponse (response, "application/json")
+        val resultsMap = content.parse(Map::class)
+        assert (resultsMap.containsKey ("id") && resultsMap.containsKey ("randomNumber"))
+    }
+
+    fun empty_query_parameter () = checkDbRequest ("/query?queries", 1)
+    fun text_query_parameter () = checkDbRequest ("/query?queries=text", 1)
+    fun zero_queries () = checkDbRequest ("/query?queries=0", 1)
+    fun one_thousand_queries () = checkDbRequest ("/query?queries=1000", 500)
+    fun one_query () = checkDbRequest ("/query?queries=1", 1)
+    fun ten_queries () = checkDbRequest ("/query?queries=10", 10)
+    fun one_hundred_queries () = checkDbRequest ("/query?queries=100", 100)
+    fun five_hundred_queries () = checkDbRequest ("/query?queries=500", 500)
+
+    fun empty_updates_parameter () = checkDbRequest ("/update?queries", 1)
+    fun text_updates_parameter () = checkDbRequest ("/update?queries=text", 1)
+    fun zero_updates () = checkDbRequest ("/update?queries=0", 1)
+    fun one_thousand_updates () = checkDbRequest ("/update?queries=1000", 500)
+    fun one_update () = checkDbRequest ("/update?queries=1", 1)
+    fun ten_updates () = checkDbRequest ("/update?queries=10", 10)
+    fun one_hundred_updates () = checkDbRequest ("/update?queries=100", 100)
+    fun five_hundred_updates () = checkDbRequest ("/update?queries=500", 500)
+
+    private fun checkDbRequest (path: String, itemsCount: Int) {
+        val response = client.get (path)
+        val content = response.responseBody
+
+        checkResponse (response, "application/json")
+        checkResultItems (content, itemsCount)
+    }
+
+    private fun checkResponse (res: Response, contentType: String) {
+        assert(res.headers ["Server"] != null)
+        assert(res.headers ["Transfer-Encoding"] != null)
+        assert(res.headers ["Content-Type"].contains (contentType))
+    }
+
+    private fun checkResultItems (result: String, size: Int) {
+        val resultsList = result.parse(List::class)
+        assert (size == resultsList.size)
+
+        (1..size).forEach {
+            val r = resultsList[it - 1] as Map<*, *>
+            assert (r.containsKey ("id") && r.containsKey ("randomNumber"))
+        }
+    }
+}

+ 32 - 0
frameworks/Kotlin/hexagon/src/test/resources/logback-test.xml

@@ -0,0 +1,32 @@
+<!--
+ | Logback configuration for tests
+ !-->
+<configuration>
+  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <Pattern>
+        %d{HH:mm:ss.SSS} %highlight(%-5p) %magenta([%-15.15thread]) %-30logger{30} %cyan(%X{jvmId}) | %m%n
+      </Pattern>
+    </encoder>
+  </appender>
+
+  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+      <FileNamePattern>log/%d{yyyy-MM-dd}.log</FileNamePattern>
+      <MaxHistory>5</MaxHistory>
+    </rollingPolicy>
+    <encoder>
+      <Pattern>%d{HH:mm:ss.SSS} %-5p [%-15.15thread] %-30logger{30} %X{jvmId} | %m%n</Pattern>
+    </encoder>
+  </appender>
+
+  <root>
+    <level value="off" />
+    <appender-ref ref="console" />
+    <appender-ref ref="file" />
+  </root>
+
+  <logger name="co.there4.hexagon">
+    <level value="off" />
+  </logger>
+</configuration>