devserver: Tests for CherryPy zero-port extensions.

This adds both unit tests, as well as extending integration tests, to
ensure that our CherryPy extensions module (cherrypy_ext) works as
intended.

BUG=chromium:322436
TEST=Unit + integration tests pass.

Change-Id: I93221d11d855cd4d78d39e34e587b5b24e9eb4d1
Reviewed-on: https://chromium-review.googlesource.com/186848
Tested-by: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: Gilad Arnold <garnold@chromium.org>
diff --git a/devserver_integration_test.py b/devserver_integration_test.py
index 3dbddb6..755f2ce 100755
--- a/devserver_integration_test.py
+++ b/devserver_integration_test.py
@@ -26,6 +26,7 @@
 import psutil
 import shutil
 import signal
+import socket
 import subprocess
 import tempfile
 import time
@@ -66,6 +67,7 @@
 
 DEVSERVER_START_TIMEOUT = 15
 DEVSERVER_START_SLEEP = 1
+MAX_START_ATTEMPTS = 5
 
 
 class DevserverFailedToStart(Exception):
@@ -286,6 +288,32 @@
     self._StartServer()
 
 
+class DevserverStartTests(DevserverTestBase):
+  """Test that devserver starts up correctly."""
+
+  def testStartAnyPort(self):
+    """Starts the devserver, have it bind to an arbitrary available port."""
+    self._StartServer()
+
+  def testStartSpecificPort(self):
+    """Starts the devserver with a specific port."""
+    for _ in range(MAX_START_ATTEMPTS):
+      # This is a cheap hack to find an arbitrary unused port: we open a socket
+      # and bind it to port zero, then pull out the actual port number and
+      # close the socket. In all likelihood, this will leave us with an
+      # available port number that we can use for starting the devserver.
+      # However, this heuristic is susceptible to race conditions, hence the
+      # retry loop.
+      s = socket.socket()
+      s.bind(('', 0))
+      # s.getsockname() is definitely callable.
+      # pylint: disable=E1102
+      _, port = s.getsockname()
+      s.close()
+
+      self._StartServer(port=port)
+
+
 class DevserverBasicTests(AutoStartDevserverTestBase):
   """Short running tests for the devserver (no remote deps).