Delete hourly limits.  Change limits to defaultLimits.

Add limit override.  Override local config.  Simplify throttle test, use new limits, and fix various bugs.
diff --git a/tests/src/limits/ThrottleTests.scala b/tests/src/limits/ThrottleTests.scala
index 47ca75f..59bd4b8 100644
--- a/tests/src/limits/ThrottleTests.scala
+++ b/tests/src/limits/ThrottleTests.scala
@@ -21,6 +21,7 @@
 import scala.concurrent.Future
 import scala.concurrent.Promise
 import scala.concurrent.duration._
+import scala.util.Try
 
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
@@ -57,9 +58,26 @@
     val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
 
     val throttleWindow = 1.minute
-    val maximumInvokesPerMinute = WhiskProperties.getProperty("limits.actions.invokes.perMinute").toInt
-    val maximumFiringsPerMinute = WhiskProperties.getProperty("limits.triggers.fires.perMinute").toInt
-    val maximumConcurrentInvokes = WhiskProperties.getProperty("limits.actions.invokes.concurrent").toInt
+
+    val maximumInvokesPerMinute = getLimit("defaultLimits.actions.invokes.perMinute", "limits.actions.invokes.perMinute")
+    val maximumFiringsPerMinute = getLimit("defaultLimits.triggers.fires.perMinute", "limits.triggers.fires.perMinute")
+    val maximumConcurrentInvokes = getLimit("defaultLimits.actions.invokes.concurrent", "limits.actions.invokes.concurrent")
+
+    println(s"maximumInvokesPerMinute  = $maximumInvokesPerMinute")
+    println(s"maximumFiringsPerMinute  = $maximumFiringsPerMinute")
+    println(s"maximumConcurrentInvokes = $maximumConcurrentInvokes")
+
+    val rateMessage = "Too many requests from user"
+    val concurrencyMessage = "The user has sent too many requests in a given amount of time."
+
+    /*
+     * Retrieve a numeric limit for the key from the property set.  If the overrideKey is present, use that.
+     */
+    def getLimit(key: String, overrideKey: String) = Try {
+        WhiskProperties.getProperty(overrideKey).toInt
+    } getOrElse {
+        WhiskProperties.getProperty(key).toInt
+    }
 
     /**
      * Extracts the number of throttled results from a sequence of <code>RunResult</code>
@@ -114,6 +132,7 @@
         val results = List.fill(count)(Future {
             if (!p.isCompleted) {
                 val rr = run()
+                println(s"exitCode = ${rr.exitCode}   stderr = ${rr.stderr}")
                 if (rr.exitCode == THROTTLED) p.trySuccess(())
                 Some(rr)
             } else {
@@ -132,10 +151,19 @@
                 (action, _) => action.create(name, defaultAction)
             }
 
-            // invokes per minute * 2 because the current minute could advance which resets the throttle
-            val results = untilThrottled(maximumInvokesPerMinute * 2 + 1) { () =>
-                wsk.action.invoke(name, Map("payload" -> "testWord".toJson), expectedExitCode = DONTCARE_EXIT)
-            }
+            // Two things to be careful of:
+            //   1) We do not know the minute boundary so we perform twice max so that it will trigger no matter where they fall
+            //   2) We cannot issue too quickly or else the concurrency throttle will be triggered
+            val totalInvokes = 2 * maximumInvokesPerMinute
+            val numGroups = (totalInvokes / maximumConcurrentInvokes) + 1
+            val invokesPerGroup = (totalInvokes / numGroups) + 1
+            val interGroupSleep = 5.seconds
+            val results = (1 to numGroups).map { i =>
+                if (i != 1) { Thread.sleep(interGroupSleep.toMillis) }
+                untilThrottled(invokesPerGroup) { () =>
+                    wsk.action.invoke(name, Map("payload" -> "testWord".toJson), expectedExitCode = DONTCARE_EXIT)
+                }
+            }.flatten.toList
             val afterInvokes = Instant.now
 
             waitForActivations(results.par)
@@ -174,34 +202,40 @@
                 (action, _) => action.create(name, timeoutAction)
             }
 
-            val slowInvokes = maximumConcurrentInvokes * 0.6
-            val fastInvokes = maximumConcurrentInvokes * 0.4 + 1
+            // The sleep is necessary as the load balancer currently has a latency before recognizing concurency.
+            val sleep = 10.seconds
+            val slowInvokes = maximumConcurrentInvokes
+            val fastInvokes = 1
+            val fastInvokeDuration = 3.seconds
+            val slowInvokeDuration = sleep + fastInvokeDuration
 
-            // Keep queue from draining with these
-            val slowResults = untilThrottled(slowInvokes.toInt) { () =>
-                wsk.action.invoke(name, Map("payload" -> 15.seconds.toMillis.toJson), expectedExitCode = DONTCARE_EXIT)
+            // These invokes will stay active long enough that all are issued and load balancer has recognized concurrency.
+            val startSlowInvokes = Instant.now
+            val slowResults = untilThrottled(slowInvokes) { () =>
+                wsk.action.invoke(name, Map("payload" -> slowInvokeDuration.toMillis.toJson), expectedExitCode = DONTCARE_EXIT)
             }
+            val afterSlowInvokes = Instant.now
+            val slowIssueDuration = durationBetween(startSlowInvokes, afterSlowInvokes)
+            println(s"$slowInvokes slow invokes (dur = ${slowInvokeDuration.toSeconds} sec) took ${slowIssueDuration.toSeconds} seconds to issue")
 
-            // Create queue length quickly, drain fast
-            val fastResults = untilThrottled(fastInvokes.toInt) { () =>
-                wsk.action.invoke(name, Map("payload" -> 10.milliseconds.toMillis.toJson), expectedExitCode = DONTCARE_EXIT)
+            // Sleep to let the background thread get the newest values (refreshes every 2 seconds)
+            println(s"Sleeping for ${sleep.toSeconds} sec")
+            Thread.sleep(sleep.toMillis)
+
+            // These fast invokes will trigger the concurrency-based throttling.
+            val startFastInvokes = Instant.now
+            val fastResults = untilThrottled(fastInvokes) { () =>
+                wsk.action.invoke(name, Map("payload" -> slowInvokeDuration.toMillis.toJson), expectedExitCode = DONTCARE_EXIT)
             }
+            val afterFastInvokes = Instant.now
+            val fastIssueDuration = durationBetween(afterFastInvokes, startFastInvokes)
+            println(s"$fastInvokes fast invokes (dur = ${fastInvokeDuration.toSeconds} sec) took ${fastIssueDuration.toSeconds} seconds to issue")
 
-            // Sleep 5 seconds to let the background thread get the newest values (refreshes every 2 seconds)
-            Thread.sleep(5.seconds.toMillis)
-
-            // start 1 invoke less than the maximum per minute to avoid getting rate throttled
-            val throttledInvokes = maximumInvokesPerMinute - slowInvokes.toInt - fastInvokes.toInt - 1
-            val endResults = untilThrottled(throttledInvokes) { () =>
-                wsk.action.invoke(name, Map("payload" -> 10.milliseconds.toMillis.toJson), expectedExitCode = DONTCARE_EXIT)
-            }
-            val afterInvokes = Instant.now
-
-            val combinedResults = slowResults ++ fastResults ++ endResults
+            val combinedResults = slowResults ++ fastResults
             waitForActivations(combinedResults.par)
             throttledActivations(combinedResults, tooManyConcurrentRequests) should be > 0
 
-            val alreadyWaited = durationBetween(afterInvokes, Instant.now)
+            val alreadyWaited = durationBetween(afterSlowInvokes, Instant.now)
             settleThrottles(alreadyWaited)
     }
 }
diff --git a/tests/src/whisk/core/WhiskConfigTests.scala b/tests/src/whisk/core/WhiskConfigTests.scala
index 365592c..bd8d9b1 100644
--- a/tests/src/whisk/core/WhiskConfigTests.scala
+++ b/tests/src/whisk/core/WhiskConfigTests.scala
@@ -37,7 +37,7 @@
         bw.write("a=A")
         bw.close()
 
-        val config = new WhiskConfig(Map("a" -> null), file)
+        val config = new WhiskConfig(Map("a" -> null), Set(), file)
         assert(config.isValid && config("a") == "A")
     }
 
@@ -49,7 +49,7 @@
         bw.write("a=A")
         bw.close()
 
-        val config = new WhiskConfig(Map("a" -> null, "b" -> null), file)
+        val config = new WhiskConfig(Map("a" -> null, "b" -> null), Set(), file)
         assert(!config.isValid && config("b") == null)
     }
 }