Merge changes I944f73e9,Ib4971b20,I9d5fdf99 into stable-2.9

* changes:
  tests: Use insecure random for acceptance tests
  Acceptance-tests: Fix 'Address already in use' bug
  Acceptance-tests: Fix 'verify: false' bug
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index db36732..c6faac5 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -469,6 +469,45 @@
   EOF
 ----
 
+== Rerun unit tests
+
+If for some reasons tests, that were already run must be repeated, unit test
+cache must be removed fist. That's because the test execution results are
+cached by Buck:
+
+----
+  $ rm -rf buck-out/bin/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/.AddRemoveGroupMembersIT/
+----
+
+After clearing the cache test can be rerun again:
+
+----
+  $ buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group:AddRemoveGroupMembersIT
+  TESTING //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group:AddRemoveGroupMembersIT
+  PASS  14,9s  8 Passed   0 Failed   com.google.gerrit.acceptance.rest.group.AddRemoveGroupMembersIT
+  TESTS PASSED
+----
+
+An alternative approach is to use a Buck feature:
+--test-selectors (-filters, -f) option:
+
+----
+  buck test --all -f 'com.google.gerrit.acceptance.rest.change.SubmitByMergeAlwaysIT'
+  TESTING SELECTED TESTS
+  PASS  14,5s  6 Passed   0 Failed   com.google.gerrit.acceptance.rest.change.SubmitByMergeAlwaysIT
+  TESTS PASSED
+----
+
+When this option is used, cache is disabled per design and doesn't need to be deleted.
+Note: when -f option is used, the whole unit test cache is dropped. As a consequence,
+repeating the
+
+----
+buck test --all
+----
+
+would re-execute all tests again.
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index a8ce229..cbf2685 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -36,7 +36,6 @@
 import java.lang.reflect.Field;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
-import java.net.ServerSocket;
 import java.net.URI;
 import java.net.UnknownHostException;
 import java.util.concurrent.BrokenBarrierException;
@@ -123,23 +122,18 @@
 
   private static void mergeTestConfig(Config cfg)
       throws IOException {
-    InetSocketAddress http = newPort();
-    InetSocketAddress sshd = newPort();
-    String url = "http://" + format(http) + "/";
-
+    String forceEphemeralPort = String.format("%s:0",
+        getLocalHost().getHostName());
+    String url = "http://" + forceEphemeralPort + "/";
     cfg.setString("gerrit", null, "canonicalWebUrl", url);
     cfg.setString("httpd", null, "listenUrl", url);
-    cfg.setString("sshd", null, "listenAddress", format(sshd));
+    cfg.setString("sshd", null, "listenAddress", forceEphemeralPort);
     cfg.setString("cache", null, "directory", null);
     cfg.setBoolean("sendemail", null, "enable", false);
     cfg.setInt("cache", "projects", "checkFrequency", 0);
     cfg.setInt("plugins", null, "checkFrequency", 0);
   }
 
-  private static String format(InetSocketAddress s) {
-    return String.format("%s:%d", s.getAddress().getHostAddress(), s.getPort());
-  }
-
   private static Injector createTestInjector(Daemon daemon) throws Exception {
     Injector sysInjector = get(daemon, "sysInjector");
     Module module = new FactoryModule() {
@@ -160,15 +154,6 @@
     return (T) f.get(obj);
   }
 
-  private static final InetSocketAddress newPort() throws IOException {
-    ServerSocket s = new ServerSocket(0, 0, getLocalHost());
-    try {
-      return (InetSocketAddress) s.getLocalSocketAddress();
-    } finally {
-      s.close();
-    }
-  }
-
   private static InetAddress getLocalHost() throws UnknownHostException {
     return InetAddress.getLoopbackAddress();
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java
new file mode 100644
index 0000000..9bbc125
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/JschVerifyFalseBugIT.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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 com.google.gerrit.acceptance.ssh;
+
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+@NoHttpd
+// To see this test failing with 'verify: false', at least in the Jcsh 0.1.51
+// remove bouncycastle libs from the classpath, and run:
+// buck test //gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh:JschVerifyFalseBugIT
+public class JschVerifyFalseBugIT extends AbstractDaemonTest {
+
+  @Test
+  @Ignore// we know it works now, so let's not clone a project 500 times ;-)
+  public void test() throws Exception {
+    test(5);
+  }
+
+  private void test(int threads) throws InterruptedException,
+      ExecutionException {
+    Callable<Void> task = new Callable<Void>() {
+      @Override
+      public Void call() throws Exception {
+        for (int i = 1; i < 100; i++) {
+          String p = "p" + i;
+          createProject(sshSession, p);
+          cloneProject(sshSession.getUrl() + "/" + p);
+        }
+        return null;
+      }
+    };
+    List<Callable<Void>> nCopies = Collections.nCopies(threads, task);
+    List<Future<Void>> futures = Executors.newFixedThreadPool(threads)
+        .invokeAll(nCopies);
+    for (Future<Void> future : futures) {
+      future.get();
+    }
+    Assert.assertEquals(threads, futures.size());
+  }
+}
diff --git a/gerrit-acceptance-tests/tests.defs b/gerrit-acceptance-tests/tests.defs
index 3c8b4d5..0131887 100644
--- a/gerrit-acceptance-tests/tests.defs
+++ b/gerrit-acceptance-tests/tests.defs
@@ -1,7 +1,19 @@
+# these need as workaround for the 'verify: false' bug in Jcraft SSH library
+BOUNCYCASTLE = [
+  '//lib/bouncycastle:bcpkix',
+  '//lib/bouncycastle:bcpg',
+]
+
 def acceptance_tests(
     srcs,
     deps = [],
     vm_args = ['-Xmx256m']):
+  from os import environ, path
+  if not environ.get('NO_BOUNCYCASTLE'):
+    deps = BOUNCYCASTLE + deps
+  if path.exists('/dev/urandom'):
+    vm_args = vm_args + ['-Djava.security.egd=file:/dev/./urandom']
+
   for j in srcs:
     java_test(
       name = j[:-len('.java')],
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 6ba54c9..067eeab 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -19,6 +19,7 @@
 
 import com.google.common.base.Charsets;
 import com.google.common.base.Objects;
+import com.google.common.base.Strings;
 import com.google.common.escape.Escaper;
 import com.google.common.html.HtmlEscapers;
 import com.google.common.io.ByteStreams;
@@ -103,16 +104,33 @@
 
   static class Lifecycle implements LifecycleListener {
     private final JettyServer server;
+    private final Config cfg;
 
     @Inject
-    Lifecycle(final JettyServer server) {
+    Lifecycle(final JettyServer server, @GerritServerConfig final Config cfg) {
       this.server = server;
+      this.cfg = cfg;
     }
 
     @Override
     public void start() {
       try {
+        String origUrl = cfg.getString("httpd", null, "listenUrl");
+        boolean rewrite = !Strings.isNullOrEmpty(origUrl)
+            && origUrl.endsWith(":0/");
         server.httpd.start();
+        if (rewrite) {
+          Connector con = server.httpd.getConnectors()[0];
+          if (con instanceof ServerConnector) {
+            @SuppressWarnings("resource")
+            ServerConnector serverCon = (ServerConnector)con;
+            String host = serverCon.getHost();
+            int port = serverCon.getLocalPort();
+            String url = String.format("http://%s:%d", host, port);
+            cfg.setString("gerrit", null, "canonicalWebUrl", url);
+            cfg.setString("httpd", null, "listenUrl", url);
+          }
+        }
       } catch (Exception e) {
         throw new IllegalStateException("Cannot start HTTP daemon", e);
       }
@@ -259,7 +277,7 @@
         } else {
           final URI r = u.parseServerAuthority();
           c.setHost(r.getHost());
-          c.setPort(0 < r.getPort() ? r.getPort() : defaultPort);
+          c.setPort(0 <= r.getPort() ? r.getPort() : defaultPort);
         }
       } catch (URISyntaxException e) {
         throw new IllegalArgumentException("Invalid httpd.listenurl " + u, e);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 5338c16..f507920 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -18,6 +18,8 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.extensions.events.LifecycleListener;
@@ -51,11 +53,11 @@
 import org.apache.sshd.common.cipher.AES192CBC;
 import org.apache.sshd.common.cipher.AES256CBC;
 import org.apache.sshd.common.cipher.AES256CTR;
+import org.apache.sshd.common.cipher.ARCFOUR128;
+import org.apache.sshd.common.cipher.ARCFOUR256;
 import org.apache.sshd.common.cipher.BlowfishCBC;
 import org.apache.sshd.common.cipher.CipherNone;
 import org.apache.sshd.common.cipher.TripleDESCBC;
-import org.apache.sshd.common.cipher.ARCFOUR128;
-import org.apache.sshd.common.cipher.ARCFOUR256;
 import org.apache.sshd.common.compression.CompressionNone;
 import org.apache.sshd.common.file.FileSystemFactory;
 import org.apache.sshd.common.file.FileSystemView;
@@ -100,6 +102,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.UnknownHostException;
 import java.security.InvalidKeyException;
@@ -144,6 +147,7 @@
   private final boolean keepAlive;
   private final List<HostKey> hostKeys;
   private volatile IoAcceptor acceptor;
+  private final Config cfg;
 
   @Inject
   SshDaemon(final CommandFactory commandFactory, final NoShell noShell,
@@ -155,6 +159,7 @@
       @SshAdvertisedAddresses final List<String> advertised) {
     setPort(IANA_SSH_PORT /* never used */);
 
+    this.cfg = cfg;
     this.listen = listen;
     this.advertised = advertised;
     keepAlive = cfg.getBoolean("sshd", "tcpkeepalive", true);
@@ -276,7 +281,14 @@
       acceptor = createAcceptor();
 
       try {
+        String listenAddress = cfg.getString("sshd", null, "listenAddress");
+        boolean rewrite = !Strings.isNullOrEmpty(listenAddress)
+            && listenAddress.endsWith(":0");
         acceptor.bind(listen);
+        if (rewrite) {
+          SocketAddress bound = Iterables.getOnlyElement(acceptor.getBoundAddresses());
+          cfg.setString("sshd", null, "listenAddress", format((InetSocketAddress)bound));
+        }
       } catch (IOException e) {
         throw new IllegalStateException("Cannot bind to " + addressList(), e);
       }
@@ -286,6 +298,10 @@
     }
   }
 
+  private static String format(InetSocketAddress s) {
+    return String.format("%s:%d", s.getAddress().getHostAddress(), s.getPort());
+  }
+
   @Override
   public synchronized void stop() {
     if (acceptor != null) {