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 <[email protected]>
Reviewed-by: Chris Sosa <[email protected]>
Commit-Queue: Gilad Arnold <[email protected]>
diff --git a/cherrypy_ext_unittest.py b/cherrypy_ext_unittest.py
new file mode 100755
index 0000000..65a70fc
--- /dev/null
+++ b/cherrypy_ext_unittest.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for cherrypy_ext."""
+
+import cherrypy
+import tempfile
+import unittest
+
+import mox
+
+import cherrypy_ext
+
+
+class CherrypyExtTest(mox.MoxTestBase):
+ """Tests for the cherrypy_ext module."""
+
+ def testPortFile(self):
+ """Check that PortFile correctly reports a bound port."""
+ with tempfile.NamedTemporaryFile(delete=False) as f:
+ portfile = f.name
+ bus = self.mox.CreateMock(cherrypy.engine)
+ self.mox.StubOutWithMock(bus, 'log')
+ bus.log(mox.IsA(str)).MultipleTimes()
+
+ cherrypy.server = self.mox.CreateMock(object)
+ cherrypy.server.httpserver = self.mox.CreateMock(object)
+ cherrypy.server.httpserver.socket = self.mox.CreateMock(object)
+ cherrypy.server.httpserver.socket.getsockname = None
+ self.mox.StubOutWithMock(cherrypy.server.httpserver.socket, 'getsockname')
+ cherrypy.server.httpserver.socket.getsockname().AndReturn(None)
+ cherrypy.server.httpserver.socket.getsockname().AndReturn(('', 55555))
+
+ self.mox.ReplayAll()
+
+ plugin = cherrypy_ext.PortFile(bus, portfile)
+ plugin.start() # Signal server start; no socket binding yet.
+ with open(portfile) as f:
+ self.assertEqual('', f.read())
+ plugin.log('foo', 1) # Emit a log signal; socket "bound" at this point.
+ with open(portfile) as f:
+ self.assertEqual('55555', f.read())
+
+ self.mox.VerifyAll()
+
+ def testZeroPortPatcherSuccess(self):
+ """Make sure that ZeroPatcher successfully patches CherryPy.
+
+ This merely ensures that the patcher applies cleanly to the CherryPy
+ version available to the test environment, giving us some assurance that
+ it's still compatible with the range of versions that we might be using it
+ with. The actual testing of the arbitrary port binding feature is covered
+ by integration tests.
+ """
+ self.assertIsNone(cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy))
+
+ def testZeroPortPatcherFailure(self):
+ """Make sure that ZeroPatcher fails with an incompatible CherryPy.
+
+ This ensures that the patcher fails when applied to a CherryPy version that
+ does not have the desired properties.
+ """
+ module = cherrypy.process.servers
+ func_name = 'wait_for_free_port'
+ orig_func = getattr(module, func_name, None)
+ self.assertTrue(orig_func)
+ delattr(module, func_name)
+ self.assertRaises(AttributeError, cherrypy_ext.ZeroPortPatcher.DoPatch,
+ cherrypy)
+ setattr(module, func_name, orig_func)
+
+
+if __name__ == '__main__':
+ unittest.main()
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).