Browse Source

Merge pull request #1420 from jamming/master

Java: Update Sabina
Hamilton Turner 10 years ago
parent
commit
ef63fb6ecd

+ 6 - 4
frameworks/Java/sabina/benchmark_config

@@ -2,16 +2,18 @@
   "framework": "sabina",
   "tests": [{
     "default": {
-      "setup_file": "setup",
       "json_url": "/json",
       "db_url": "/db",
-      "query_url": "/db?queries=",
+      "query_url": "/query?queries=",
+      "fortune_url": "/fortune",
+      "update_url": "/update?queries=",
       "plaintext_url": "/plaintext",
-      "port": 8080,
+      "port": 5050,
+      "setup_file": "setup",
       "approach": "Realistic",
       "classification": "Micro",
       "database": "MySQL",
-      "framework": "sabina",
+      "framework": "Sabina",
       "language": "Java",
       "orm": "Raw",
       "platform": "Servlet",

+ 21 - 9
frameworks/Java/sabina/pom.xml

@@ -1,3 +1,17 @@
+<!--
+ ! Copyright © 2015 Juan José Aguililla. All rights reserved.
+ !
+ ! Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ ! except in compliance with the License. You may obtain a copy of the License at
+ !
+ !     http://www.apache.org/licenses/LICENSE-2.0
+ !
+ ! Unless required by applicable law or agreed to in writing, software distributed under the
+ ! License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ ! either express or implied. See the License for the specific language governing permissions
+ ! and limitations under the License.
+ !-->
+
 <project
     xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -7,7 +21,7 @@
 
     <groupId>sabina</groupId>
     <artifactId>sabina</artifactId>
-    <version>1.0.0-SNAPSHOT</version>
+    <version>1.0.0</version>
 
     <name>Sabina benchmark project</name>
 
@@ -21,10 +35,8 @@
         <maven.compiler.debug>false</maven.compiler.debug>
 
         <db.host>localhost</db.host>
-        <web.port>8080</web.port>
 
-        <sabina-version>1.0.0</sabina-version>
-        <mysql-connector-version>5.1.28</mysql-connector-version>
+        <sabina-version>1.1.1</sabina-version>
     </properties>
 
     <repositories>
@@ -78,19 +90,19 @@
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
-            <version>${mysql-connector-version}</version>
+            <version>5.1.28</version>
         </dependency>
 
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>4.11</version>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <version>6.8.21</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>fluent-hc</artifactId>
-            <version>4.4</version>
+            <version>4.3.3</version>
             <scope>test</scope>
         </dependency>
     </dependencies>

+ 0 - 4
frameworks/Java/sabina/readme.md

@@ -38,7 +38,3 @@ http://localhost:8080/db?queries=5
 
 http://localhost:8080/plaintext
 
-## TODO
-
-* Implement 'update' test
-* Implement 'fortunes' test

+ 3 - 2
frameworks/Java/sabina/setup.sh

@@ -1,4 +1,5 @@
 #!/bin/bash
 
-mvn clean package -Ddb.host=${DBHOST}
-${JAVA_HOME}/bin/java -jar target/sabina-1.0.0-SNAPSHOT.jar &
+export JAVA_HOME=/opt/java8
+mvn clean package -DskipTests -Ddb.host=${DBHOST}
+${JAVA_HOME}/bin/java -jar target/sabina-1.0.0.jar &

+ 7 - 4
frameworks/Java/sabina/source_code

@@ -1,4 +1,7 @@
-./sabina/src/main/java/
-./sabina/src/main/java/Application.java
-./sabina/src/main/java/Message.java
-./sabina/src/main/java/World.java
+./sabina/src/main/java/sabina/benchmark/
+./sabina/src/main/java/sabina/benchmark/Application.java
+./sabina/src/main/java/sabina/benchmark/Message.java
+./sabina/src/main/java/sabina/benchmark/World.java
+./sabina/src/main/java/sabina/benchmark/Fortune.java
+./sabina/src/main/resources/fortunes.mustache
+./sabina/src/main/resources/server.properties

+ 135 - 52
frameworks/Java/sabina/src/main/java/sabina/benchmark/Application.java

@@ -1,43 +1,62 @@
+/*
+ * Copyright © 2015 Juan José Aguililla. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
 package sabina.benchmark;
 
 import static java.lang.Integer.parseInt;
+import static java.lang.System.getProperty;
 import static sabina.Sabina.*;
 import static sabina.content.JsonContent.toJson;
+import static sabina.view.MustacheView.renderMustache;
 
 import com.mchange.v2.c3p0.ComboPooledDataSource;
-import sabina.Exchange;
 import sabina.Request;
 
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
+import java.sql.*;
+import java.util.*;
 import java.util.Date;
-import java.util.Properties;
-import java.util.Random;
 import java.util.concurrent.ThreadLocalRandom;
 
 import javax.sql.DataSource;
 
 /**
- * When it is implemented, add this to benchmark_config
- * "fortune_url": "/fortune",
- * "update_url": "/update",
+ * .
  */
 final class Application {
-    private static final Properties CONFIG = loadConfig ();
-    private static final DataSource DS = createSessionFactory ();
-    private static final String QUERY = "select * from world where id = ?";
+    private static final String SETTINGS_RESOURCE = "/server.properties";
+    private static final Properties SETTINGS = loadConfiguration ();
 
+    private static final String JDBC_URL = SETTINGS.getProperty ("mysql.uri")
+        .replace ("${db.host}", "localhost"); // TODO Move this to Gradle build
+    private static final DataSource DATA_SOURCE = createSessionFactory ();
     private static final int DB_ROWS = 10000;
+
+    private static final boolean AUTOCOMMIT = getProperty ("sabina.benchmark.autocommit") != null;
+    private static final String SELECT_WORLD = "select * from world where id = ?";
+    private static final String UPDATE_WORLD = "update world set randomNumber = ? where id = ?";
+    private static final String SELECT_FORTUNES = "select * from fortune";
+
     private static final String MESSAGE = "Hello, World!";
     private static final String CONTENT_TYPE_TEXT = "text/plain";
+    private static final String CONTENT_TYPE_JSON = "application/json";
+    private static final String QUERIES_PARAM = "queries";
 
-    private static Properties loadConfig () {
+    private static Properties loadConfiguration () {
         try {
-            Properties config = new Properties ();
-            config.load (Class.class.getResourceAsStream ("/server.properties"));
-            return config;
+            Properties settings = new Properties ();
+            settings.load (Class.class.getResourceAsStream (SETTINGS_RESOURCE));
+            return settings;
         }
         catch (Exception ex) {
             throw new RuntimeException (ex);
@@ -46,13 +65,13 @@ final class Application {
 
     private static DataSource createSessionFactory () {
         try {
-            ComboPooledDataSource cpds = new ComboPooledDataSource ();
-            cpds.setJdbcUrl (CONFIG.getProperty ("mysql.uri"));
-            cpds.setMinPoolSize (32);
-            cpds.setMaxPoolSize (256);
-            cpds.setCheckoutTimeout (1800);
-            cpds.setMaxStatements (50);
-            return cpds;
+            ComboPooledDataSource dataSource = new ComboPooledDataSource ();
+            dataSource.setMinPoolSize (32);
+            dataSource.setMaxPoolSize (256);
+            dataSource.setCheckoutTimeout (1800);
+            dataSource.setMaxStatements (50);
+            dataSource.setJdbcUrl (JDBC_URL);
+            return dataSource;
         }
         catch (Exception ex) {
             throw new RuntimeException (ex);
@@ -61,11 +80,11 @@ final class Application {
 
     private static int getQueries (final Request request) {
         try {
-            String param = request.queryParams ("queries");
-            if (param == null)
+            String parameter = request.queryParams (QUERIES_PARAM);
+            if (parameter == null)
                 return 1;
 
-            int queries = parseInt (param);
+            int queries = parseInt (parameter);
             if (queries < 1)
                 return 1;
             if (queries > 500)
@@ -78,51 +97,113 @@ final class Application {
         }
     }
 
-    private static Object getJson (Exchange it) {
-        it.response.type ("application/json");
+    private static Object getJson (Request it) {
+        it.response.type (CONTENT_TYPE_JSON);
         return toJson (new Message ());
     }
 
-    private static Object getDb (Exchange it) {
-        final int queries = getQueries (it.request);
+    private static Object getDb (Request it) {
+        final int queries = getQueries (it);
         final World[] worlds = new World[queries];
 
-        try (final Connection con = DS.getConnection ()) {
+        try (final Connection con = DATA_SOURCE.getConnection ()) {
             final Random random = ThreadLocalRandom.current ();
-            PreparedStatement stmt = con.prepareStatement (QUERY);
+            final PreparedStatement stmt = con.prepareStatement (SELECT_WORLD);
 
-            for (int i = 0; i < queries; i++) {
+            for (int ii = 0; ii < queries; ii++) {
                 stmt.setInt (1, random.nextInt (DB_ROWS) + 1);
-                ResultSet rs = stmt.executeQuery ();
-                while (rs.next ()) {
-                    worlds[i] = new World ();
-                    worlds[i].id = rs.getInt (1);
-                    worlds[i].randomNumber = rs.getInt (2);
-                }
+                final ResultSet rs = stmt.executeQuery ();
+                while (rs.next ())
+                    worlds[ii] = new World (rs.getInt (1), rs.getInt (2));
             }
         }
         catch (SQLException e) {
             e.printStackTrace ();
         }
 
-        it.response.type ("application/json");
-        return toJson (it.request.queryParams ("queries") == null? worlds[0] : worlds);
+        it.response.type (CONTENT_TYPE_JSON);
+        return toJson (it.queryParams (QUERIES_PARAM) == null? worlds[0] : worlds);
     }
 
-    private static Object getFortune (Exchange aExchange) {
-        throw new UnsupportedOperationException ();
+    private static Object getFortunes (Request it) {
+        final List<Fortune> fortunes = new ArrayList<> ();
+
+        try (final Connection con = DATA_SOURCE.getConnection ()) {
+            final ResultSet rs = con.prepareStatement (SELECT_FORTUNES).executeQuery ();
+            while (rs.next ())
+                fortunes.add (new Fortune (rs.getInt (1), rs.getString (2)));
+        }
+        catch (SQLException e) {
+            e.printStackTrace ();
+        }
+
+        fortunes.add (new Fortune (0, "Additional fortune added at request time."));
+        fortunes.sort ((a, b) -> a.message.compareTo (b.message));
+
+        it.response.type ("text/html; charset=utf-8");
+        return renderMustache ("/fortunes.mustache", fortunes);
     }
 
-    private static Object getUpdate (Exchange aExchange) {
-        throw new UnsupportedOperationException ();
+    private static Object getUpdates (Request it) {
+        final int queries = getQueries (it);
+        final World[] worlds = new World[queries];
+
+        try (final Connection con = DATA_SOURCE.getConnection ()) {
+            con.setAutoCommit (AUTOCOMMIT);
+
+            final Random random = ThreadLocalRandom.current ();
+            final PreparedStatement stmtSelect = con.prepareStatement (SELECT_WORLD);
+            final PreparedStatement stmtUpdate = con.prepareStatement (UPDATE_WORLD);
+
+            for (int ii = 0; ii < queries; ii++) {
+                stmtSelect.setInt (1, random.nextInt (DB_ROWS) + 1);
+                final ResultSet rs = stmtSelect.executeQuery ();
+                while (rs.next ()) {
+                    worlds[ii] = new World (rs.getInt (1), rs.getInt (2));
+                    stmtUpdate.setInt (1, random.nextInt (DB_ROWS) + 1);
+                    stmtUpdate.setInt (2, worlds[ii].id);
+
+                    if (AUTOCOMMIT) {
+                        stmtUpdate.executeUpdate ();
+                    }
+                    else {
+                        stmtUpdate.addBatch ();
+                    }
+                }
+            }
+
+            if (!AUTOCOMMIT) {
+                int count = 0;
+                boolean retrying;
+
+                do {
+                    try {
+                        stmtUpdate.executeBatch ();
+                        retrying = false;
+                    }
+                    catch (BatchUpdateException e) {
+                        retrying = true;
+                    }
+                }
+                while (count++ < 10 && retrying);
+
+                con.commit ();
+            }
+        }
+        catch (SQLException e) {
+            e.printStackTrace ();
+        }
+
+        it.response.type (CONTENT_TYPE_JSON);
+        return toJson (it.queryParams (QUERIES_PARAM) == null? worlds[0] : worlds);
     }
 
-    private static Object getPlaintext (Exchange it) {
+    private static Object getPlaintext (Request it) {
         it.response.type (CONTENT_TYPE_TEXT);
         return MESSAGE;
     }
 
-    private static void addCommonHeaders (Exchange it) {
+    private static void addCommonHeaders (Request it) {
         it.header ("Server", "Undertow/1.1.2");
         it.response.raw ().addDateHeader ("Date", new Date ().getTime ());
     }
@@ -130,12 +211,14 @@ final class Application {
     public static void main (String[] args) {
         get ("/json", Application::getJson);
         get ("/db", Application::getDb);
-        get ("/fortune", Application::getFortune);
-        get ("/update", Application::getUpdate);
+        get ("/query", Application::getDb);
+        get ("/fortune", Application::getFortunes);
+        get ("/update", Application::getUpdates);
         get ("/plaintext", Application::getPlaintext);
         after (Application::addCommonHeaders);
 
-        setIpAddress (CONFIG.getProperty ("web.host"));
-        start (parseInt (CONFIG.getProperty ("web.port")));
+        host (SETTINGS.getProperty ("web.host"));
+        port (SETTINGS.getProperty ("web.port"));
+        start ();
     }
 }

+ 22 - 1
frameworks/Java/sabina/src/main/java/sabina/benchmark/Fortune.java

@@ -1,3 +1,17 @@
+/*
+ * Copyright © 2015 Juan José Aguililla. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
 package sabina.benchmark;
 
 /**
@@ -5,5 +19,12 @@ package sabina.benchmark;
  *
  * @author jam
  */
-public class Fortune {
+final class Fortune {
+    final int id;
+    final String message;
+
+    Fortune (final int id, final String message) {
+        this.id = id;
+        this.message = message;
+    }
 }

+ 15 - 1
frameworks/Java/sabina/src/main/java/sabina/benchmark/Message.java

@@ -1,5 +1,19 @@
+/*
+ * Copyright © 2015 Juan José Aguililla. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
 package sabina.benchmark;
 
 final class Message {
-    public final String message = "Hello, World!";
+    final String message = "Hello, World!";
 }

+ 20 - 1
frameworks/Java/sabina/src/main/java/sabina/benchmark/World.java

@@ -1,5 +1,24 @@
+/*
+ * Copyright © 2015 Juan José Aguililla. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
 package sabina.benchmark;
 
 final class World {
-    public int id, randomNumber;
+    final int id, randomNumber;
+
+    World (final int id, final int randomNumber) {
+        this.id = id;
+        this.randomNumber = randomNumber;
+    }
 }

+ 4 - 4
frameworks/Java/sabina/src/main/resources/sabina/view/fortunes.ftl → frameworks/Java/sabina/src/main/resources/fortunes.mustache

@@ -10,10 +10,10 @@
         <th>message</th>
     </tr>
     {{#.}}
-      <tr>
-          <td>{{id}}</td>
-          <td>{{message}}</td>
-      </tr>
+    <tr>
+        <td>{{id}}</td>
+        <td>{{message}}</td>
+    </tr>
     {{/.}}
 </table>
 </body>

+ 0 - 20
frameworks/Java/sabina/src/main/resources/sabina/view/fortunes.html

@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>Fortunes</title>
-</head>
-<body>
-<table>
-    <tr>
-        <th>id</th>
-        <th>message</th>
-    </tr>
-    <#list fortunes as fortune>
-      <tr>
-          <td>${fortune.id}</td>
-          <td>${fortune.message!null}</td>
-      </tr>
-    </#list>
-</table>
-</body>
-</html>

+ 15 - 1
frameworks/Java/sabina/src/main/resources/server.properties

@@ -1,4 +1,18 @@
-web.port = ${web.port}
+#
+# Copyright \u00A9 2015 Juan Jos\u00E9 Aguililla. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the
+# License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific language governing permissions
+# and limitations under the License.
+#
+
+web.port = 5050
 web.host = 0.0.0.0
 
 mongodb.uri = ${db.host}:27017

+ 144 - 47
frameworks/Java/sabina/src/test/java/sabina/benchmark/ApplicationTest.java

@@ -1,34 +1,83 @@
+/*
+ * Copyright © 2015 Juan José Aguililla. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
 package sabina.benchmark;
 
 import static org.apache.http.client.fluent.Request.Get;
-import static org.junit.Assert.*;
+import static org.testng.AssertJUnit.*;
 import static sabina.benchmark.Application.main;
 import static sabina.Sabina.stop;
-import static sun.misc.IOUtils.readFully;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.List;
 import java.util.Map;
+import java.util.Scanner;
 
 import com.google.gson.Gson;
 import org.apache.http.HttpResponse;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * <p>TODO
+ * Write article about stress test with TestNG (scenarios, combine different tests in scenarios,
+ * adding random pauses...)
+ *
+ * <p>TODO Change assert's order
+ */
 public final class ApplicationTest {
-    private static final String ENDPOINT = "http://localhost:8080";
+    private static final int THREADS = 16, EXECUTIONS = 32, WARM_UP = 32;
+
+    private static final String ENDPOINT = "http://localhost:5050";
     private static final Gson GSON = new Gson ();
 
     @BeforeClass public static void setup () {
         main (null);
     }
 
+    @BeforeClass public void warm_up () throws IOException {
+        for (int ii = 0; ii < WARM_UP; ii++) {
+            json ();
+            plaintext ();
+            no_query_parameter ();
+            empty_query_parameter ();
+            text_query_parameter ();
+            zero_queries ();
+            one_thousand_queries ();
+            one_query ();
+            ten_queries ();
+            five_hundred_queries ();
+            fortunes ();
+            no_updates_parameter ();
+            empty_updates_parameter ();
+            text_updates_parameter ();
+            zero_updates ();
+            one_thousand_updates ();
+            one_update ();
+            ten_updates ();
+            five_hundred_updates ();
+        }
+    }
+
     @AfterClass public static void close () {
         stop ();
     }
 
-    @Test public void json () throws IOException {
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void json () throws IOException {
         HttpResponse response = get (ENDPOINT + "/json");
         String content = getContent (response);
 
@@ -36,7 +85,8 @@ public final class ApplicationTest {
         assertEquals ("Hello, World!", GSON.fromJson (content, Map.class).get ("message"));
     }
 
-    @Test public void plaintext () throws IOException {
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void plaintext () throws IOException {
         HttpResponse response = get (ENDPOINT + "/plaintext");
         String content = getContent (response);
 
@@ -44,7 +94,8 @@ public final class ApplicationTest {
         assertEquals ("Hello, World!", content);
     }
 
-    @Test public void no_query_parameter () throws IOException {
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void no_query_parameter () throws IOException {
         HttpResponse response = get (ENDPOINT + "/db");
         String content = getContent (response);
 
@@ -53,75 +104,121 @@ public final class ApplicationTest {
         assertTrue (resultsMap.containsKey ("id") && resultsMap.containsKey ("randomNumber"));
     }
 
-    @Test public void empty_query_parameter () throws IOException {
-        HttpResponse response = get (ENDPOINT + "/db?queries");
-        String content = getContent (response);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void empty_query_parameter () throws IOException {
+        checkDbRequest ("/query?queries", 1);
+    }
 
-        checkResponse (response, content, "application/json");
-        checkResultItems (content, 1);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void text_query_parameter () throws IOException {
+        checkDbRequest ("/query?queries=text", 1);
     }
 
-    @Test public void text_query_parameter () throws IOException {
-        HttpResponse response = get (ENDPOINT + "/db?queries=text");
-        String content = getContent (response);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void zero_queries () throws IOException {
+        checkDbRequest ("/query?queries=0", 1);
+    }
 
-        checkResponse (response, content, "application/json");
-        checkResultItems (content, 1);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void one_thousand_queries () throws IOException {
+        checkDbRequest ("/query?queries=1000", 500);
     }
 
-    @Test public void zero_queries () throws IOException {
-        HttpResponse response = get (ENDPOINT + "/db?queries=0");
-        String content = getContent (response);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void one_query () throws IOException {
+        checkDbRequest ("/query?queries=1", 1);
+    }
 
-        checkResponse (response, content, "application/json");
-        checkResultItems (content, 1);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void ten_queries () throws IOException {
+        checkDbRequest ("/query?queries=10", 10);
+    }
+
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void five_hundred_queries () throws IOException {
+        checkDbRequest ("/query?queries=500", 500);
     }
 
-    @Test public void one_thousand_queries () throws IOException {
-        HttpResponse response = get (ENDPOINT + "/db?queries=1000");
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void fortunes () throws IOException {
+        HttpResponse response = get (ENDPOINT + "/fortune");
         String content = getContent (response);
+        String contentType = response.getEntity ().getContentType ().getValue ();
 
-        checkResponse (response, content, "application/json");
-        checkResultItems (content, 500);
+        assertTrue (response.getFirstHeader ("Server") != null);
+        assertTrue (response.getFirstHeader ("Date") != null);
+        assertTrue (content.contains ("&lt;script&gt;alert(&quot;This should not be displayed"));
+        assertTrue (content.contains ("フレームワークのベンチマーク"));
+        assertEquals ("text/html; charset=utf-8", contentType.toLowerCase ());
     }
 
-    @Test public void one_query () throws IOException {
-        HttpResponse response = get (ENDPOINT + "/db?queries=1");
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void no_updates_parameter () throws IOException {
+        HttpResponse response = get (ENDPOINT + "/update");
         String content = getContent (response);
 
         checkResponse (response, content, "application/json");
-        checkResultItems (content, 1);
+        Map<?, ?> resultsMap = GSON.fromJson (content, Map.class);
+        assertTrue (resultsMap.containsKey ("id") && resultsMap.containsKey ("randomNumber"));
     }
 
-    @Test public void ten_query () throws IOException {
-        HttpResponse response = get (ENDPOINT + "/db?queries=10");
-        String content = getContent (response);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void empty_updates_parameter () throws IOException {
+        checkDbRequest ("/update?queries", 1);
+    }
 
-        checkResponse (response, content, "application/json");
-        checkResultItems (content, 10);
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void text_updates_parameter () throws IOException {
+        checkDbRequest ("/update?queries=text", 1);
+    }
+
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void zero_updates () throws IOException {
+        checkDbRequest ("/update?queries=0", 1);
+    }
+
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void one_thousand_updates () throws IOException {
+        checkDbRequest ("/update?queries=1000", 500);
+    }
+
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void one_update () throws IOException {
+        checkDbRequest ("/update?queries=1", 1);
+    }
+
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void ten_updates () throws IOException {
+        checkDbRequest ("/update?queries=10", 10);
+    }
+
+    @Test(threadPoolSize = THREADS, invocationCount = EXECUTIONS)
+    public void five_hundred_updates () throws IOException {
+        checkDbRequest ("/update?queries=500", 500);
     }
 
-    @Test public void five_hundred_queries () throws IOException {
-        HttpResponse response = get (ENDPOINT + "/db?queries=500");
+    private void checkDbRequest (String path, int itemsCount) throws IOException {
+        HttpResponse response = get (ENDPOINT + path);
         String content = getContent (response);
 
         checkResponse (response, content, "application/json");
-        checkResultItems (content, 500);
+        checkResultItems (content, itemsCount);
     }
 
     private HttpResponse get (String uri) throws IOException {
         return Get (uri).execute ().returnResponse ();
     }
 
-    private String getContent (HttpResponse aResponse) throws IOException {
-        return new String (readFully (aResponse.getEntity ().getContent (), -1, true));
+    private String getContent (HttpResponse response) throws IOException {
+        InputStream in = response.getEntity ().getContent ();
+        return new Scanner (in).useDelimiter ("\\A").next ();
     }
 
-    private void checkResponse (HttpResponse aRes, String aContent, String contentType) {
-        assertTrue (aRes.getFirstHeader ("Server") != null);
-        assertTrue (aRes.getFirstHeader ("Date") != null);
-        assertEquals (aContent.length (), aRes.getEntity ().getContentLength ());
-        assertEquals (contentType, aRes.getEntity ().getContentType ().getValue ());
+    private void checkResponse (HttpResponse res, String content, String contentType) {
+        assertTrue (res.getFirstHeader ("Server") != null);
+        assertTrue (res.getFirstHeader ("Date") != null);
+        assertEquals (content.length (), res.getEntity ().getContentLength ());
+        assertEquals (contentType, res.getEntity ().getContentType ().getValue ());
     }
 
     private void checkResultItems (String result, int size) {