Explorar el Código

Merge pull request #883 from richdougherty/play-split-and-play-2.3

Play2: Upgrade non-DB tests to Play 2.3
Hamilton Turner hace 11 años
padre
commit
5f5a5676cb
Se han modificado 69 ficheros con 759 adiciones y 332 borrados
  1. 1 2
      .travis.yml
  2. 2 1
      config/benchmark_profile
  3. 0 81
      play-java/app/controllers/Application.java
  4. 0 25
      play-java/benchmark_config
  5. 0 3
      play-java/install.sh
  6. 0 12
      play-java/setup.py
  7. 0 0
      play-scala/__init__.py
  8. 0 93
      play-scala/app/controllers/Application.scala
  9. 0 49
      play-scala/benchmark_config
  10. 0 3
      play-scala/install.sh
  11. 0 33
      play-scala/setup.py
  12. 5 0
      play2/README.md
  13. 0 0
      play2/__init__.py
  14. 1 0
      play2/bash_profile.sh
  15. 104 0
      play2/benchmark_config
  16. 72 0
      play2/generate_config.py
  17. 3 0
      play2/install.sh
  18. 0 0
      play2/play2-java-ebean/.gitignore
  19. 2 9
      play2/play2-java-ebean/README.md
  20. 97 0
      play2/play2-java-ebean/app/controllers/Application.java
  21. 0 0
      play2/play2-java-ebean/app/models/World.java
  22. 0 0
      play2/play2-java-ebean/app/utils/Predicate.java
  23. 0 0
      play2/play2-java-ebean/app/utils/Predicated.java
  24. 0 0
      play2/play2-java-ebean/app/utils/PredicatedAction.java
  25. 1 1
      play2/play2-java-ebean/build.sbt
  26. 1 1
      play2/play2-java-ebean/conf/application.conf
  27. 10 0
      play2/play2-java-ebean/conf/routes
  28. 0 0
      play2/play2-java-ebean/project/build.properties
  29. 0 0
      play2/play2-java-ebean/project/plugins.sbt
  30. 0 0
      play2/play2-java-ebean/source_code
  31. 30 0
      play2/play2-java/.gitignore
  32. 18 0
      play2/play2-java/README.md
  33. 19 0
      play2/play2-java/app/controllers/Application.java
  34. 5 0
      play2/play2-java/build.sbt
  35. 26 0
      play2/play2-java/conf/application.conf
  36. 0 1
      play2/play2-java/conf/routes
  37. 1 0
      play2/play2-java/project/build.properties
  38. 8 0
      play2/play2-java/project/plugins.sbt
  39. 9 0
      play2/play2-java/source_code
  40. 0 0
      play2/play2-scala-anorm/.gitignore
  41. 1 8
      play2/play2-scala-anorm/README.md
  42. 106 0
      play2/play2-scala-anorm/app/controllers/Application.scala
  43. 0 0
      play2/play2-scala-anorm/app/models/Fortune.scala
  44. 6 5
      play2/play2-scala-anorm/app/models/World.scala
  45. 0 0
      play2/play2-scala-anorm/app/utils/PredicatedAction.scala
  46. 0 0
      play2/play2-scala-anorm/app/views/fortune.scala.html
  47. 0 0
      play2/play2-scala-anorm/app/views/main.scala.html
  48. 1 1
      play2/play2-scala-anorm/build.sbt
  49. 1 1
      play2/play2-scala-anorm/conf/application.conf
  50. 3 3
      play2/play2-scala-anorm/conf/routes
  51. 0 0
      play2/play2-scala-anorm/project/build.properties
  52. 0 0
      play2/play2-scala-anorm/project/plugins.sbt
  53. 0 0
      play2/play2-scala-anorm/source_code
  54. 33 0
      play2/play2-scala/.gitignore
  55. 18 0
      play2/play2-scala/README.md
  56. 12 0
      play2/play2-scala/app/controllers/Application.scala
  57. 5 0
      play2/play2-scala/build.sbt
  58. 31 0
      play2/play2-scala/conf/application.conf
  59. 9 0
      play2/play2-scala/conf/routes
  60. 1 0
      play2/play2-scala/project/build.properties
  61. 8 0
      play2/play2-scala/project/plugins.sbt
  62. 11 0
      play2/play2-scala/source_code
  63. 55 0
      play2/setup_common.py
  64. 6 0
      play2/setup_java.py
  65. 6 0
      play2/setup_java_ebean.py
  66. 6 0
      play2/setup_scala.py
  67. 6 0
      play2/setup_scala_anorm.py
  68. 7 0
      toolset/setup/linux/systools/sbt.sh
  69. 12 0
      toolset/setup/windows/installer.ps1

+ 1 - 2
.travis.yml

@@ -97,10 +97,9 @@ env:
     - TESTDIR=plain
     - TESTDIR=play1
     - TESTDIR=play1siena
+    - TESTDIR=play2
     - TESTDIR=play-activate-mysql
-    - TESTDIR=play-java
     - TESTDIR=play-java-jpa
-    - TESTDIR=play-scala
     - TESTDIR=play-scala-mongodb
     - TESTDIR=play-slick
     - TESTDIR=pyramid

+ 2 - 1
config/benchmark_profile

@@ -21,6 +21,7 @@ export NODE_HOME=${IROOT}/node-v0.10.8-linux-x64
 export PLAY_HOME=${IROOT}/play-2.2.0
 export PLAY1_HOME=${IROOT}/play-1.2.5
 export MAVEN_HOME=${IROOT}/apache-maven-3.0.5
+export SBT_HOME=${IROOT}/sbt
 export PERL_HOME=${IROOT}/perl-5.18
 export DART_HOME=${IROOT}/dart-sdk
 export RACKET_HOME=${IROOT}/racket-5.3.6
@@ -28,7 +29,7 @@ export NIMROD_HOME=${IROOT}/nimrod
 export NGINX_HOME=/usr/local/nginx
 export ELIXIR_HOME=${IROOT}/elixir-0.13.3
 
-export PATH="$JAVA_HOME/bin:$GRAILS_HOME/bin:$PLAY_HOME:$PLAY1_HOME:$VERTX_HOME/bin:$GOROOT/bin:$NODE_HOME/bin:$HOME/FrameworkBenchmarks/installs/bin:$MAVEN_HOME/bin:$PERL_HOME/bin:$DART_HOME/bin:$RACKET_HOME/bin:$NIMROD_HOME/bin:$NGINX_HOME/sbin:$ELIXIR_HOME/bin:$PATH"
+export PATH="$JAVA_HOME/bin:$GRAILS_HOME/bin:$PLAY_HOME:$PLAY1_HOME:$VERTX_HOME/bin:$GOROOT/bin:$NODE_HOME/bin:$HOME/FrameworkBenchmarks/installs/bin:$MAVEN_HOME/bin:$SBT_HOME/bin:$PERL_HOME/bin:$DART_HOME/bin:$RACKET_HOME/bin:$NIMROD_HOME/bin:$NGINX_HOME/sbin:$ELIXIR_HOME/bin:$PATH"
 
 export LD_LIBRARY_PATH='$LD_LIBRARY_PATH:/usr/local/apr/lib'
 

+ 0 - 81
play-java/app/controllers/Application.java

@@ -1,81 +0,0 @@
-package controllers;
-
-import akka.dispatch.ExecutionContexts;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import models.World;
-import play.Play;
-import play.core.NamedThreadFactory;
-import play.libs.F;
-import play.libs.Json;
-
-import play.mvc.Controller;
-import play.mvc.Result;
-import scala.concurrent.ExecutionContext;
-import utils.Predicate;
-import utils.Predicated;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.*;
-
-public class Application extends Controller {
-
-    private static final int MAX_QUERIES_PER_REQUEST = 20;
-    private static final int TEST_DATABASE_ROWS = 10000;
-    //http://stackoverflow.com/questions/3907929/should-i-make-jacksons-objectmapper-as-static-final
-    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
-    private static final int partitionCount = Play.application().configuration().getInt("db.default.partitionCount");
-    private static final int maxConnections =
-            partitionCount * Play.application().configuration().getInt("db.default.maxConnectionsPerPartition");
-    private static final int minConnections =
-            partitionCount * Play.application().configuration().getInt("db.default.minConnectionsPerPartition");
-
-    private static final ThreadPoolExecutor tpe = new ThreadPoolExecutor(minConnections, maxConnections,
-            0L, TimeUnit.MILLISECONDS,
-            new LinkedBlockingQueue<Runnable>(),
-            new NamedThreadFactory("dbEc"));
-    private static final ExecutionContext dbEc = ExecutionContexts.fromExecutorService(tpe);
-
-    // A predicate for checking our ability to service database requests is determined by ensuring that the request
-    // queue doesn't fill up beyond a certain threshold. For convenience we use the max number of connections * the max
-    // # of db requests per web request to determine this threshold. It is a rough check as we don't know how many
-    // queries we're going to make or what other threads are running in parallel etc. Nevertheless, the check is
-    // adequate in order to throttle the acceptance of requests to the size of the pool.
-    public static class IsDbAvailable implements Predicate {
-        @Override
-        public boolean condition() {
-            return tpe.getQueue().size() < maxConnections * MAX_QUERIES_PER_REQUEST;
-        }
-    }
-
-    public static Result json() {
-        final ObjectNode result = OBJECT_MAPPER.createObjectNode();
-        result.put("message", "Hello, World!");
-        return ok(result);
-    }
-
-    @Predicated(predicate = IsDbAvailable.class, failed = SERVICE_UNAVAILABLE)
-    public static F.Promise<Result> db(final Integer queries) {
-        final Random random = ThreadLocalRandom.current();
-        final List<F.Promise<? extends World>> promises = new ArrayList<F.Promise<? extends World>>(queries);
-        for (int i = 0; i < queries; ++i) {
-            final F.Promise<World> p = F.Promise.promise(new F.Function0<World>() {
-                @Override
-                public World apply() throws Throwable {
-                    return World.find.byId(Long.valueOf(random.nextInt(TEST_DATABASE_ROWS) + 1));
-                }
-            }, dbEc);
-            promises.add(p);
-        }
-        return F.Promise.sequence(promises).map(new F.Function<List<World>, Result>() {
-            @Override
-            public Result apply(List<World> worlds) {
-                return ok(Json.toJson(worlds));
-            }
-        });
-    }
-
-}

+ 0 - 25
play-java/benchmark_config

@@ -1,25 +0,0 @@
-{
-  "framework": "play2",
-  "tests": [{
-    "default": {
-      "setup_file": "setup",
-      "json_url": "/json",
-      "db_url": "/db",
-      "query_url": "/db?queries=",
-      "port": 9000,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "MySQL",
-      "framework": "play2",
-      "language": "Java",
-      "orm": "Full",
-      "platform": "Netty",
-      "webserver": "None",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "play-java-ebean",
-      "notes": "",
-      "versus": "netty"
-    }
-  }]
-}

+ 0 - 3
play-java/install.sh

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-fw_depends play2 java

+ 0 - 12
play-java/setup.py

@@ -1,12 +0,0 @@
-import setup_util
-import subprocess
-
-def start(args, logfile, errfile):
-  setup_util.replace_text("play-java/conf/application.conf", "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
-  subprocess.Popen(["play","start"], stdin=subprocess.PIPE, cwd="play-java", stderr=errfile, stdout=logfile)
-  return 0
-
-def stop(logfile, errfile):
-  p = subprocess.Popen(["play","stop"], cwd="play-java", stderr=errfile, stdout=logfile)
-  p.communicate()
-  return 0

+ 0 - 0
play-scala/__init__.py


+ 0 - 93
play-scala/app/controllers/Application.scala

@@ -1,93 +0,0 @@
-package controllers
-
-import play.api.Play.current
-import play.api.mvc._
-import play.api.libs.json.Json
-import java.util.concurrent._
-import scala.concurrent._
-import models.{World, Fortune}
-import utils._
-import scala.concurrent.Future
-
-import play.api.libs.concurrent.Execution.Implicits._
-import play.core.NamedThreadFactory
-
-object Application extends Controller {
-
-  private val MaxQueriesPerRequest = 20
-  private val TestDatabaseRows = 10000
-
-  private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
-  private val maxConnections =
-    partitionCount * current.configuration.getInt("db.default.maxConnectionsPerPartition").getOrElse(5)
-  private val minConnections =
-    partitionCount * current.configuration.getInt("db.default.minConnectionsPerPartition").getOrElse(5)
-
-  private val tpe = new ThreadPoolExecutor(minConnections, maxConnections,
-    0L, TimeUnit.MILLISECONDS,
-    new LinkedBlockingQueue[Runnable](),
-    new NamedThreadFactory("dbEc"))
-  private val dbEc = ExecutionContext.fromExecutorService(tpe)
-
-  // A predicate for checking our ability to service database requests is determined by ensuring that the request
-  // queue doesn't fill up beyond a certain threshold. For convenience we use the max number of connections * the max
-  // # of db requests per web request to determine this threshold. It is a rough check as we don't know how many
-  // queries we're going to make or what other threads are running in parallel etc. Nevertheless, the check is
-  // adequate in order to throttle the acceptance of requests to the size of the pool.
-  def isDbAvailable: Boolean = tpe.getQueue.size() < maxConnections * MaxQueriesPerRequest
-
-
-  def json() = Action {
-    Ok(Json.obj("message" -> "Hello, World!"))
-  }
-
-  def db(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
-    Action.async {
-      val random = ThreadLocalRandom.current()
-
-      val worlds = Future.sequence((for {
-        _ <- 1 to queries
-      } yield Future(World.findById(random.nextInt(TestDatabaseRows) + 1))(dbEc)
-        ).toList)
-
-      worlds map {
-        w => Ok(Json.toJson(w))
-      }
-    }
-  }
-
-  def fortunes() = PredicatedAction(isDbAvailable, ServiceUnavailable) {
-    Action.async {
-      Future(Fortune.getAll())(dbEc).map { fs =>
-        val fortunes =  Fortune(0.toLong, "Additional fortune added at request time.") +: fs
-        Ok(views.html.fortune(fortunes))
-      }
-    }
-  }
-
-  def update(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
-    Action.async {
-      val random = ThreadLocalRandom.current()
-
-      val boundsCheckedQueries = queries match {
-        case q if q > 500 => 500
-        case q if q <   1 => 1
-        case _ => queries
-      }
-
-      val worlds = Future.sequence((for {
-        _ <- 1 to boundsCheckedQueries
-      } yield Future {
-          val world = World.findById(random.nextInt(TestDatabaseRows) + 1)
-          val updatedWorld = world.copy(randomNumber = random.nextInt(TestDatabaseRows) + 1)
-          World.updateRandom(updatedWorld)
-          updatedWorld
-        }(dbEc)
-      ).toList)
-
-      worlds.map {
-        w => Ok(Json.toJson(w)).withHeaders("Server" -> "Netty")
-      }
-    }
-  }
-}

+ 0 - 49
play-scala/benchmark_config

@@ -1,49 +0,0 @@
-{
-  "framework": "play-scala",
-  "tests": [{
-    "default": {
-      "setup_file": "setup",
-      "json_url": "/json",
-      "db_url": "/db",
-      "query_url": "/db?queries=",
-      "fortune_url": "/fortunes",
-      "update_url": "/update?queries=",
-      "port": 9000,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "MySQL",
-      "framework": "play2",
-      "language": "Scala",
-      "orm": "Full",
-      "platform": "Netty",
-      "webserver": "None",
-      "os": "Linux",
-      "database_os": "Linux",
-      "display_name": "play-scala-anorm",
-      "notes": "",
-      "versus": "netty"
-    },
-    "windows-default": {
-      "setup_file": "setup",
-      "json_url": "/json",
-      "db_url": "/db",
-      "query_url": "/db?queries=",
-      "fortune_url": "/fortunes",
-      "update_url": "/update?queries=",
-      "port": 9000,
-      "approach": "Realistic",
-      "classification": "Fullstack",
-      "database": "MySQL",
-      "framework": "play2",
-      "language": "Scala",
-      "orm": "Full",
-      "platform": "Netty",
-      "webserver": "None",
-      "os": "Windows",
-      "database_os": "Linux",
-      "display_name": "play-scala-anorm",
-      "notes": "",
-      "versus": "netty"
-    }
-  }]
-}

+ 0 - 3
play-scala/install.sh

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-fw_depends play2

+ 0 - 33
play-scala/setup.py

@@ -1,33 +0,0 @@
-import setup_util
-import subprocess
-import os
-
-def start(args, logfile, errfile):
-  kill_running_process() # Kill the running process and delete the 
-                         # RUNNING_PID file (if any). With any luck no 
-                         # new process has picked up the same PID.
-
-  play_cmd = "play"
-  if args.os.lower() == "windows":
-    play_cmd = "play.bat"
-  
-  setup_util.replace_text("play-scala/conf/application.conf", "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
-  subprocess.Popen([play_cmd,"start"], stdin=subprocess.PIPE, cwd="play-scala", stderr=errfile, stdout=logfile)
-  return 0
-
-def stop(logfile, errfile):
-  kill_running_process()  
-  return 0
-
-def kill_running_process():
-  try:
-    with open("./play-scala/RUNNING_PID") as f:
-      pid = int(f.read())
-      os.kill(pid, 15)
-  except:
-  	pass
-
-  try:
-    os.remove("play-scala/RUNNING_PID")
-  except OSError:
-    pass  

+ 5 - 0
play2/README.md

@@ -0,0 +1,5 @@
+# play2 framework tests
+
+1. Add new test applications in subdirectories named either `play2-language` or `play2-language-orm`.
+2. Edit `generate_config.py` and add configuration for the new test applications.
+3. Run `python generate_config.py` to generate a new `benchmark_config` file and to generate a `setup_play2_*.py` file for your test application.

+ 0 - 0
play-java/__init__.py → play2/__init__.py


+ 1 - 0
play2/bash_profile.sh

@@ -0,0 +1 @@
+# Intentionally blank

+ 104 - 0
play2/benchmark_config

@@ -0,0 +1,104 @@
+{
+  "framework": "play2", 
+  "tests": [
+    {
+      "java": {
+        "display_name": "play2-java", 
+        "setup_file": "setup_java", 
+        "framework": "play2", 
+        "language": "Java", 
+        "orm": "Raw", 
+        "os": "Linux", 
+        "database": "None", 
+        "approach": "Realistic", 
+        "classification": "Fullstack", 
+        "platform": "Netty", 
+        "webserver": "None", 
+        "database_os": "Linux", 
+        "notes": "", 
+        "versus": "netty", 
+        "port": "9000", 
+        "json_url": "/json"
+      }, 
+      "scala": {
+        "display_name": "play2-scala", 
+        "setup_file": "setup_scala", 
+        "framework": "play2", 
+        "language": "Scala", 
+        "orm": "Raw", 
+        "os": "Linux", 
+        "database": "None", 
+        "approach": "Realistic", 
+        "classification": "Fullstack", 
+        "platform": "Netty", 
+        "webserver": "None", 
+        "database_os": "Linux", 
+        "notes": "", 
+        "versus": "netty", 
+        "port": "9000", 
+        "json_url": "/json"
+      }, 
+      "java-ebean": {
+        "display_name": "play2-java-ebean", 
+        "setup_file": "setup_java_ebean", 
+        "framework": "play2", 
+        "language": "Java", 
+        "orm": "Ebean", 
+        "os": "Linux", 
+        "database": "MySQL", 
+        "approach": "Realistic", 
+        "classification": "Fullstack", 
+        "platform": "Netty", 
+        "webserver": "None", 
+        "database_os": "Linux", 
+        "notes": "", 
+        "versus": "netty", 
+        "port": "9000", 
+        "db_url": "/db", 
+        "query_url": "/queries?queries="
+      }, 
+      "scala-anorm-linux": {
+        "display_name": "play2-scala-anorm-linux", 
+        "setup_file": "setup_scala_anorm", 
+        "framework": "play2", 
+        "language": "Scala", 
+        "orm": "Anorm", 
+        "os": "Linux", 
+        "database": "MySQL", 
+        "approach": "Realistic", 
+        "classification": "Fullstack", 
+        "platform": "Netty", 
+        "webserver": "None", 
+        "database_os": "Linux", 
+        "notes": "", 
+        "versus": "netty", 
+        "port": "9000", 
+        "db_url": "/db", 
+        "query_url": "/queries?queries=", 
+        "fortune_url": "/fortunes", 
+        "update_url": "/update?queries="
+      }, 
+      "scala-anorm-windows": {
+        "display_name": "play2-scala-anorm-windows", 
+        "setup_file": "setup_scala_anorm", 
+        "framework": "play2", 
+        "language": "Scala", 
+        "orm": "Anorm", 
+        "os": "Windows", 
+        "database": "MySQL", 
+        "approach": "Realistic", 
+        "classification": "Fullstack", 
+        "platform": "Netty", 
+        "webserver": "None", 
+        "database_os": "Linux", 
+        "notes": "", 
+        "versus": "netty", 
+        "port": "9000", 
+        "db_url": "/db", 
+        "query_url": "/queries?queries=", 
+        "fortune_url": "/fortunes", 
+        "update_url": "/update?queries="
+      }
+    }
+  ]
+}

+ 72 - 0
play2/generate_config.py

@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+import collections, json, textwrap
+
+# This script generates the benchmark_config and setup_*.py files.
+# To add new tests, modify the `configurations` and `test_urls` tables.
+
+# Each line corresponds to a test application.
+# Format is: (language, orm, (os, ...), (test, ...))
+# See the dir_name logic below to see the directory name for each test application.
+configurations = [
+  ('Java',  None,    ['Linux'],            ['json']),
+  ('Scala', None,    ['Linux'],            ['json']),
+  ('Java',  'Ebean', ['Linux'],            ['db', 'query']),
+  ('Scala', 'Anorm', ['Linux', 'Windows'], ['db', 'query', 'fortune', 'update']),
+]
+
+# All play2 test applications must use the same URLs.
+test_urls = {
+  'json': '/json',
+  'db': '/db',
+  'query': '/queries?queries=',
+  'fortune': '/fortunes',
+  'update': '/update?queries=',
+}
+
+tests_config_json = collections.OrderedDict()
+
+for lang, orm, oses, tests in configurations:
+  dir_name = 'play2-' + lang.lower() + (('-'+orm.lower()) if orm else '')
+  print 'Generating tests for test application '+dir_name
+  setup_name = 'setup_' + lang.lower() + (('_'+orm.lower()) if orm else '')
+  for os in oses:
+    if len(oses) == 1:
+      test_name = lang.lower() + (('-'+orm.lower()) if orm else '')
+    else:
+      test_name = lang.lower() + (('-'+orm.lower()) if orm else '') + '-'+os.lower()
+    test_config_json = collections.OrderedDict([
+      ('display_name', 'play2-'+test_name),
+      ('setup_file', setup_name),
+      ('framework', 'play2'),
+      ('language', lang),
+      ('orm', orm if orm else 'Raw'),
+      ('os', os),
+      ('database', 'MySQL' if orm else 'None'),
+      ('approach', 'Realistic'),
+      ('classification', 'Fullstack'),
+      ('platform', 'Netty'),
+      ('webserver', 'None'),
+      ('database_os', 'Linux'),
+      ('notes', ''),
+      ('versus', 'netty'),
+      ('port', '9000'),
+    ])
+    for test in tests:
+      test_config_json[test+'_url'] = test_urls[test]
+      tests_config_json[test_name] = test_config_json
+    with open(setup_name+'.py', 'w') as f:
+      f.write(textwrap.dedent("""
+        # This file was generated by generate_config.py.
+        # Do not edit this file directly.
+        from .setup_common import make_setup_for_dir
+
+        make_setup_for_dir(globals(), '"""+dir_name+"""')
+      """))
+
+with open('benchmark_config', 'w') as f:
+  json_str = json.dumps({
+    'framework': 'play2',
+    'tests': [tests_config_json]
+  }, indent=2)
+  f.write(json_str)

+ 3 - 0
play2/install.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+fw_depends java sbt

+ 0 - 0
play-java/.gitignore → play2/play2-java-ebean/.gitignore


+ 2 - 9
play-java/README.md → play2/play2-java-ebean/README.md

@@ -2,10 +2,6 @@
 
 This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
 
-### JSON Encoding Test
-
-* [JSON test source](app/controllers/Application.java)
-
 ### Data-Store/Database Mapping Test
 
 * [Database test controller](app/controllers/Application.java)
@@ -14,13 +10,10 @@ This is the Play portion of a [benchmarking test suite](../) comparing a variety
 ## Infrastructure Software Versions
 The tests were run with:
 
-* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
-* [Play 2.1.0](http://http://www.playframework.com/)
+* [Java OpenJDK 1.7](http://openjdk.java.net/)
+* [Play 2.2.0](http://http://www.playframework.com/)
 
 ## Test URLs
-### JSON Encoding Test
-
-http://localhost/json
 
 ### Data-Store/Database Mapping Test
 

+ 97 - 0
play2/play2-java-ebean/app/controllers/Application.java

@@ -0,0 +1,97 @@
+package controllers;
+
+import akka.dispatch.ExecutionContexts;
+import models.World;
+import play.Play;
+import play.core.NamedThreadFactory;
+import play.libs.F;
+import play.libs.Json;
+
+import play.mvc.Controller;
+import play.mvc.Result;
+import scala.concurrent.ExecutionContext;
+import utils.Predicate;
+import utils.Predicated;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.*;
+
+public class Application extends Controller {
+
+    private static final int MAX_QUERIES_PER_REQUEST = 20;
+    private static final int TEST_DATABASE_ROWS = 10000;
+
+    private static final int partitionCount = Play.application().configuration().getInt("db.default.partitionCount");
+    private static final int maxConnections =
+            partitionCount * Play.application().configuration().getInt("db.default.maxConnectionsPerPartition");
+    private static final int minConnections =
+            partitionCount * Play.application().configuration().getInt("db.default.minConnectionsPerPartition");
+
+    private static final ThreadPoolExecutor tpe = new ThreadPoolExecutor(minConnections, maxConnections,
+            0L, TimeUnit.MILLISECONDS,
+            new LinkedBlockingQueue<Runnable>(),
+            new NamedThreadFactory("dbEc"));
+    private static final ExecutionContext dbEc = ExecutionContexts.fromExecutorService(tpe);
+
+    // If the thread-pool used by the database grows too large then our server
+    // is probably struggling, and we should start dropping requests. Set
+    // the max size of our queue something above the number of concurrent
+    // connections that we need to handle.
+    public static class IsDbAvailable implements Predicate {
+        @Override
+        public boolean condition() {
+            return tpe.getQueue().size() <= 1024;
+        }
+    }
+
+    @Predicated(predicate = IsDbAvailable.class, failed = SERVICE_UNAVAILABLE)
+    public static F.Promise<Result> db() {
+        return getRandomWorlds(1).map(new F.Function<List<World>, Result>() {
+            @Override
+            public Result apply(List<World> worlds) {
+                return ok(Json.toJson(worlds.get(0)));
+            }
+        });
+    }
+
+    @Predicated(predicate = IsDbAvailable.class, failed = SERVICE_UNAVAILABLE)
+    public static F.Promise<Result> queries(final String queryCountString) {
+        int queryCount;
+        try {
+            queryCount = Integer.parseInt(queryCountString, 10);
+        } catch (NumberFormatException e) {
+            queryCount = 1;
+        }
+        if (queryCount < 1) {
+            queryCount = 1;
+        } else if (queryCount > 500) {
+            queryCount = 500;
+        }
+
+        return getRandomWorlds(queryCount).map(new F.Function<List<World>, Result>() {
+            @Override
+            public Result apply(List<World> worlds) {
+                return ok(Json.toJson(worlds));
+            }
+        });
+    }
+
+    private static F.Promise<List<World>> getRandomWorlds(final int n) {
+        return F.Promise.promise(new F.Function0<List<World>>() {
+            @Override
+            public List<World> apply() {
+                Random random = ThreadLocalRandom.current();
+                List<World> worlds = new ArrayList<World>(n);
+                for (int i = 0; i < n; ++i) {
+                    long randomId = random.nextInt(TEST_DATABASE_ROWS) + 1;
+                    World world = World.find.byId(randomId);
+                    worlds.add(world);
+                }
+                return worlds;
+            }
+        }, dbEc);
+    }
+
+}

+ 0 - 0
play-java/app/models/World.java → play2/play2-java-ebean/app/models/World.java


+ 0 - 0
play-java/app/utils/Predicate.java → play2/play2-java-ebean/app/utils/Predicate.java


+ 0 - 0
play-java/app/utils/Predicated.java → play2/play2-java-ebean/app/utils/Predicated.java


+ 0 - 0
play-java/app/utils/PredicatedAction.java → play2/play2-java-ebean/app/utils/PredicatedAction.java


+ 1 - 1
play-java/build.sbt → play2/play2-java-ebean/build.sbt

@@ -1,4 +1,4 @@
-name := "play-java"
+name := "play2-java-ebean"
 
 version := "1.0-SNAPSHOT"
 

+ 1 - 1
play-java/conf/application.conf → play2/play2-java-ebean/conf/application.conf

@@ -50,7 +50,7 @@ db.default.minConnectionsPerPartition=64
 # Evolutions
 # ~~~~~
 # You can disable evolutions if needed
-# evolutionplugin=disabled
+evolutionplugin=disabled
 
 # Ebean configuration
 # ~~~~~

+ 10 - 0
play2/play2-java-ebean/conf/routes

@@ -0,0 +1,10 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+
+# Home page
+GET     /db                             controllers.Application.db()
+GET     /queries                        controllers.Application.queries(queries ?= "1")
+
+# Map static resources from the /public folder to the /assets URL path
+GET     /assets/*file               controllers.Assets.at(path="/public", file)

+ 0 - 0
play-java/project/build.properties → play2/play2-java-ebean/project/build.properties


+ 0 - 0
play-java/project/plugins.sbt → play2/play2-java-ebean/project/plugins.sbt


+ 0 - 0
play-java/source_code → play2/play2-java-ebean/source_code


+ 30 - 0
play2/play2-java/.gitignore

@@ -0,0 +1,30 @@
+logs
+project/project
+project/target
+target
+tmp
+.history
+dist
+
+# Ignore all dotfiles...
+.*
+# except for .gitignore
+!.gitignore
+
+# Ignore Play! working directory #
+db
+eclipse
+lib
+log
+logs
+modules
+precompiled
+project/project
+project/target
+target
+tmp
+test-result
+server.pid
+*.iml
+*.eml
+

+ 18 - 0
play2/play2-java/README.md

@@ -0,0 +1,18 @@
+#Play Benchmarking Test
+
+This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](app/controllers/Application.java)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
+* [Play 2](http://http://www.playframework.com/)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/json

+ 19 - 0
play2/play2-java/app/controllers/Application.java

@@ -0,0 +1,19 @@
+package controllers;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import play.mvc.Controller;
+import play.mvc.Result;
+
+public class Application extends Controller {
+
+    //http://stackoverflow.com/questions/3907929/should-i-make-jacksons-objectmapper-as-static-final
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    public static Result json() {
+        final ObjectNode result = OBJECT_MAPPER.createObjectNode();
+        result.put("message", "Hello, World!");
+        return ok(result);
+    }
+
+}

+ 5 - 0
play2/play2-java/build.sbt

@@ -0,0 +1,5 @@
+name := "play2-java"
+
+version := "1.0-SNAPSHOT"
+
+lazy val root = (project in file(".")).enablePlugins(PlayJava)

+ 26 - 0
play2/play2-java/conf/application.conf

@@ -0,0 +1,26 @@
+# This is the main configuration file for the application.
+# ~~~~~
+
+# Secret key
+# ~~~~~
+# The secret key is used to secure cryptographics functions.
+# If you deploy your application to several instances be sure to use the same key!
+application.secret="RItx1I:80?W@]8GAtPDuF8Ydd3mXM85p/<7og]Q;uBOdijQAauRDgu73B6`wQP59"
+
+# The application languages
+# ~~~~~
+application.langs="en"
+
+# Logger
+# ~~~~~
+# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory .
+
+# Root logger:
+logger.root=ERROR
+
+# Logger used by the framework:
+logger.play=ERROR
+
+# Logger provided to your application:
+logger.application=ERROR
+

+ 0 - 1
play-java/conf/routes → play2/play2-java/conf/routes

@@ -4,7 +4,6 @@
 
 # Home page
 GET     /json                           controllers.Application.json
-GET     /db                             controllers.Application.db(queries: Int ?= 1)
 
 # Map static resources from the /public folder to the /assets URL path
 GET     /assets/*file               controllers.Assets.at(path="/public", file)

+ 1 - 0
play2/play2-java/project/build.properties

@@ -0,0 +1 @@
+sbt.version=0.13.5

+ 8 - 0
play2/play2-java/project/plugins.sbt

@@ -0,0 +1,8 @@
+// Comment to get more information during initialization
+logLevel := Level.Warn
+
+// The Typesafe repository 
+resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
+
+// Use the Play sbt plugin for Play projects
+addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.2")

+ 9 - 0
play2/play2-java/source_code

@@ -0,0 +1,9 @@
+./play-java/app/
+./play-java/app/controllers
+./play-java/app/controllers/Application.java
+./play-java/app/utils
+./play-java/app/utils/Predicate.java
+./play-java/app/utils/PredicatedAction.java
+./play-java/app/utils/Predicated.java
+./play-java/app/models
+./play-java/app/models/World.java

+ 0 - 0
play-scala/.gitignore → play2/play2-scala-anorm/.gitignore


+ 1 - 8
play-scala/README.md → play2/play2-scala-anorm/README.md

@@ -2,10 +2,6 @@
 
 This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
 
-### JSON Encoding Test
-
-* [JSON test source](app/controllers/Application.scala)
-
 ### Data-Store/Database Mapping Test
 
 * [Database test controller](app/controllers/Application.scala)
@@ -15,12 +11,9 @@ This is the Play portion of a [benchmarking test suite](../) comparing a variety
 The tests were run with:
 
 * [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
-* [Play 2.1.0](http://http://www.playframework.com/)
+* [Play 2](http://http://www.playframework.com/)
 
 ## Test URLs
-### JSON Encoding Test
-
-http://localhost/json
 
 ### Data-Store/Database Mapping Test
 

+ 106 - 0
play2/play2-scala-anorm/app/controllers/Application.scala

@@ -0,0 +1,106 @@
+package controllers
+
+import play.api.Play.current
+import play.api.db.DB
+import play.api.mvc._
+import play.api.libs.json.Json
+import java.util.concurrent._
+import scala.concurrent._
+import models.{World, Fortune}
+import utils._
+import scala.concurrent.Future
+
+import play.api.libs.concurrent.Execution.Implicits._
+import play.core.NamedThreadFactory
+
+object Application extends Controller {
+
+  private val TestDatabaseRows = 10000
+
+  private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
+  private val maxConnections =
+    partitionCount * current.configuration.getInt("db.default.maxConnectionsPerPartition").getOrElse(5)
+  private val minConnections =
+    partitionCount * current.configuration.getInt("db.default.minConnectionsPerPartition").getOrElse(5)
+
+  private val tpe = new ThreadPoolExecutor(minConnections, maxConnections,
+    0L, TimeUnit.MILLISECONDS,
+    new LinkedBlockingQueue[Runnable](),
+    new NamedThreadFactory("dbEc"))
+  private val dbEc = ExecutionContext.fromExecutorService(tpe)
+
+  // If the thread-pool used by the database grows too large then our server
+  // is probably struggling, and we should start dropping requests. Set
+  // the max size of our queue something above the number of concurrent
+  // connections that we need to handle.
+  def isDbQueueTooBig: Boolean = tpe.getQueue.size() <= 1024
+
+  def db = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
+    Action.async {
+      getRandomWorlds(1).map { worlds =>
+        Ok(Json.toJson(worlds.head))
+      }
+    }
+  }
+
+  def queries(countString: String) = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
+    Action.async {
+      val n = parseCount(countString)
+      getRandomWorlds(n).map { worlds =>
+        Ok(Json.toJson(worlds))
+      }
+    }
+  }
+
+  private def parseCount(s: String): Int = {
+    try {
+      val parsed = java.lang.Integer.parseInt(s, 10)
+      parsed match {
+        case i if i < 1 => 1
+        case i if i > 500 => 500
+        case i => i
+      }
+    } catch {
+      case _: NumberFormatException => 1
+    }
+  }
+
+  private def getRandomWorlds(n: Int): Future[Seq[World]] = Future {
+    val random = ThreadLocalRandom.current()
+    DB.withConnection { implicit connection =>
+      for (_ <- 1 to n) yield {
+        val randomId: Long = random.nextInt(TestDatabaseRows) + 1
+        World.findById(randomId)
+      }
+    }
+  }(dbEc)
+
+  def fortunes() = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
+    Action.async {
+      Future {
+        val fortunes = Fortune.getAll()
+        val extendedFortunes = Fortune(0.toLong, "Additional fortune added at request time.") +: fortunes
+        Ok(views.html.fortune(extendedFortunes))
+      }
+    }
+  }
+
+  def update(countString: String) = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
+    Action.async {
+      val n = parseCount(countString)
+      Future {
+        val random = ThreadLocalRandom.current()
+        val worlds = DB.withConnection { implicit connection =>
+          for(_ <- 1 to n) yield {
+            val randomId: Long = random.nextInt(TestDatabaseRows) + 1
+            val world = World.findById(random.nextInt(TestDatabaseRows) + 1)
+            val updatedWorld = world.copy(randomNumber = random.nextInt(10000) + 1)
+            World.updateRandom(updatedWorld)
+            updatedWorld
+          }
+        }
+        Ok(Json.toJson(worlds)).withHeaders("Server" -> "Netty")
+      }(dbEc)
+    }
+  }
+}

+ 0 - 0
play-scala/app/models/Fortune.scala → play2/play2-scala-anorm/app/models/Fortune.scala


+ 6 - 5
play-scala/app/models/World.scala → play2/play2-scala-anorm/app/models/World.scala

@@ -1,11 +1,12 @@
 package models
 
-import play.api.db._
-import play.api.Play.current
 import anorm._
 import anorm.SqlParser._
-import play.api.libs.json._
+import java.sql.Connection
+import play.api.db._
 import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import play.api.Play.current
 
 case class World(id: Pk[Long], randomNumber: Long)
 
@@ -35,7 +36,7 @@ object World {
   /**
    * Retrieve a World by id.
    */
-  def findById(id: Long): World = {
+  def findById(id: Long)(implicit connection: Connection): World = {
     DB.withConnection { implicit connection =>
       SQL("SELECT * FROM World WHERE id = {id}").on(
           "id" -> id
@@ -43,7 +44,7 @@ object World {
     }
   }
 
-  def updateRandom(world: World) {
+  def updateRandom(world: World)(implicit connection: Connection) {
     DB.withConnection { implicit connection =>
       SQL("UPDATE World SET randomNumber = {randomNumber} WHERE id = {id}").on(
         "id" -> world.id.get,

+ 0 - 0
play-scala/app/utils/PredicatedAction.scala → play2/play2-scala-anorm/app/utils/PredicatedAction.scala


+ 0 - 0
play-scala/app/views/fortune.scala.html → play2/play2-scala-anorm/app/views/fortune.scala.html


+ 0 - 0
play-scala/app/views/main.scala.html → play2/play2-scala-anorm/app/views/main.scala.html


+ 1 - 1
play-scala/build.sbt → play2/play2-scala-anorm/build.sbt

@@ -1,4 +1,4 @@
-name := "play-scala"
+name := "play2-scala-anorm"
 
 version := "1.0-SNAPSHOT"
 

+ 1 - 1
play-scala/conf/application.conf → play2/play2-scala-anorm/conf/application.conf

@@ -30,7 +30,7 @@ application.langs="en"
 # You can expose this datasource via JNDI if needed (Useful for JPA)
 # db.default.jndiName=DefaultDS
 db.default.driver= com.mysql.jdbc.Driver
-db.default.url="jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
+db.default.url="jdbc:mysql://!!DATABASE_HOST!!:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
 db.default.user=benchmarkdbuser
 db.default.password=benchmarkdbpass
 db.default.jndiName=DefaultDS

+ 3 - 3
play-scala/conf/routes → play2/play2-scala-anorm/conf/routes

@@ -3,10 +3,10 @@
 # ~~~~
 
 # Home page
-GET     /json                           controllers.Application.json
-GET     /db                             controllers.Application.db(queries: Int ?= 1)
+GET     /db                             controllers.Application.db
+GET     /queries                        controllers.Application.queries(queries ?= "1")
 GET     /fortunes                       controllers.Application.fortunes
-GET     /update                         controllers.Application.update(queries: Int ?= 1)
+GET     /update                         controllers.Application.update(queries ?= "1")
 
 # Map static resources from the /public folder to the /assets URL path
 GET     /assets/*file                   controllers.Assets.at(path="/public", file)

+ 0 - 0
play-scala/project/build.properties → play2/play2-scala-anorm/project/build.properties


+ 0 - 0
play-scala/project/plugins.sbt → play2/play2-scala-anorm/project/plugins.sbt


+ 0 - 0
play-scala/source_code → play2/play2-scala-anorm/source_code


+ 33 - 0
play2/play2-scala/.gitignore

@@ -0,0 +1,33 @@
+logs
+project/project
+project/target
+public
+target
+test
+tmp
+.history
+dist
+conf/evolutions
+
+# Ignore all dotfiles...
+.*
+# except for .gitignore
+!.gitignore
+
+# Ignore Play! working directory #
+db
+eclipse
+lib
+log
+logs
+modules
+precompiled
+project/project
+project/target
+target
+tmp
+test-result
+server.pid
+*.iml
+*.eml
+

+ 18 - 0
play2/play2-scala/README.md

@@ -0,0 +1,18 @@
+#Play Benchmarking Test
+
+This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](app/controllers/Application.scala)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
+* [Play 2](http://http://www.playframework.com/)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/json

+ 12 - 0
play2/play2-scala/app/controllers/Application.scala

@@ -0,0 +1,12 @@
+package controllers
+
+import play.api.mvc._
+import play.api.libs.json.Json
+
+object Application extends Controller {
+
+  def json() = Action {
+    Ok(Json.obj("message" -> "Hello, World!"))
+  }
+
+}

+ 5 - 0
play2/play2-scala/build.sbt

@@ -0,0 +1,5 @@
+name := "play2-scala"
+
+version := "1.0-SNAPSHOT"
+
+lazy val root = (project in file(".")).enablePlugins(PlayScala)

+ 31 - 0
play2/play2-scala/conf/application.conf

@@ -0,0 +1,31 @@
+# This is the main configuration file for the application.
+# ~~~~~
+
+# Secret key
+# ~~~~~
+# The secret key is used to secure cryptographics functions.
+# If you deploy your application to several instances be sure to use the same key!
+application.secret="RItx1I:80?W@]8GAtPDuF8Ydd3mXM85p/<7og]Q;uBOdijQAauRDgu73B6`wQP59"
+
+# The application languages
+# ~~~~~
+application.langs="en"
+
+# Global object class
+# ~~~~~
+# Define the Global object class for this application.
+# Default to Global in the root package.
+# global=Global
+
+# Logger
+# ~~~~~
+# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory .
+
+# Root logger:
+logger.root=ERROR
+
+# Logger used by the framework:
+logger.play=ERROR
+
+# Logger provided to your application:
+logger.application=ERROR

+ 9 - 0
play2/play2-scala/conf/routes

@@ -0,0 +1,9 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+
+# Home page
+GET     /json                           controllers.Application.json
+
+# Map static resources from the /public folder to the /assets URL path
+GET     /assets/*file                   controllers.Assets.at(path="/public", file)

+ 1 - 0
play2/play2-scala/project/build.properties

@@ -0,0 +1 @@
+sbt.version=0.13.5

+ 8 - 0
play2/play2-scala/project/plugins.sbt

@@ -0,0 +1,8 @@
+// Comment to get more information during initialization
+logLevel := Level.Warn
+
+// The Typesafe repository 
+resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
+
+// Use the Play sbt plugin for Play projects
+addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.2")

+ 11 - 0
play2/play2-scala/source_code

@@ -0,0 +1,11 @@
+./play-scala/app/
+./play-scala/app/controllers
+./play-scala/app/controllers/Application.scala
+./play-scala/app/views
+./play-scala/app/views/main.scala.html
+./play-scala/app/views/fortune.scala.html
+./play-scala/app/utils
+./play-scala/app/utils/PredicatedAction.scala
+./play-scala/app/models
+./play-scala/app/models/Fortune.scala
+./play-scala/app/models/World.scala

+ 55 - 0
play2/setup_common.py

@@ -0,0 +1,55 @@
+import os, setup_util, signal, subprocess
+
+# Create start and stop functions for the Play project with the given dir
+# and install them in the given module's globals.
+def make_setup_for_dir(module_globals, subtest_name):
+
+  def start(args, logfile, errfile):
+    kill_running_process(logfile)
+
+    subtest_dir = get_subtest_dir()
+    install_dir = os.environ['IROOT']
+
+    is_windows = os.name == "nt"
+    cmd_suffix = '.bat' if is_windows else ''
+    sbt_cmd = os.path.join(install_dir, 'sbt', 'bin', 'sbt'+cmd_suffix)
+    app_cmd = os.path.join(subtest_dir, 'target','universal','stage','bin',subtest_name+cmd_suffix)
+
+    setup_util.replace_text(
+      os.path.join(subtest_dir,'conf','application.conf'),
+      "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
+    logfile.write('Staging app: '+sbt_cmd+' stage\n')
+    subprocess.call(
+      [sbt_cmd, 'stage'],
+      stdin=subprocess.PIPE, cwd=subtest_dir, stderr=errfile, stdout=logfile)
+    logfile.write('Starting app: '+app_cmd+'\n')
+    subprocess.Popen(
+      [app_cmd],
+      shell=True, stdin=subprocess.PIPE, cwd=subtest_dir, stderr=errfile, stdout=logfile)
+    return 0
+
+  def stop(logfile, errfile):
+    kill_running_process(logfile)  
+    return 0
+
+  # Install the start and stop functions in the calling module
+  module_globals['start'] = start
+  module_globals['stop'] = stop
+
+  def get_subtest_dir():
+    test_dir = os.environ['TROOT']
+    return os.path.join(test_dir, subtest_name)
+
+  # Kill the running process and delete the RUNNING_PID file (if any).
+  def kill_running_process(logfile):
+    subtest_dir = get_subtest_dir()
+    pidfile = os.path.join(subtest_dir,"target","universal","stage","RUNNING_PID")
+    if not os.path.exists(pidfile):
+      logfile.write('No PID file: {}\n'.format(pidfile))
+      return
+    logfile.write('Reading and deleting PID file: {}\n'.format(pidfile))
+    with open(pidfile) as f:
+      pid = int(f.read())
+    os.remove(pidfile)
+    logfile.write('Sending SIGTERM to process: {}\n'.format(pid))
+    os.kill(pid, signal.SIGTERM)

+ 6 - 0
play2/setup_java.py

@@ -0,0 +1,6 @@
+
+# This file was generated by generate_config.py.
+# Do not edit this file directly.
+from .setup_common import make_setup_for_dir
+
+make_setup_for_dir(globals(), 'play2-java')

+ 6 - 0
play2/setup_java_ebean.py

@@ -0,0 +1,6 @@
+
+# This file was generated by generate_config.py.
+# Do not edit this file directly.
+from .setup_common import make_setup_for_dir
+
+make_setup_for_dir(globals(), 'play2-java-ebean')

+ 6 - 0
play2/setup_scala.py

@@ -0,0 +1,6 @@
+
+# This file was generated by generate_config.py.
+# Do not edit this file directly.
+from .setup_common import make_setup_for_dir
+
+make_setup_for_dir(globals(), 'play2-scala')

+ 6 - 0
play2/setup_scala_anorm.py

@@ -0,0 +1,6 @@
+
+# This file was generated by generate_config.py.
+# Do not edit this file directly.
+from .setup_common import make_setup_for_dir
+
+make_setup_for_dir(globals(), 'play2-scala-anorm')

+ 7 - 0
toolset/setup/linux/systools/sbt.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+RETCODE=$(fw_exists sbt/bin/sbt)
+[ ! "$RETCODE" == 0 ] || { return 0; }
+
+fw_get http://dl.bintray.com/sbt/native-packages/sbt/0.13.5/sbt-0.13.5.zip -O sbt-0.13.5.zip
+fw_unzip sbt-0.13.5.zip

+ 12 - 0
toolset/setup/windows/installer.ps1

@@ -24,6 +24,7 @@ $maven_installer_path     = "maven-3/3.0.5/binaries/$maven_installer_file"
 $scala_version            = "2.10.2"
 $play_version             = "2.2.0"
 $play_installer_file      = "play-$play_version.zip"
+$sbt_version              = "0.13.5"
 $mercurial_installer_file = "mercurial-2.6.1-x64.msi"
 $cygwin_installer_file    = "setup-x86_64.exe"
 
@@ -306,6 +307,17 @@ $play_dir = "C:\Java\play"
 Move-Item "$workdir\play-$play_version" $play_dir
 $env:Path += ";$play_dir"; [Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
 
+# sbt
+$sbt_installer_file = "sbt-$sbt_version.zip"
+$sbt_url = "http://dl.bintray.com/sbt/native-packages/sbt/$sbt_version/$sbt_installer_file"
+$sbt_local = "$workdir\$sbt_installer_file"
+$sbt_dir = "C:\Java\sbt"
+(New-Object System.Net.WebClient).DownloadFile($sbt_url, $sbt_local)
+[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
+[System.IO.Compression.ZipFile]::ExtractToDirectory($sbt_local, $workdir) | Out-Null
+Move-Item "$workdir\sbt" $sbt_dir
+$env:Path += ";$sbt_dir\bin"; [Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine)
+
 #
 # Firewall
 #