Repository: buildbot/buildbot

Issue:
buildbot 2.0.0 wheel is installed in python 2
buildbot 2.0.0 does not support python 2, but it is installed when using python 2. The wheel appears to declare support for py2: `buildbot-2.0.0-py2.py3-none-any.whl`. But the installed code is incompatible with python 2.

```sh-session
[aw@jello server] $ virtualenv /tmp/env
New python executable in /tmp/env/bin/python
Installing setuptools, pip, wheel...
done.
[aw@jello server] $ /tmp/env/bin/pip install buildbot
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.
Collecting buildbot
  Downloading https://files.pythonhosted.org/packages/36/b7/47a8f7f6dacd3580bc51c04e608a1c85e80e1e3e53141c8c804924065de8/buildbot-2.0.0-py2.py3-none-any.whl (779kB)
    100% |████████████████████████████████| 788kB 6.6MB/s
Collecting txaio>=2.2.2 (from buildbot)
  Using cached https://files.pythonhosted.org/packages/e9/6d/e1a6f7835cde86728e5bb1f577be9b2d7d273fdb33c286e70b087d418ded/txaio-18.8.1-py2.py3-none-any.whl
Collecting Twisted>=17.9.0 (from buildbot)
Collecting autobahn>=0.16.0 (from buildbot)
  Downloading https://files.pythonhosted.org/packages/3d/d9/28aa58047558351eea29dafb8ff813af72e39ac35ade686394ca856914a7/autobahn-19.1.1-py2.py3-none-any.whl (302kB)
    100% |████████████████████████████████| 307kB 8.1MB/s
Collecting future (from buildbot)
Collecting pyyaml (from buildbot)
Collecting PyJWT (from buildbot)
  Using cached https://files.pythonhosted.org/packages/87/8b/6a9f14b5f781697e51259d81657e6048fd31a113229cf346880bb7545565/PyJWT-1.7.1-py2.py3-none-any.whl
Collecting zope.interface>=4.1.1 (from buildbot)
  Using cached https://files.pythonhosted.org/packages/28/39/0b5a168621fadd822810e8f3e2b9502b279aca07d6911ad06495ac9dff97/zope.interface-4.6.0-cp27-cp27m-macosx_10_6_intel.whl
Requirement already satisfied: setuptools>=8.0 in /private/tmp/env/lib/python2.7/site-packages (from buildbot) (40.8.0)
Collecting python-dateutil>=1.5 (from buildbot)
  Downloading https://files.pythonhosted.org/packages/41/17/c62faccbfbd163c7f57f3844689e3a78bae1f403648a6afb1d0866d87fbb/python_dateutil-2.8.0-py2.py3-none-any.whl (226kB)
    100% |████████████████████████████████| 235kB 9.8MB/s
Collecting Jinja2>=2.1 (from buildbot)
  Using cached https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl
Collecting sqlalchemy-migrate>=0.9 (from buildbot)
  Downloading https://files.pythonhosted.org/packages/b5/99/92c4c90d5a22aac19554effd04789b78b5549c62fefbda1f42cc549b31b1/sqlalchemy_migrate-0.12.0-py2.py3-none-any.whl (108kB)
    100% |████████████████████████████████| 112kB 11.1MB/s
Collecting sqlalchemy>=1.1.0 (from buildbot)
  Downloading https://files.pythonhosted.org/packages/c6/52/73d1c92944cd294a5b165097038418abb6a235f5956d43d06f97254f73bf/SQLAlchemy-1.2.17.tar.gz (5.7MB)
    100% |████████████████████████████████| 5.7MB 1.4MB/s
Collecting six (from txaio>=2.2.2->buildbot)
  Using cached https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl
Collecting attrs>=17.4.0 (from Twisted>=17.9.0->buildbot)
  Using cached https://files.pythonhosted.org/packages/3a/e1/5f9023cc983f1a628a8c2fd051ad19e76ff7b142a0faf329336f9a62a514/attrs-18.2.0-py2.py3-none-any.whl
Collecting constantly>=15.1 (from Twisted>=17.9.0->buildbot)
  Using cached https://files.pythonhosted.org/packages/b9/65/48c1909d0c0aeae6c10213340ce682db01b48ea900a7d9fce7a7910ff318/constantly-15.1.0-py2.py3-none-any.whl
Collecting incremental>=16.10.1 (from Twisted>=17.9.0->buildbot)
  Using cached https://files.pythonhosted.org/packages/f5/1d/c98a587dc06e107115cf4a58b49de20b19222c83d75335a192052af4c4b7/incremental-17.5.0-py2.py3-none-any.whl
Collecting hyperlink>=17.1.1 (from Twisted>=17.9.0->buildbot)
  Using cached https://files.pythonhosted.org/packages/a7/b6/84d0c863ff81e8e7de87cff3bd8fd8f1054c227ce09af1b679a8b17a9274/hyperlink-18.0.0-py2.py3-none-any.whl
Collecting Automat>=0.3.0 (from Twisted>=17.9.0->buildbot)
  Using cached https://files.pythonhosted.org/packages/a3/86/14c16bb98a5a3542ed8fed5d74fb064a902de3bdd98d6584b34553353c45/Automat-0.7.0-py2.py3-none-any.whl
Collecting PyHamcrest>=1.9.0 (from Twisted>=17.9.0->buildbot)
  Using cached https://files.pythonhosted.org/packages/9a/d5/d37fd731b7d0e91afcc84577edeccf4638b4f9b82f5ffe2f8b62e2ddc609/PyHamcrest-1.9.0-py2.py3-none-any.whl
Collecting MarkupSafe>=0.23 (from Jinja2>=2.1->buildbot)
  Using cached https://files.pythonhosted.org/packages/cd/52/927263d9cf66a12e05c5caef43ee203bd92355e9a321552d2b8c4aee5f1e/MarkupSafe-1.1.0-cp27-cp27m-macosx_10_6_intel.whl
Collecting pbr>=1.8 (from sqlalchemy-migrate>=0.9->buildbot)
  Downloading https://files.pythonhosted.org/packages/8c/7f/fed53b379500fd889707d1f6e61c2a35e12f2de87396894aff89b017d1d6/pbr-5.1.2-py2.py3-none-any.whl (107kB)
    100% |████████████████████████████████| 112kB 9.6MB/s
Collecting sqlparse (from sqlalchemy-migrate>=0.9->buildbot)
  Using cached https://files.pythonhosted.org/packages/65/85/20bdd72f4537cf2c4d5d005368d502b2f464ede22982e724a82c86268eda/sqlparse-0.2.4-py2.py3-none-any.whl
Collecting Tempita>=0.4 (from sqlalchemy-migrate>=0.9->buildbot)
Collecting decorator (from sqlalchemy-migrate>=0.9->buildbot)
  Downloading https://files.pythonhosted.org/packages/f1/cd/7c8240007e9716b14679bc217a1baefa4432aa30394f7e2ec40a52b1a708/decorator-4.3.2-py2.py3-none-any.whl
Collecting idna>=2.5 (from hyperlink>=17.1.1->Twisted>=17.9.0->buildbot)
  Downloading https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl (58kB)
    100% |████████████████████████████████| 61kB 7.5MB/s
Building wheels for collected packages: sqlalchemy
  Building wheel for sqlalchemy (setup.py) ... done
  Stored in directory: /Users/aw/Library/Caches/pip/wheels/b3/94/e4/e556159f8d26575bfa3cf64cff277b9112f1ad8a50cc438bbb
Successfully built sqlalchemy
Installing collected packages: six, txaio, attrs, constantly, incremental, idna, hyperlink, Automat, zope.interface, PyHamcrest, Twisted, autobahn, future, pyyaml, PyJWT, python-dateutil, MarkupSafe, Jinja2, sqlalchemy, pbr, sqlparse, Tempita, decorator, sqlalchemy-migrate, buildbot
Successfully installed Automat-0.7.0 Jinja2-2.10 MarkupSafe-1.1.0 PyHamcrest-1.9.0 PyJWT-1.7.1 Tempita-0.5.2 Twisted-18.9.0 attrs-18.2.0 autobahn-19.1.1 buildbot-2.0.0 constantly-15.1.0 decorator-4.3.2 future-0.17.1 hyperlink-18.0.0 idna-2.8 incremental-17.5.0 pbr-5.1.2 python-dateutil-2.8.0 pyyaml-3.13 six-1.12.0 sqlalchemy-1.2.17 sqlalchemy-migrate-0.12.0 sqlparse-0.2.4 txaio-18.8.1 zope.interface-4.6.0

[aw@jello server] $ /tmp/env/bin/buildbot
Traceback (most recent call last):
  File "/tmp/env/bin/buildbot", line 6, in <module>
    from buildbot.scripts.runner import run
  File "/tmp/env/lib/python2.7/site-packages/buildbot/scripts/runner.py", line 31, in <module>
    from buildbot.scripts import base
  File "/tmp/env/lib/python2.7/site-packages/buildbot/scripts/base.py", line 37
    return 1
SyntaxError: 'return' with argument inside generator
```


Discussion:

Resolving patch:
diff --git a/master/buildbot/scripts/upgrade_master.py b/master/buildbot/scripts/upgrade_master.py
--- a/master/buildbot/scripts/upgrade_master.py
+++ b/master/buildbot/scripts/upgrade_master.py
@@ -75,6 +75,7 @@ def upgradeDatabase(config, master_cfg):
     if not config['quiet']:
         print("upgrading database (%s)"
               % (stripUrlPassword(master_cfg.db['db_url'])))
+        print("Warning: Stopping this process might cause data loss")
 
     master = BuildMaster(config['basedir'])
     master.config = master_cfg
diff --git a/master/buildbot/worker/base.py b/master/buildbot/worker/base.py
--- a/master/buildbot/worker/base.py
+++ b/master/buildbot/worker/base.py
@@ -357,6 +357,8 @@ def updateWorker(self):
     def attached(self, conn):
         """This is called when the worker connects."""
 
+        assert self.conn is None
+
         metrics.MetricCountEvent.log("AbstractWorker.attached_workers", 1)
 
         # now we go through a sequence of calls, gathering information, then
@@ -412,6 +414,11 @@ def messageReceivedFromWorker(self):
 
     @defer.inlineCallbacks
     def detached(self):
+        # protect against race conditions in conn disconnect path and someone
+        # calling detached directly. At the moment the null worker does that.
+        if self.conn is None:
+            return
+
         metrics.MetricCountEvent.log("AbstractWorker.attached_workers", -1)
         self.conn = None
         self._old_builder_list = []
diff --git a/master/buildbot/www/plugin.py b/master/buildbot/www/plugin.py
--- a/master/buildbot/www/plugin.py
+++ b/master/buildbot/www/plugin.py
@@ -26,10 +26,10 @@ class Application(object):
     def __init__(self, modulename, description, ui=True):
         self.description = description
         self.version = pkg_resources.resource_string(
-            modulename, "/VERSION").strip()
+            modulename, "VERSION").strip()
         self.version = bytes2unicode(self.version)
         self.static_dir = pkg_resources.resource_filename(
-            modulename, "/static")
+            modulename, "static")
         self.resource = static.File(self.static_dir)
         self.ui = ui
 


Test patch:
diff --git a/master/buildbot/test/fake/latent.py b/master/buildbot/test/fake/latent.py
--- a/master/buildbot/test/fake/latent.py
+++ b/master/buildbot/test/fake/latent.py
@@ -70,13 +70,15 @@ def do_start_instance(self):
         if self.auto_connect_worker:
             self.connect_worker()
 
+    @defer.inlineCallbacks
     def auto_stop(self, result):
         self.auto_stop_flag = result
         if self.auto_stop_flag and self.stopping:
-            self.stop_instance(True)
+            yield self.stop_instance(True)
 
+    @defer.inlineCallbacks
     def stop_instance(self, result):
-        self.do_stop_instance()
+        yield self.do_stop_instance()
         d, self._stop_deferred = self._stop_deferred, None
         d.callback(result)
 
@@ -84,7 +86,7 @@ def do_stop_instance(self):
         assert self.stopping
         self.stopping = False
         self._started_kind = None
-        self.disconnect_worker()
+        return self.disconnect_worker()
 
     def connect_worker(self):
         if self.remote_worker is not None:
@@ -165,6 +167,7 @@ def start_instance(self, build):
         self._controller._start_deferred = defer.Deferred()
         return self._controller._start_deferred
 
+    @defer.inlineCallbacks
     def stop_instance(self, build):
         assert not self._controller.stopping
         # TODO: we get duplicate stop_instance sometimes, this might indicate
@@ -175,7 +178,7 @@ def stop_instance(self, build):
         self._controller.starting = False
         self._controller.stopping = True
         if self._controller.auto_stop_flag:
-            self._controller.do_stop_instance()
-            return defer.succeed(True)
+            yield self._controller.do_stop_instance()
+            return True
         self._controller._stop_deferred = defer.Deferred()
-        return self._controller._stop_deferred
+        return (yield self._controller._stop_deferred)
diff --git a/master/buildbot/test/fake/reactor.py b/master/buildbot/test/fake/reactor.py
--- a/master/buildbot/test/fake/reactor.py
+++ b/master/buildbot/test/fake/reactor.py
@@ -21,11 +21,13 @@
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 
+from twisted.internet import defer
 from twisted.internet import reactor
 from twisted.internet.base import _ThreePhaseEvent
 from twisted.internet.interfaces import IReactorCore
 from twisted.internet.interfaces import IReactorThreads
 from twisted.internet.task import Clock
+from twisted.python import log
 from twisted.python.failure import Failure
 from zope.interface import implementer
 
@@ -132,6 +134,17 @@ def _executeCurrentDelayedCalls(self):
 
         self._pendingCurrentCalls = False
 
+    @defer.inlineCallbacks
+    def _catchPrintExceptions(self, what, *a, **kw):
+        try:
+            r = what(*a, **kw)
+            if isinstance(r, defer.Deferred):
+                yield r
+        except Exception as e:
+            log.msg('Unhandled exception from deferred when doing '
+                    'TestReactor.advance()', e)
+            raise
+
     def callLater(self, when, what, *a, **kw):
         # Buildbot often uses callLater(0, ...) to defer execution of certain
         # code to the next iteration of the reactor. This means that often
@@ -141,10 +154,14 @@ def callLater(self, when, what, *a, **kw):
         # chance to advance the test reactor whenever we detect that there
         # are callbacks that should run in the next iteration of the test
         # reactor.
+        #
+        # Additionally, we wrap all calls with a function that prints any
+        # unhandled exceptions
         if when <= 0 and not self._pendingCurrentCalls:
             reactor.callLater(0, self._executeCurrentDelayedCalls)
 
-        return Clock.callLater(self, when, what, *a, **kw)
+        return Clock.callLater(self, when, self._catchPrintExceptions,
+                               what, *a, **kw)
 
     def stop(self):
         # first fire pending calls until the current time. Note that the real
diff --git a/master/buildbot/test/integration/test_worker.py b/master/buildbot/test/integration/test_worker.py
--- a/master/buildbot/test/integration/test_worker.py
+++ b/master/buildbot/test/integration/test_worker.py
@@ -14,8 +14,8 @@
 # Copyright Buildbot Team Members
 
 
-from twisted.internet.defer import Deferred
-from twisted.trial.unittest import SynchronousTestCase
+from twisted.internet import defer
+from twisted.trial.unittest import TestCase
 from zope.interface import implementer
 
 from buildbot.config import BuilderConfig
@@ -42,7 +42,7 @@ def __init__(self, **kwargs):
         self.steps = []
 
     def buildStep(self):
-        step_deferred = Deferred()
+        step_deferred = defer.Deferred()
         step = ControllableStep(step_deferred, **self.kwargs)
         self.steps.append((step, step_deferred))
         return step
@@ -61,7 +61,7 @@ def interrupt(self, reason):
         self._step_deferred.callback(CANCELLED)
 
 
-class Tests(SynchronousTestCase, TestReactorMixin):
+class Tests(TestCase, TestReactorMixin):
 
     def setUp(self):
         self.setUpTestReactor()
@@ -69,6 +69,7 @@ def setUp(self):
     def tearDown(self):
         self.assertFalse(self.master.running, "master is still running!")
 
+    @defer.inlineCallbacks
     def test_latent_max_builds(self):
         """
         If max_builds is set, only one build is started on a latent
@@ -91,31 +92,28 @@ def test_latent_max_builds(self):
             'protocols': {'null': {}},
             'multiMaster': True,
         }
-        self.master = master = self.successResultOf(
-            getMaster(self, self.reactor, config_dict))
+        self.master = master = yield getMaster(self, self.reactor, config_dict)
         builder_ids = [
-            self.successResultOf(master.data.updates.findBuilderId('testy-1')),
-            self.successResultOf(master.data.updates.findBuilderId('testy-2')),
+            (yield master.data.updates.findBuilderId('testy-1')),
+            (yield master.data.updates.findBuilderId('testy-2')),
         ]
 
         started_builds = []
-        self.successResultOf(master.mq.startConsuming(
+        yield master.mq.startConsuming(
             lambda key, build: started_builds.append(build),
-            ('builds', None, 'new')))
+            ('builds', None, 'new'))
 
         # Trigger a buildrequest
-        bsid, brids = self.successResultOf(
-            master.data.updates.addBuildset(
-                waited_for=False,
-                builderids=builder_ids,
-                sourcestamps=[
-                    {'codebase': '',
-                     'repository': '',
-                     'branch': None,
-                     'revision': None,
-                     'project': ''},
-                ],
-            )
+        bsid, brids = yield master.data.updates.addBuildset(
+            waited_for=False,
+            builderids=builder_ids,
+            sourcestamps=[
+                {'codebase': '',
+                 'repository': '',
+                 'branch': None,
+                 'revision': None,
+                 'project': ''},
+            ],
         )
 
         # The worker fails to substantiate.
@@ -124,8 +122,9 @@ def test_latent_max_builds(self):
         controller.connect_worker()
 
         self.assertEqual(len(started_builds), 1)
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
 
+    @defer.inlineCallbacks
     def test_local_worker_max_builds(self):
         """
         If max_builds is set, only one build is started on a worker
@@ -147,31 +146,28 @@ def test_local_worker_max_builds(self):
             'protocols': {'null': {}},
             'multiMaster': True,
         }
-        self.master = master = self.successResultOf(
-            getMaster(self, self.reactor, config_dict))
+        self.master = master = yield getMaster(self, self.reactor, config_dict)
         builder_ids = [
-            self.successResultOf(master.data.updates.findBuilderId('testy-1')),
-            self.successResultOf(master.data.updates.findBuilderId('testy-2')),
+            (yield master.data.updates.findBuilderId('testy-1')),
+            (yield master.data.updates.findBuilderId('testy-2')),
         ]
 
         started_builds = []
-        self.successResultOf(master.mq.startConsuming(
+        yield master.mq.startConsuming(
             lambda key, build: started_builds.append(build),
-            ('builds', None, 'new')))
+            ('builds', None, 'new'))
 
         # Trigger a buildrequest
-        bsid, brids = self.successResultOf(
-            master.data.updates.addBuildset(
-                waited_for=False,
-                builderids=builder_ids,
-                sourcestamps=[
-                    {'codebase': '',
-                     'repository': '',
-                     'branch': None,
-                     'revision': None,
-                     'project': ''},
-                ],
-            )
+        bsid, brids = yield master.data.updates.addBuildset(
+            waited_for=False,
+            builderids=builder_ids,
+            sourcestamps=[
+                {'codebase': '',
+                 'repository': '',
+                 'branch': None,
+                 'revision': None,
+                 'project': ''},
+            ],
         )
 
         self.assertEqual(len(started_builds), 1)
diff --git a/master/buildbot/test/integration/test_worker_latent.py b/master/buildbot/test/integration/test_worker_latent.py
--- a/master/buildbot/test/integration/test_worker_latent.py
+++ b/master/buildbot/test/integration/test_worker_latent.py
@@ -76,7 +76,10 @@ def createBuildrequest(self, master, builder_ids, properties=None):
         ))
 
     @defer.inlineCallbacks
-    def create_single_worker_config(self):
+    def create_single_worker_config(self, controller_kwargs=None):
+        if not controller_kwargs:
+            controller_kwargs = {}
+
         controller = LatentController(self, 'local')
         config_dict = {
             'builders': [
@@ -95,6 +98,30 @@ def create_single_worker_config(self):
 
         return controller, master, builder_id
 
+    @defer.inlineCallbacks
+    def create_single_worker_config_with_step(self, controller_kwargs=None):
+        if not controller_kwargs:
+            controller_kwargs = {}
+
+        controller = LatentController(self, 'local', **controller_kwargs)
+        stepcontroller = BuildStepController()
+        config_dict = {
+            'builders': [
+                BuilderConfig(name="testy",
+                              workernames=["local"],
+                              factory=BuildFactory([stepcontroller.step]),
+                              ),
+            ],
+            'workers': [controller.worker],
+            'protocols': {'null': {}},
+            # Disable checks about missing scheduler.
+            'multiMaster': True,
+        }
+        master = yield self.getMaster(config_dict)
+        builder_id = yield master.data.updates.findBuilderId('testy')
+
+        return controller, stepcontroller, master, builder_id
+
     @defer.inlineCallbacks
     def test_latent_workers_start_in_parallel(self):
         """
@@ -128,7 +155,7 @@ def test_latent_workers_start_in_parallel(self):
         self.assertEqual(controllers[1].starting, True)
         for controller in controllers:
             controller.start_instance(True)
-            controller.auto_stop(True)
+            yield controller.auto_stop(True)
 
     @defer.inlineCallbacks
     def test_refused_substantiations_get_requeued(self):
@@ -155,7 +182,7 @@ def test_refused_substantiations_get_requeued(self):
             set(brids),
             {req['buildrequestid'] for req in unclaimed_build_requests}
         )
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
         self.flushLoggedErrors(LatentWorkerFailedToSubstantiate)
 
     @defer.inlineCallbacks
@@ -186,7 +213,7 @@ def test_failed_substantiations_get_requeued(self):
             set(brids),
             {req['buildrequestid'] for req in unclaimed_build_requests}
         )
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
 
     @defer.inlineCallbacks
     def test_failed_substantiations_get_exception(self):
@@ -209,7 +236,7 @@ def test_failed_substantiations_get_exception(self):
 
         # When the substantiation fails, the result is an exception.
         self.assertEqual(EXCEPTION, dbdict['results'])
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
 
     @defer.inlineCallbacks
     def test_worker_accepts_builds_after_failure(self):
@@ -220,7 +247,7 @@ def test_worker_accepts_builds_after_failure(self):
         controller, master, builder_id = \
             yield self.create_single_worker_config()
 
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
         # Trigger a buildrequest
         bsid, brids = yield self.createBuildrequest(master, [builder_id])
 
@@ -305,7 +332,7 @@ def test_worker_multiple_substantiations_succeed(self):
         # that they both finished with success
         self.assertEqual([build['results']
                           for build in finished_builds], [SUCCESS] * 2)
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
 
     @defer.inlineCallbacks
     def test_stalled_substantiation_then_timeout_get_requeued(self):
@@ -334,7 +361,7 @@ def test_stalled_substantiation_then_timeout_get_requeued(self):
             set(brids),
             {req['buildrequestid'] for req in unclaimed_build_requests}
         )
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
 
     @defer.inlineCallbacks
     def test_failed_sendBuilderList_get_requeued(self):
@@ -386,7 +413,7 @@ def remote_setBuilderList(self, dirs):
             # make sure stacktrace is present in html
             self.assertIn("buildbot.test.integration.test_worker_latent.TestException",
                 logs_by_name[i])
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
 
     @defer.inlineCallbacks
     def test_failed_ping_get_requeued(self):
@@ -438,35 +465,22 @@ def remote_print(self, msg):
             # make sure stacktrace is present in html
             self.assertIn("buildbot.test.integration.test_worker_latent.TestException",
                 logs_by_name[i])
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
 
     @defer.inlineCallbacks
     def test_worker_close_connection_while_building(self):
         """
-        If the worker close connection in the middle of the build, the next build can start correctly
+        If the worker close connection in the middle of the build, the next
+        build can start correctly
         """
-        controller = LatentController(self, 'local', build_wait_timeout=0)
-        # a step that we can finish when we want
-        stepcontroller = BuildStepController()
-        config_dict = {
-            'builders': [
-                BuilderConfig(name="testy",
-                              workernames=["local"],
-                              factory=BuildFactory([stepcontroller.step]),
-                              ),
-            ],
-            'workers': [controller.worker],
-            'protocols': {'null': {}},
-            # Disable checks about missing scheduler.
-            'multiMaster': True,
-        }
-        master = yield self.getMaster(config_dict)
-        builder_id = yield master.data.updates.findBuilderId('testy')
+        controller, stepcontroller, master, builder_id = \
+            yield self.create_single_worker_config_with_step(
+                controller_kwargs=dict(build_wait_timeout=0)
+            )
 
-        # Request two builds.
-        for i in range(2):
-            yield self.createBuildrequest(master, [builder_id])
-        controller.auto_stop(True)
+        # Request a build and disconnect midway
+        yield self.createBuildrequest(master, [builder_id])
+        yield controller.auto_stop(True)
 
         self.assertTrue(controller.starting)
         controller.start_instance(True)
@@ -511,7 +525,7 @@ def test_build_stop_with_cancelled_during_substantiation(self):
 
         dbdict = yield master.db.builds.getBuildByNumber(builder_id, 1)
         self.assertEqual(CANCELLED, dbdict['results'])
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
         self.flushLoggedErrors(LatentWorkerFailedToSubstantiate)
 
     @defer.inlineCallbacks
@@ -546,7 +560,7 @@ def test_build_stop_with_retry_during_substantiation(self):
             set(brids),
             {req['buildrequestid'] for req in unclaimed_build_requests}
         )
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
         self.flushLoggedErrors(LatentWorkerFailedToSubstantiate)
 
     @defer.inlineCallbacks
@@ -557,26 +571,13 @@ def test_rejects_build_on_instance_with_different_type_timeout_zero(self):
         schedule any builds on workers that are running different instance type
         than what these builds will require.
         """
-        controller = LatentController(self, 'local',
-                                      kind=Interpolate('%(prop:worker_kind)s'),
-                                      build_wait_timeout=0)
-
-        # a step that we can finish when we want
-        stepcontroller = BuildStepController()
-        config_dict = {
-            'builders': [
-                BuilderConfig(name="testy",
-                              workernames=["local"],
-                              factory=BuildFactory([stepcontroller.step]),
-                              ),
-            ],
-            'workers': [controller.worker],
-            'protocols': {'null': {}},
-            'multiMaster': True,
-        }
-
-        master = yield self.getMaster(config_dict)
-        builder_id = yield master.data.updates.findBuilderId('testy')
+        controller, stepcontroller, master, builder_id = \
+            yield self.create_single_worker_config_with_step(
+                controller_kwargs=dict(
+                    kind=Interpolate('%(prop:worker_kind)s'),
+                    build_wait_timeout=0
+                )
+            )
 
         # create build request
         yield self.createBuildrequest(master, [builder_id],
@@ -590,7 +591,7 @@ def test_rejects_build_on_instance_with_different_type_timeout_zero(self):
         controller.auto_connect_worker = True
         controller.auto_disconnect_worker = True
         controller.auto_start(True)
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
         controller.connect_worker()
         self.assertEqual((yield controller.get_started_kind()),
                          'a')
@@ -623,26 +624,14 @@ def test_rejects_build_on_instance_with_different_type_timeout_nonzero(self):
         schedule any builds on workers that are running different instance type
         than what these builds will require.
         """
-        controller = LatentController(self, 'local',
-                                      kind=Interpolate('%(prop:worker_kind)s'),
-                                      build_wait_timeout=5)
 
-        # a step that we can finish when we want
-        stepcontroller = BuildStepController()
-        config_dict = {
-            'builders': [
-                BuilderConfig(name="testy",
-                              workernames=["local"],
-                              factory=BuildFactory([stepcontroller.step]),
-                              ),
-            ],
-            'workers': [controller.worker],
-            'protocols': {'null': {}},
-            'multiMaster': True,
-        }
-
-        master = yield self.getMaster(config_dict)
-        builder_id = yield master.data.updates.findBuilderId('testy')
+        controller, stepcontroller, master, builder_id = \
+            yield self.create_single_worker_config_with_step(
+                controller_kwargs=dict(
+                    kind=Interpolate('%(prop:worker_kind)s'),
+                    build_wait_timeout=5
+                )
+            )
 
         # create build request
         yield self.createBuildrequest(master, [builder_id],
@@ -656,7 +645,7 @@ def test_rejects_build_on_instance_with_different_type_timeout_nonzero(self):
         controller.auto_connect_worker = True
         controller.auto_disconnect_worker = True
         controller.auto_start(True)
-        controller.auto_stop(True)
+        yield controller.auto_stop(True)
         controller.connect_worker()
         self.assertEqual((yield controller.get_started_kind()),
                          'a')