Merge branch 'stable-3.1' into stable-3.2

* stable-3.1:
  BatchUpdateTest: Extend GerritBaseTests
  dev-bazel: Fix package name in example test invocation
  ReviewDbBatchUpdate: Fix typo in member name
  Enable to run online noteDb migration using multiple threads
  GerritSimulation: Support runtime factor property
  GerritSimulation: Add replaceOverride javadoc tags
  GerritSimulation: Support reusable step wait times

Change-Id: Ifdb0007187a3d772e0398c07aed3b4836e4a97a0
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 3653241..39bdd6a 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -387,7 +387,7 @@
 `GERRIT_LOG_LEVEL=debug` environment variable:
 
 ----
-  bazel test --test_filter=com.gerrit.server.notedb.ChangeNotesTest \
+  bazel test --test_filter=com.google.gerrit.server.notedb.ChangeNotesTest \
   --test_env=GERRIT_LOG_LEVEL=debug \
   javatests/com/google/gerrit/server:server_tests
 ----
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 6d897ec..8fe8f4e 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -170,6 +170,17 @@
 gets automatically generated by the scenario. Any property setting for it is therefore not
 applicable. Its usage differs from the non-prefixed `PROJECT` keyword, in that sense.
 
+The following core property can be optionally set depending on the runtime environment. The test
+environments used as reference for scenarios development assume its default value, `1.0`. For
+slower or more complex execution environments, the value can be increased this way for example:
+
+* `-Dcom.google.gerrit.scenarios.power_factor=1.5`
+
+This will make the scenario steps take half more time to expect proper completion. A value smaller
+than the default, say `0.8`, will make scenarios wait somewhat less than how they were developed.
+Scenario development is often done using locally running Gerrit systems under test, which are
+sometimes dockerized.
+
 == How to run tests
 
 Run all tests:
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
index c3f772a..ab91185 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
@@ -23,6 +23,7 @@
 class CloneUsingBothProtocols extends GitSimulation {
   private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue
   private val default: String = name
+  private val duration: Int = 2
 
   override def replaceOverride(in: String): String = {
     replaceKeyWith("_project", default, in)
@@ -37,14 +38,15 @@
 
   setUp(
     createProject.test.inject(
+      nothingFor(stepWaitTime(createProject) seconds),
       atOnceUsers(1)
     ),
     test.inject(
-      nothingFor(2 seconds),
-      constantUsersPerSec(1) during (2 seconds)
+      nothingFor(stepWaitTime(this) seconds),
+      constantUsersPerSec(1) during (duration seconds)
     ),
     deleteProject.test.inject(
-      nothingFor(6 seconds),
+      nothingFor(stepWaitTime(deleteProject) + duration seconds),
       atOnceUsers(1)
     ),
   ).protocols(gitProtocol, httpProtocol)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
index 75fb0b7..39b6d42 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
@@ -26,6 +26,8 @@
   private val default: String = name
   private val numberKey = "_number"
 
+  override def relativeRuntimeWeight = 2
+
   val test: ScenarioBuilder = scenario(unique)
       .feed(data)
       .exec(httpRequest
@@ -42,18 +44,19 @@
 
   setUp(
     createProject.test.inject(
+      nothingFor(stepWaitTime(createProject) seconds),
       atOnceUsers(1)
     ),
     test.inject(
-      nothingFor(2 seconds),
+      nothingFor(stepWaitTime(this) seconds),
       atOnceUsers(1)
     ),
     deleteChange.test.inject(
-      nothingFor(6 seconds),
+      nothingFor(stepWaitTime(deleteChange) seconds),
       atOnceUsers(1)
     ),
     deleteProject.test.inject(
-      nothingFor(8 seconds),
+      nothingFor(stepWaitTime(deleteProject) seconds),
       atOnceUsers(1)
     ),
   ).protocols(httpProtocol)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
index 0466ced..f3a2d14 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
@@ -23,6 +23,8 @@
   private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).queue
   var number: Option[Int] = None
 
+  override def relativeRuntimeWeight = 2
+
   val test: ScenarioBuilder = scenario(unique)
       .feed(data)
       .exec(session => {
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
index 06f0bdf..36df627 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
@@ -31,6 +31,26 @@
   protected val body: String = s"$pathName-body.json"
   protected val unique: String = name + "-" + this.hashCode()
 
+  private val powerFactor: Double = replaceProperty("power_factor", 1.0).toDouble
+  private val SecondsPerWeightUnit: Int = 2
+  val maxExecutionTime: Int = (SecondsPerWeightUnit * relativeRuntimeWeight * powerFactor).toInt
+  private var cumulativeWaitTime: Int = 0
+
+  /**
+   * How long a scenario step should wait before starting to execute.
+   * This is also registering that step's resulting wait time, so that time
+   * can be reused cumulatively by a potentially following scenario step.
+   * (Otherwise, the Gatling set-up scenario steps execute all at once.)
+   *
+   * @param scenario for which to return a wait time.
+   * @return that step's wait time as an Int.
+   */
+  protected def stepWaitTime(scenario: GerritSimulation): Int = {
+    val currentWaitTime = cumulativeWaitTime
+    cumulativeWaitTime += scenario.maxExecutionTime
+    currentWaitTime
+  }
+
   protected val httpRequest: HttpRequestBuilder = http(unique).post("${url}")
   protected val httpProtocol: HttpProtocolBuilder = http.basicAuth(
     conf.httpConfiguration.userName,
@@ -54,11 +74,15 @@
     replaceProperty(term, term, in)
   }
 
+  private def replaceProperty(term: String, default: Any): String = {
+    replaceProperty(term, default, term.toUpperCase)
+  }
+
   protected def replaceProperty(term: String, default: Any, in: String): String = {
     val property = pack + "." + term
     var value = default
     default match {
-      case _: String =>
+      case _: String | _: Double =>
         val propertyValue = Option(System.getProperty(property))
         if (propertyValue.nonEmpty) {
           value = propertyValue.get
@@ -84,8 +108,26 @@
    * override def replaceOverride(in: String): String = {
    * // Simple e.g., replaceProperty("EXTENSION_JSON_KEY", "default", in)
    * </pre>
+   *
+   * @param in which string to perform the replacements.
+   * @return the resulting String.
    */
   def replaceOverride(in: String): String = {
     in
   }
+
+  /**
+   * Meant to be optionally overridden by (heavier) scenarios.
+   * This is the relative runtime weight of the scenario class or type,
+   * compared to other scenarios' own runtime weights.
+   *
+   * The default weight or unit of weight is the pre-assigned value below.
+   * This default applies to any scenario class that is not overriding it
+   * with a greater, relative runtime weight value. Overriding scenarios
+   * happen to relatively require more run time than siblings, prior to
+   * being expected as completed.
+   *
+   * @return the relative runtime weight of this scenario as an Int.
+   */
+  def relativeRuntimeWeight = 1
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
index e0b3206d..74dc052 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
@@ -24,6 +24,8 @@
   private val data: FileBasedFeederBuilder[Any]#F#F = jsonFile(resource).convert(keys).circular
   private val default: String = name
 
+  override def relativeRuntimeWeight = 30
+
   override def replaceOverride(in: String): String = {
     replaceKeyWith("_project", default, in)
   }
@@ -36,22 +38,24 @@
 
   private val createProject = new CreateProject(default)
   private val deleteProject = new DeleteProject(default)
+  private val maxBeforeDelete: Int = maxExecutionTime - deleteProject.maxExecutionTime
 
   setUp(
     createProject.test.inject(
+      nothingFor(stepWaitTime(createProject) seconds),
       atOnceUsers(1)
     ),
     test.inject(
-      nothingFor(4 seconds),
+      nothingFor(stepWaitTime(this) seconds),
       atOnceUsers(10),
       rampUsers(10) during (5 seconds),
       constantUsersPerSec(20) during (15 seconds),
       constantUsersPerSec(20) during (15 seconds) randomized
     ),
     deleteProject.test.inject(
-      nothingFor(59 seconds),
+      nothingFor(maxBeforeDelete seconds),
       atOnceUsers(1)
     ),
   ).protocols(gitProtocol, httpProtocol)
-      .maxDuration(61 seconds)
+      .maxDuration(maxExecutionTime seconds)
 }