Repository: buildbot/buildbot

Issue:
HGPoller fails with some Mercurial repositories
HGPoller crashes on some repositories when using Python3 based Buildbot. Using the same master.cfg file, Python2 based Buildbot masters don't experience the same crash. I tested this with two different Mercurial repositories on the Python3 based Buildbot and only one of those experienced the crash - I am not certain how to explain that detail, but the permissions on each repository were configured identically.

[master.cfg.txt](https://github.com/buildbot/buildbot/files/1099078/master.cfg.txt)
[err.txt](https://github.com/buildbot/buildbot/files/1099083/err.txt)




Discussion:
Can you patch hgpoller.py with this change and try again with Python 3 and
Python 2
and report the result:

diff --git a/master/buildbot/changes/hgpoller.py
b/master/buildbot/changes/hgpoller.py
index 63126072c..82f2b2ad0 100644
--- a/master/buildbot/changes/hgpoller.py
+++ b/master/buildbot/changes/hgpoller.py
@@ -279,6 +279,7 @@ class HgPoller(base.PollingChangeSource):
                        r'--template={rev}:{node}\n']
         results = yield utils.getProcessOutput(self.hgbin, revListArgs,
                                                path=self._absWorkdir(),
env=os.environ, errortoo=False)
+        results = bytes2unicode(results, self.encoding, "replace")

         revNodeList = [rn.split(':', 1) for rn in results.strip().split()]


--
Craig


On Fri, Jun 23, 2017 at 2:20 PM, Steven Carpenter <notifications@github.com>
wrote:

> HGPoller crashes on some repositories when using Python3 based Buildbot.
> Using the same master.cfg file, Python2 based Buildbot masters don't
> experience the same crash. I tested this with two different Mercurial
> repositories on the Python3 based Buildbot and only one of those
> experienced the crash - I am not certain how to explain that detail, but
> the permissions on each repository were configured identically.
>
> master.cfg.txt
> <https://github.com/buildbot/buildbot/files/1099078/master.cfg.txt>
> err.txt <https://github.com/buildbot/buildbot/files/1099083/err.txt>
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/buildbot/buildbot/issues/3366>, or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABzuB9vvJNXGouY40JKDcsWCI5LUPFoEks5sHCwEgaJpZM4OEDpL>
> .
>


Resolving patch:
diff --git a/master/buildbot/changes/gitpoller.py b/master/buildbot/changes/gitpoller.py
--- a/master/buildbot/changes/gitpoller.py
+++ b/master/buildbot/changes/gitpoller.py
@@ -29,7 +29,6 @@
 from buildbot import config
 from buildbot.changes import base
 from buildbot.util import ascii2unicode
-from buildbot.util import bytes2NativeString
 from buildbot.util import bytes2unicode
 from buildbot.util import unicode2bytes
 from buildbot.util.state import StateMixin
@@ -122,7 +121,7 @@ def setLastRev(lastRev):
 
     def describe(self):
         str = ('GitPoller watching the remote git repository ' +
-               bytes2NativeString(self.repourl, self.encoding))
+               bytes2unicode(self.repourl, self.encoding))
 
         if self.branches:
             if self.branches is True:
@@ -198,7 +197,7 @@ def poll(self):
             try:
                 rev = yield self._dovccmd(
                     'rev-parse', [self._trackerBranch(branch)], path=self.workdir)
-                revs[branch] = bytes2NativeString(rev, self.encoding)
+                revs[branch] = bytes2unicode(rev, self.encoding)
                 yield self._process_changes(revs[branch], branch)
             except Exception:
                 log.err(_why="trying to poll branch %s of %s"
@@ -207,13 +206,9 @@ def poll(self):
         self.lastRev.update(revs)
         yield self.setState('lastRev', self.lastRev)
 
-    def _decode(self, git_output):
-        return bytes2unicode(git_output, self.encoding)
-
     def _get_commit_comments(self, rev):
         args = ['--no-walk', r'--format=%s%n%b', rev, '--']
         d = self._dovccmd('log', args, path=self.workdir)
-        d.addCallback(self._decode)
         return d
 
     def _get_commit_timestamp(self, rev):
@@ -243,7 +238,7 @@ def decode_file(file):
             match = re.match('^"(.*)"$', file)
             if match:
                 file = bytes2unicode(match.groups()[0], 'unicode_escape')
-            return self._decode(file)
+            return bytes2unicode(file)
 
         @d.addCallback
         def process(git_output):
@@ -259,7 +254,6 @@ def _get_commit_author(self, rev):
 
         @d.addCallback
         def process(git_output):
-            git_output = self._decode(git_output)
             if not git_output:
                 raise EnvironmentError('could not get commit author for rev')
             return git_output
@@ -361,9 +355,8 @@ def _convert_nonzero_to_failure(res,
                                         path):
             "utility to handle the result of getProcessOutputAndValue"
             (stdout, stderr, code) = res
-            stdout = bytes2NativeString(stdout, self.encoding)
-            stderr = bytes2NativeString(stderr, self.encoding)
-            args = bytes2NativeString(args, self.encoding)
+            stdout = bytes2unicode(stdout, self.encoding)
+            stderr = bytes2unicode(stderr, self.encoding)
             if code != 0:
                 if code == 128:
                     raise GitError('command %s %s in %s on repourl %s failed with exit code %d: %s'
diff --git a/master/buildbot/changes/hgpoller.py b/master/buildbot/changes/hgpoller.py
--- a/master/buildbot/changes/hgpoller.py
+++ b/master/buildbot/changes/hgpoller.py
@@ -27,7 +27,6 @@
 from buildbot import config
 from buildbot.changes import base
 from buildbot.util import ascii2unicode
-from buildbot.util import bytes2unicode
 from buildbot.util import deferredLocked
 
 
@@ -112,7 +111,7 @@ def _getRevDetails(self, rev):
         @d.addCallback
         def process(output):
             # all file names are on one line
-            output = bytes2unicode(output, self.encoding, "replace")
+            output = output.decode(self.encoding, "replace")
             date, author, files, comments = output.split(
                 os.linesep, 3)
 
@@ -279,8 +278,9 @@ def _processChanges(self, unused_output):
                        r'--template={rev}:{node}\n']
         results = yield utils.getProcessOutput(self.hgbin, revListArgs,
                                                path=self._absWorkdir(), env=os.environ, errortoo=False)
+        results = results.decode(self.encoding)
 
-        revNodeList = [rn.split(':', 1) for rn in results.strip().split()]
+        revNodeList = [rn.split(u':', 1) for rn in results.strip().split()]
 
         log.msg('hgpoller: processing %d changes: %r in %r'
                 % (len(revNodeList), revNodeList, self._absWorkdir()))
diff --git a/master/buildbot/changes/p4poller.py b/master/buildbot/changes/p4poller.py
--- a/master/buildbot/changes/p4poller.py
+++ b/master/buildbot/changes/p4poller.py
@@ -235,6 +235,13 @@ def _poll(self):
             args.extend(['-m', '1', '%s...' % (self.p4base,)])
 
         result = yield self._get_process_output(args)
+        # decode the result from its designated encoding
+        try:
+            result = bytes2unicode(result, self.encoding)
+        except UnicodeError as ex:
+            log.msg(u"Warning: cannot fully decode {} in {}".format(
+                    repr(result), self.encoding))
+            result = bytes2unicode(result, encoding=self.encoding, errors="replace")
 
         last_change = self.last_change
         changelists = []


Test patch:
diff --git a/master/buildbot/test/unit/test_changes_gitpoller.py b/master/buildbot/test/unit/test_changes_gitpoller.py
--- a/master/buildbot/test/unit/test_changes_gitpoller.py
+++ b/master/buildbot/test/unit/test_changes_gitpoller.py
@@ -32,8 +32,8 @@
 from buildbot.test.util import config
 from buildbot.test.util import gpo
 from buildbot.test.util import logging
-from buildbot.util import bytes2NativeString
 from buildbot.util import bytes2unicode
+from buildbot.util import unicode2bytes
 
 # Test that environment variables get propagated to subprocesses (See #2116)
 os.environ['TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES'] = 'TRUE'
@@ -123,16 +123,18 @@ def cb_desired(r):
 
     def test_get_commit_author(self):
         authorStr = u'Sammy Jankis <email@example.com>'
+        authorBytes = unicode2bytes(authorStr)
         return self._perform_git_output_test(self.poller._get_commit_author,
                                              [b'log', b'--no-walk', b'--format=%aN <%aE>',
                                                  self.dummyRevStr, b'--'],
-                                             authorStr, authorStr)
+                                             authorBytes, authorStr)
 
     def _test_get_commit_comments(self, commentStr):
+        commentBytes = unicode2bytes(commentStr)
         return self._perform_git_output_test(self.poller._get_commit_comments,
                                              [b'log', b'--no-walk', b'--format=%s%n%b',
                                                  self.dummyRevStr, b'--'],
-                                             commentStr, commentStr, emptyRaisesException=False)
+                                             commentBytes, commentStr, emptyRaisesException=False)
 
     def test_get_commit_comments(self):
         comments = [u'this is a commit message\n\nthat is multiline',
@@ -140,26 +142,27 @@ def test_get_commit_comments(self):
         return defer.DeferredList([self._test_get_commit_comments(commentStr) for commentStr in comments])
 
     def test_get_commit_files(self):
-        filesStr = '\n\nfile1\nfile2\n"\146ile_octal"\nfile space'
-        filesRes = ['file1', 'file2', 'file_octal', 'file space']
+        filesBytes = b'\n\nfile1\nfile2\n"\146ile_octal"\nfile space'
+        filesRes = [u'file1', u'file2', u'file_octal', u'file space']
         return self._perform_git_output_test(self.poller._get_commit_files,
                                              [b'log', b'--name-only', b'--no-walk',
                                                  b'--format=%n', self.dummyRevStr, b'--'],
-                                             filesStr, filesRes, emptyRaisesException=False)
+                                             filesBytes, filesRes, emptyRaisesException=False)
 
     def test_get_commit_files_with_space_in_changed_files(self):
-        filesStr = 'normal_directory/file1\ndirectory with space/file2'
+        filesBytes = b'normal_directory/file1\ndirectory with space/file2'
+        filesStr = bytes2unicode(filesBytes)
         return self._perform_git_output_test(
             self.poller._get_commit_files,
             [b'log', b'--name-only', b'--no-walk',
                 b'--format=%n', self.dummyRevStr, b'--'],
-            filesStr,
+            filesBytes,
             [l for l in filesStr.splitlines() if l.strip()],
             emptyRaisesException=False,
         )
 
     def test_get_commit_timestamp(self):
-        stampStr = '1273258009'
+        stampStr = b'1273258009'
         return self._perform_git_output_test(self.poller._get_commit_timestamp,
                                              [b'log', b'--no-walk', b'--format=%ct',
                                                  self.dummyRevStr, b'--'],
@@ -209,7 +212,7 @@ def test_poll_initial(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('bf0b01df6d00ae8d1ffa0b2e2acbe642a6cd35d5\n'),
+            .stdout(b'bf0b01df6d00ae8d1ffa0b2e2acbe642a6cd35d5\n'),
         )
 
         d = self.poller.poll()
@@ -221,7 +224,7 @@ def cb(_):
                 'master': 'bf0b01df6d00ae8d1ffa0b2e2acbe642a6cd35d5'
             })
             self.master.db.state.assertStateByClass(
-                name=bytes2NativeString(self.REPOURL), class_name='GitPoller',
+                name=bytes2unicode(self.REPOURL), class_name='GitPoller',
                 lastRev={
                     'master': 'bf0b01df6d00ae8d1ffa0b2e2acbe642a6cd35d5'
                 })
@@ -280,7 +283,7 @@ def test_poll_failLog(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
@@ -340,18 +343,18 @@ def test_poll_nothingNew(self):
             gpo.Expect(b'git', b'fetch', self.REPOURL,
                        b'+master:refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('no interesting output'),
+            .stdout(b'no interesting output'),
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'^4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout(''),
+            .stdout(b''),
         )
 
         self.poller.lastRev = {
@@ -363,7 +366,7 @@ def test_poll_nothingNew(self):
         def cb(_):
             self.assertAllCommandsRan()
             self.master.db.state.assertStateByClass(
-                name=bytes2NativeString(self.REPOURL), class_name='GitPoller',
+                name=bytes2unicode(self.REPOURL), class_name='GitPoller',
                 lastRev={
                     'master': '4423cdbcbb89c14e50dd5f4152415afd686c5241'
                 })
@@ -379,11 +382,11 @@ def test_poll_multipleBranches_initial(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/release')
             .path(b'gitpoller-work')
-            .stdout('9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
+            .stdout(b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
         )
 
         # do the poll
@@ -410,7 +413,7 @@ def test_poll_multipleBranches(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
@@ -418,13 +421,13 @@ def test_poll_multipleBranches(self):
                        b'^fa3ae8ed68e664d4db24798611b352e3c6509930',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join([
-                '64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
+            .stdout(b'\n'.join([
+                b'64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/release')
             .path(b'gitpoller-work')
-            .stdout('9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
+            .stdout(b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2',
@@ -432,8 +435,8 @@ def test_poll_multipleBranches(self):
                        b'^bf0b01df6d00ae8d1ffa0b2e2acbe642a6cd35d5',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join([
-                '9118f4ab71963d23d02d4bdc54876ac8bf05acf2'
+            .stdout(b'\n'.join([
+                b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2'
             ])),
         )
 
@@ -532,14 +535,14 @@ def test_poll_multipleBranches_buildPushesWithNoCommits_default(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/release')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'^4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout(''),
+            .stdout(b''),
         )
 
         # do the poll
@@ -569,14 +572,14 @@ def test_poll_multipleBranches_buildPushesWithNoCommits_true(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/release')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'^4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout(''),
+            .stdout(b''),
         )
 
         # and patch out the _get_commit_foo methods which were already tested
@@ -639,7 +642,7 @@ def test_poll_multipleBranches_buildPushesWithNoCommits_true_fast_forward(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/release')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
@@ -647,7 +650,7 @@ def test_poll_multipleBranches_buildPushesWithNoCommits_true_fast_forward(self):
                        b'^4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout(''),
+            .stdout(b''),
         )
 
         # and patch out the _get_commit_foo methods which were already tested
@@ -704,24 +707,24 @@ def test_poll_allBranches_single(self):
         self.expectCommands(
             gpo.Expect(b'git', b'init', b'--bare', b'gitpoller-work'),
             gpo.Expect(b'git', b'ls-remote', self.REPOURL)
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\t'
-                    'refs/heads/master\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\t'
+                    b'refs/heads/master\n'),
             gpo.Expect(b'git', b'fetch', self.REPOURL,
                        b'+master:refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work'),
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(
                 b'git', b'log', b'--format=%H',
                 b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
                 b'^fa3ae8ed68e664d4db24798611b352e3c6509930',
                 b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join([
-                '64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
+            .stdout(b'\n'.join([
+                b'64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
         )
 
         # and patch out the _get_commit_foo methods which were already tested
@@ -786,18 +789,18 @@ def test_poll_noChanges(self):
             gpo.Expect(b'git', b'fetch', self.REPOURL,
                        b'+master:refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('no interesting output'),
+            .stdout(b'no interesting output'),
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'^4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout(''),
+            .stdout(b''),
         )
 
         self.poller.lastRev = {
@@ -817,9 +820,9 @@ def test_poll_allBranches_multiple(self):
         self.expectCommands(
             gpo.Expect(b'git', b'init', b'--bare', b'gitpoller-work'),
             gpo.Expect(b'git', b'ls-remote', self.REPOURL)
-            .stdout('\n'.join([
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241\trefs/heads/master',
-                '9118f4ab71963d23d02d4bdc54876ac8bf05acf2\trefs/heads/release',
+            .stdout(b'\n'.join([
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241\trefs/heads/master',
+                b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2\trefs/heads/release',
             ])),
             gpo.Expect(
                 b'git', b'fetch', self.REPOURL,
@@ -829,7 +832,7 @@ def test_poll_allBranches_multiple(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(
                 b'git', b'log', b'--format=%H',
                 b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
@@ -837,13 +840,13 @@ def test_poll_allBranches_multiple(self):
                 b'^fa3ae8ed68e664d4db24798611b352e3c6509930',
                 b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join([
-                '64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
+            .stdout(b'\n'.join([
+                b'64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
             gpo.Expect(
                 b'git', b'rev-parse', b'refs/buildbot/' + self.REPOURL_QUOTED + b'/release')
             .path(b'gitpoller-work')
-            .stdout('9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
+            .stdout(b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
             gpo.Expect(
                 b'git', b'log', b'--format=%H',
                 b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2',
@@ -851,7 +854,7 @@ def test_poll_allBranches_multiple(self):
                 b'^bf0b01df6d00ae8d1ffa0b2e2acbe642a6cd35d5',
                 b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join(['9118f4ab71963d23d02d4bdc54876ac8bf05acf2'])),
+            .stdout(b'\n'.join([b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2'])),
         )
 
         # and patch out the _get_commit_foo methods which were already tested
@@ -918,9 +921,9 @@ def test_poll_callableFilteredBranches(self):
         self.expectCommands(
             gpo.Expect(b'git', b'init', b'--bare', b'gitpoller-work'),
             gpo.Expect(b'git', b'ls-remote', self.REPOURL)
-            .stdout('\n'.join([
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241\trefs/heads/master',
-                '9118f4ab71963d23d02d4bdc54876ac8bf05acf2\trefs/heads/release',
+            .stdout(b'\n'.join([
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241\trefs/heads/master',
+                b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2\trefs/heads/release',
             ])),
             gpo.Expect(
                 b'git', b'fetch', self.REPOURL,
@@ -929,7 +932,7 @@ def test_poll_callableFilteredBranches(self):
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(
                 b'git', b'log', b'--format=%H',
                 b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
@@ -937,9 +940,9 @@ def test_poll_callableFilteredBranches(self):
                 b'^fa3ae8ed68e664d4db24798611b352e3c6509930',
                 b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join([
-                '64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241']))
+            .stdout(b'\n'.join([
+                b'64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241']))
         )
 
         # and patch out the _get_commit_foo methods which were already tested
@@ -1008,11 +1011,11 @@ def test_poll_branchFilter(self):
         self.expectCommands(
             gpo.Expect(b'git', b'init', b'--bare', b'gitpoller-work'),
             gpo.Expect(b'git', b'ls-remote', self.REPOURL)
-            .stdout('\n'.join([
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241\t'
-                'refs/pull/410/merge',
-                '9118f4ab71963d23d02d4bdc54876ac8bf05acf2\t'
-                'refs/pull/410/head',
+            .stdout(b'\n'.join([
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241\t'
+                b'refs/pull/410/merge',
+                b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2\t'
+                b'refs/pull/410/head',
             ])),
             gpo.Expect(
                 b'git', b'fetch', self.REPOURL,
@@ -1022,7 +1025,7 @@ def test_poll_branchFilter(self):
                 b'git', b'rev-parse',
                 b'refs/buildbot/' + self.REPOURL_QUOTED + b'/refs/pull/410/head')
             .path(b'gitpoller-work')
-            .stdout('9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
+            .stdout(b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2'),
             gpo.Expect(
                 b'git', b'log', b'--format=%H',
                 b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2',
@@ -1030,7 +1033,7 @@ def test_poll_branchFilter(self):
                 b'^fa3ae8ed68e664d4db24798611b352e3c6509930',
                 b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join(['9118f4ab71963d23d02d4bdc54876ac8bf05acf2'])),
+            .stdout(b'\n'.join([b'9118f4ab71963d23d02d4bdc54876ac8bf05acf2'])),
         )
 
         # and patch out the _get_commit_foo methods which were already tested
@@ -1098,20 +1101,20 @@ def test_poll_old(self):
             gpo.Expect(b'git', b'fetch', self.REPOURL,
                        b'+master:refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('no interesting output'),
+            .stdout(b'no interesting output'),
             gpo.Expect(b'git', b'rev-parse',
                        b'refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work')
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\n'),
             gpo.Expect(b'git', b'log',
                        b'--format=%H',
                        b'4423cdbcbb89c14e50dd5f4152415afd686c5241',
                        b'^fa3ae8ed68e664d4db24798611b352e3c6509930',
                        b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join([
-                '64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241'
+            .stdout(b'\n'.join([
+                b'64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241'
             ])),
         )
 
@@ -1177,7 +1180,7 @@ def check_changes(_):
             self.assertAllCommandsRan()
 
             self.master.db.state.assertStateByClass(
-                name=bytes2NativeString(self.REPOURL), class_name='GitPoller',
+                name=bytes2unicode(self.REPOURL), class_name='GitPoller',
                 lastRev={
                     'master': '4423cdbcbb89c14e50dd5f4152415afd686c5241'
                 })
@@ -1188,8 +1191,8 @@ def test_poll_callableCategory(self):
         self.expectCommands(
             gpo.Expect(b'git', b'init', b'--bare', b'gitpoller-work'),
             gpo.Expect(b'git', b'ls-remote', self.REPOURL)
-            .stdout('4423cdbcbb89c14e50dd5f4152415afd686c5241\t'
-                    'refs/heads/master\n'),
+            .stdout(b'4423cdbcbb89c14e50dd5f4152415afd686c5241\t'
+                    b'refs/heads/master\n'),
             gpo.Expect(b'git', b'fetch', self.REPOURL,
                        b'+master:refs/buildbot/' + self.REPOURL_QUOTED + b'/master')
             .path(b'gitpoller-work'),
@@ -1203,9 +1206,9 @@ def test_poll_callableCategory(self):
                 b'^fa3ae8ed68e664d4db24798611b352e3c6509930',
                 b'--')
             .path(b'gitpoller-work')
-            .stdout('\n'.join([
-                '64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
-                '4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
+            .stdout(b'\n'.join([
+                b'64a5dc2a4bd4f558b5dd193d47c83c7d7abc9a1a',
+                b'4423cdbcbb89c14e50dd5f4152415afd686c5241'])),
         )
 
         # and patch out the _get_commit_foo methods which were already tested
@@ -1286,7 +1289,7 @@ def test_startService_loadLastRev(self):
         startService = mock.Mock()
         self.patch(base.PollingChangeSource, "startService", startService)
         self.master.db.state.fakeState(
-            name=bytes2NativeString(self.REPOURL), class_name='GitPoller',
+            name=bytes2unicode(self.REPOURL), class_name='GitPoller',
             lastRev={"master": "fa3ae8ed68e664d4db24798611b352e3c6509930"},
         )
 
diff --git a/master/buildbot/test/unit/test_changes_hgpoller.py b/master/buildbot/test/unit/test_changes_hgpoller.py
--- a/master/buildbot/test/unit/test_changes_hgpoller.py
+++ b/master/buildbot/test/unit/test_changes_hgpoller.py
@@ -26,6 +26,8 @@
 from buildbot.test.util import gpo
 
 ENVIRON_2116_KEY = 'TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES'
+LINESEP_BYTES = os.linesep.encode("ascii")
+PATHSEP_BYTES = os.pathsep.encode("ascii")
 
 
 class TestHgPoller(gpo.GetProcessOutputMixin,
@@ -102,19 +104,19 @@ def test_poll_initial(self):
             .path('/some/dir'),
             gpo.Expect(
                 'hg', 'heads', 'default', '--template={rev}' + os.linesep)
-            .path('/some/dir').stdout("73591"),
+            .path('/some/dir').stdout(b"73591"),
             gpo.Expect('hg', 'log', '-b', 'default', '-r', '73591:73591',  # only fetches that head
                        '--template={rev}:{node}\\n')
-            .path('/some/dir').stdout(os.linesep.join(['73591:4423cdb'])),
+            .path('/some/dir').stdout(LINESEP_BYTES.join([b'73591:4423cdb'])),
             gpo.Expect('hg', 'log', '-r', '4423cdb',
                        '--template={date|hgdate}' + os.linesep + '{author}' + os.linesep + "{files % '{file}" + os.pathsep + "'}" + os.linesep + '{desc|strip}')
-            .path('/some/dir').stdout(os.linesep.join([
-                '1273258100.0 -7200',
-                'Bob Test <bobtest@example.org>',
-                'file1 with spaces' + os.pathsep +
-                os.path.join('dir with spaces', 'file2') + os.pathsep,
-                'This is rev 73591',
-                ''])),
+            .path('/some/dir').stdout(LINESEP_BYTES.join([
+                b'1273258100.0 -7200',
+                b'Bob Test <bobtest@example.org>',
+                b'file1 with spaces' + PATHSEP_BYTES +
+                os.path.join(b'dir with spaces', b'file2') + PATHSEP_BYTES,
+                b'This is rev 73591',
+                b''])),
         )
 
         # do the poll
@@ -159,7 +161,7 @@ def test_poll_several_heads(self):
             .path('/some/dir'),
             gpo.Expect(
                 'hg', 'heads', 'default', '--template={rev}' + os.linesep)
-            .path('/some/dir').stdout('5' + os.linesep + '6' + os.linesep),
+            .path('/some/dir').stdout(b'5' + LINESEP_BYTES + b'6' + LINESEP_BYTES)
         )
 
         yield self.poller._setCurrentRev(3)
@@ -177,18 +179,18 @@ def test_poll_regular(self):
             .path('/some/dir'),
             gpo.Expect(
                 'hg', 'heads', 'default', '--template={rev}' + os.linesep)
-            .path('/some/dir').stdout('5' + os.linesep),
+            .path('/some/dir').stdout(b'5' + LINESEP_BYTES),
             gpo.Expect('hg', 'log', '-b', 'default', '-r', '5:5',
                        '--template={rev}:{node}\\n')
-            .path('/some/dir').stdout('5:784bd' + os.linesep),
+            .path('/some/dir').stdout(b'5:784bd' + LINESEP_BYTES),
             gpo.Expect('hg', 'log', '-r', '784bd',
                        '--template={date|hgdate}' + os.linesep + '{author}' + os.linesep + "{files % '{file}" + os.pathsep + "'}" + os.linesep + '{desc|strip}')
-            .path('/some/dir').stdout(os.linesep.join([
-                '1273258009.0 -7200',
-                'Joe Test <joetest@example.org>',
-                'file1 file2',
-                'Comment for rev 5',
-                ''])),
+            .path('/some/dir').stdout(LINESEP_BYTES.join([
+                b'1273258009.0 -7200',
+                b'Joe Test <joetest@example.org>',
+                b'file1 file2',
+                b'Comment for rev 5',
+                b''])),
         )
 
         yield self.poller._setCurrentRev(4)
diff --git a/master/buildbot/test/unit/test_changes_p4poller.py b/master/buildbot/test/unit/test_changes_p4poller.py
--- a/master/buildbot/test/unit/test_changes_p4poller.py
+++ b/master/buildbot/test/unit/test_changes_p4poller.py
@@ -33,20 +33,24 @@
 from buildbot.util import datetime2epoch
 
 first_p4changes = \
-    """Change 1 on 2006/04/13 by slamb@testclient 'first rev'
+    b"""Change 1 on 2006/04/13 by slamb@testclient 'first rev'
 """
 
 second_p4changes = \
-    """Change 3 on 2006/04/13 by bob@testclient 'short desc truncated'
+    b"""Change 3 on 2006/04/13 by bob@testclient 'short desc truncated'
 Change 2 on 2006/04/13 by slamb@testclient 'bar'
 """
 
 third_p4changes = \
-    """Change 5 on 2006/04/13 by mpatel@testclient 'first rev'
+    b"""Change 5 on 2006/04/13 by mpatel@testclient 'first rev'
+"""
+
+fourth_p4changes = \
+    b"""Change 6 on 2006/04/14 by mpatel@testclient 'bar \xd0\x91'
 """
 
 p4_describe_2 = \
-    """Change 2 by slamb@testclient on 2006/04/13 21:46:23
+    b"""Change 2 by slamb@testclient on 2006/04/13 21:46:23
 
 \tcreation
 
@@ -70,7 +74,7 @@
 """
 
 p4_describe_4 = \
-    """Change 4 by mpatel@testclient on 2006/04/13 21:55:39
+    b"""Change 4 by mpatel@testclient on 2006/04/13 21:55:39
 
 \tThis is a multiline comment with tabs and spaces
 \t
@@ -235,7 +239,7 @@ def test_poll_failed_changes(self):
                      p4base='//depot/myproject/',
                      split_file=lambda x: x.split('/', 1)))
         self.expectCommands(
-            gpo.Expect('p4', 'changes', '-m', '1', '//depot/myproject/...').stdout('Perforce client error:\n...'))
+            gpo.Expect('p4', 'changes', '-m', '1', '//depot/myproject/...').stdout(b'Perforce client error:\n...'))
 
         # call _poll, so we can catch the failure
         d = self.changesource._poll()
@@ -251,7 +255,7 @@ def test_poll_failed_describe(self):
                 'p4', 'changes', '//depot/myproject/...@3,#head').stdout(second_p4changes),
         )
         self.add_p4_describe_result(2, p4change[2])
-        self.add_p4_describe_result(3, 'Perforce client error:\n...')
+        self.add_p4_describe_result(3, b'Perforce client error:\n...')
 
         # tell poll() that it's already been called once
         self.changesource.last_change = 2
@@ -277,7 +281,7 @@ def test_poll_unicode_error(self):
                 'p4', 'changes', '//depot/myproject/...@3,#head').stdout(second_p4changes),
         )
         # Add a character which cannot be decoded with utf-8
-        undecodableText = p4change[2].encode("utf-8") + b"\x81"
+        undecodableText = p4change[2] + b"\x81"
         self.add_p4_describe_result(2, undecodableText)
 
         # tell poll() that it's already been called once
@@ -287,6 +291,21 @@ def test_poll_unicode_error(self):
         d = self.changesource._poll()
         return self.assertFailure(d, UnicodeError)
 
+    def test_poll_unicode_error2(self):
+        self.attachChangeSource(
+            P4Source(p4port=None, p4user=None,
+                     p4base='//depot/myproject/',
+                     split_file=lambda x: x.split('/', 1),
+                     encoding='ascii'))
+        # Trying to decode a certain character with ascii codec should fail.
+        self.expectCommands(
+            gpo.Expect(
+                'p4', 'changes', '-m', '1', '//depot/myproject/...').stdout(fourth_p4changes),
+        )
+
+        d = self.changesource._poll()
+        return d
+
     def test_acquire_ticket_auth(self):
         self.attachChangeSource(
             P4Source(p4port=None, p4user=None, p4passwd='pass',
diff --git a/master/buildbot/test/unit/test_changes_svnpoller.py b/master/buildbot/test/unit/test_changes_svnpoller.py
--- a/master/buildbot/test/unit/test_changes_svnpoller.py
+++ b/master/buildbot/test/unit/test_changes_svnpoller.py
@@ -28,7 +28,7 @@
 
 # this is the output of "svn info --xml
 # svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk"
-prefix_output = """\
+prefix_output = b"""\
 <?xml version="1.0"?>
 <info>
 <entry
@@ -51,7 +51,7 @@
 
 # and this is "svn info --xml svn://svn.twistedmatrix.com/svn/Twisted". I
 # think this is kind of a degenerate case.. it might even be a form of error.
-prefix_output_2 = """\
+prefix_output_2 = b"""\
 <?xml version="1.0"?>
 <info>
 </info>
@@ -59,7 +59,7 @@
 
 # this is the svn info output for a local repository, svn info --xml
 # file:///home/warner/stuff/Projects/Buildbot/trees/svnpoller/_trial_temp/test_vc/repositories/SVN-Repository
-prefix_output_3 = """\
+prefix_output_3 = b"""\
 <?xml version="1.0"?>
 <info>
 <entry
@@ -82,7 +82,7 @@
 
 # % svn info --xml file:///home/warner/stuff/Projects/Buildbot/trees/svnpoller/_trial_temp/test_vc/repositories/SVN-Repository/sample/trunk
 
-prefix_output_4 = """\
+prefix_output_4 = b"""\
 <?xml version="1.0"?>
 <info>
 <entry
@@ -110,7 +110,7 @@
                "_trial_temp/test_vc/repositories/SVN-Repository/sample")
 sample_logentries = [None] * 6
 
-sample_logentries[5] = """\
+sample_logentries[5] = b"""\
 <logentry
    revision="6">
 <author>warner</author>
@@ -123,7 +123,7 @@
 </logentry>
 """
 
-sample_logentries[4] = """\
+sample_logentries[4] = b"""\
 <logentry
    revision="5">
 <author>warner</author>
@@ -136,7 +136,7 @@
 </logentry>
 """
 
-sample_logentries[3] = """\
+sample_logentries[3] = b"""\
 <logentry
    revision="4">
 <author>warner</author>
@@ -149,7 +149,7 @@
 </logentry>
 """
 
-sample_logentries[2] = """\
+sample_logentries[2] = b"""\
 <logentry
    revision="3">
 <author>warner</author>
@@ -162,7 +162,7 @@
 </logentry>
 """
 
-sample_logentries[1] = """\
+sample_logentries[1] = b"""\
 <logentry
    revision="2">
 <author>warner</author>
@@ -177,7 +177,7 @@
 </logentry>
 """
 
-sample_logentries[0] = """\
+sample_logentries[0] = b"""\
 <logentry
    revision="1">
 <author>warner</author>
@@ -200,7 +200,7 @@
 </logentry>
 """
 
-sample_info_output = """\
+sample_info_output = b"""\
 <?xml version="1.0"?>
 <info>
 <entry
@@ -222,20 +222,16 @@
 """
 
 
-changes_output_template = """\
-<?xml version="1.0"?>
-<log>
-%s</log>
-"""
-
-
 def make_changes_output(maxrevision):
     # return what 'svn log' would have just after the given revision was
     # committed
     logs = sample_logentries[0:maxrevision]
     assert len(logs) == maxrevision
     logs.reverse()
-    output = changes_output_template % ("".join(logs))
+    output = (b"""<?xml version="1.0"?>
+<log>"""
+              + b"".join(logs)
+              + b"</log>")
     return output
 
 
@@ -578,7 +574,7 @@ def test_poll_get_prefix_exception(self):
                                  svnuser='dustin', svnpasswd='bbrocks')
 
         self.expectCommands(
-            self.makeInfoExpect().stderr("error"))
+            self.makeInfoExpect().stderr(b"error"))
         d = s.poll()
 
         @d.addCallback
@@ -594,7 +590,7 @@ def test_poll_get_logs_exception(self):
         s._prefix = "abc"  # skip the get_prefix stuff
 
         self.expectCommands(
-            self.makeLogExpect().stderr("some error"))
+            self.makeLogExpect().stderr(b"some error"))
         d = s.poll()
 
         @d.addCallback
diff --git a/master/buildbot/test/unit/test_test_util_gpo.py b/master/buildbot/test/unit/test_test_util_gpo.py
--- a/master/buildbot/test/unit/test_test_util_gpo.py
+++ b/master/buildbot/test/unit/test_test_util_gpo.py
@@ -63,7 +63,7 @@ def assertSuccessful(self, result):
                            result.failures[0][1].getErrorMessage())
             if result.errors:
                 output += ('\nerrors: %s' %
-                           map(lambda x: x[1].value, result.errors))
+                           [error[1].value for error in result.errors])
             raise self.failureException(output)
 
         self.assertTrue(result.wasSuccessful())
@@ -88,14 +88,14 @@ def method(testcase):
     def test_methodChaining(self):
         expect = Expect('command')
         self.assertEqual(expect, expect.exit(0))
-        self.assertEqual(expect, expect.stdout("output"))
-        self.assertEqual(expect, expect.stderr("error"))
+        self.assertEqual(expect, expect.stdout(b"output"))
+        self.assertEqual(expect, expect.stderr(b"error"))
 
     def test_gpo_oneCommand(self):
         def method(testcase):
             testcase.expectCommands(Expect("command"))
             d = utils.getProcessOutput("command", ())
-            d.addCallback(self.assertEqual, '')
+            d.addCallback(self.assertEqual, b'')
             d.addCallback(lambda _: testcase.assertAllCommandsRan())
             return d
         result = self.runTestMethod(method)
@@ -106,7 +106,7 @@ def method(testcase):
             testcase.expectCommands(Expect("command"))
             testcase.expectCommands(Expect("command2"))
             d = utils.getProcessOutput("command", ())
-            d.addCallback(self.assertEqual, '')
+            d.addCallback(self.assertEqual, b'')
             d.addCallback(lambda _: testcase.assertAllCommandsRan())
             return d
         result = self.runTestMethod(method)
@@ -160,18 +160,18 @@ def method(testcase):
 
     def test_gpo_errorOutput(self):
         def method(testcase):
-            testcase.expectCommands(Expect("command").stderr("some test"))
+            testcase.expectCommands(Expect("command").stderr(b"some test"))
             d = testcase.assertFailure(
                 utils.getProcessOutput("command", ()), [IOError])
             return d
         result = self.runTestMethod(method)
-        self.assertTestFailure(result, "got stderr: 'some test'")
+        self.assertTestFailure(result, "got stderr: " + repr(b'some test'))
 
     def test_gpo_errorOutput_errtoo(self):
         def method(testcase):
-            testcase.expectCommands(Expect("command").stderr("some test"))
+            testcase.expectCommands(Expect("command").stderr(b"some test"))
             d = utils.getProcessOutput("command", (), errortoo=True)
-            d.addCallback(testcase.assertEqual, "some test")
+            d.addCallback(testcase.assertEqual, b"some test")
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
@@ -180,16 +180,16 @@ def test_gpo_exitIgnored(self):
         def method(testcase):
             testcase.expectCommands(Expect("command").exit(1))
             d = utils.getProcessOutput("command", ())
-            d.addCallback(self.assertEqual, '')
+            d.addCallback(self.assertEqual, b'')
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
 
     def test_gpo_output(self):
         def method(testcase):
-            testcase.expectCommands(Expect("command").stdout("stdout"))
+            testcase.expectCommands(Expect("command").stdout(b"stdout"))
             d = utils.getProcessOutput("command", ())
-            d.addCallback(testcase.assertEqual, "stdout")
+            d.addCallback(testcase.assertEqual, b"stdout")
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
@@ -197,13 +197,13 @@ def method(testcase):
     def test_gpo_outputAndError(self):
         def method(testcase):
             testcase.expectCommands(
-                Expect("command").stdout("stdout").stderr("stderr"))
+                Expect("command").stdout(b"stdout").stderr(b"stderr"))
             d = utils.getProcessOutput("command", (), errortoo=True)
 
             @d.addCallback
             def cb(res):
-                testcase.assertSubstring("stdout", res)
-                testcase.assertSubstring("stderr", res)
+                testcase.assertSubstring(b"stdout", res)
+                testcase.assertSubstring(b"stderr", res)
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
@@ -213,7 +213,7 @@ def method(testcase):
             testcase.expectCommands(Expect("command"))
             testcase.addGetProcessOutputExpectEnv({'key': 'value'})
             d = utils.getProcessOutput("command", (), env={'key': 'value'})
-            d.addCallback(self.assertEqual, '')
+            d.addCallback(self.assertEqual, b'')
             d.addCallback(lambda _: testcase.assertAllCommandsRan())
             return d
         result = self.runTestMethod(method)
@@ -244,7 +244,7 @@ def test_gpoav_oneCommand(self):
         def method(testcase):
             testcase.expectCommands(Expect("command"))
             d = utils.getProcessOutputAndValue("command", ())
-            d.addCallback(self.assertEqual, ('', '', 0))
+            d.addCallback(self.assertEqual, (b'', b'', 0))
             d.addCallback(lambda _: testcase.assertAllCommandsRan())
             return d
         result = self.runTestMethod(method)
@@ -255,7 +255,7 @@ def method(testcase):
             testcase.expectCommands(Expect("command"))
             testcase.expectCommands(Expect("command2"))
             d = utils.getProcessOutputAndValue("command", ())
-            d.addCallback(self.assertEqual, ('', '', 0))
+            d.addCallback(self.assertEqual, (b'', b'', 0))
             d.addCallback(lambda _: testcase.assertAllCommandsRan())
             return d
         result = self.runTestMethod(method)
@@ -310,9 +310,9 @@ def method(testcase):
 
     def test_gpoav_errorOutput(self):
         def method(testcase):
-            testcase.expectCommands(Expect("command").stderr("some test"))
+            testcase.expectCommands(Expect("command").stderr(b"some test"))
             d = utils.getProcessOutputAndValue("command", ())
-            d.addCallback(self.assertEqual, ('', 'some test', 0))
+            d.addCallback(self.assertEqual, (b'', b'some test', 0))
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
@@ -321,16 +321,16 @@ def test_gpoav_exit(self):
         def method(testcase):
             testcase.expectCommands(Expect("command").exit(1))
             d = utils.getProcessOutputAndValue("command", ())
-            d.addCallback(self.assertEqual, ('', '', 1))
+            d.addCallback(self.assertEqual, (b'', b'', 1))
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
 
     def test_gpoav_output(self):
         def method(testcase):
-            testcase.expectCommands(Expect("command").stdout("stdout"))
+            testcase.expectCommands(Expect("command").stdout(b"stdout"))
             d = utils.getProcessOutputAndValue("command", ())
-            d.addCallback(testcase.assertEqual, ("stdout", '', 0))
+            d.addCallback(testcase.assertEqual, (b"stdout", b'', 0))
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
@@ -338,9 +338,9 @@ def method(testcase):
     def test_gpoav_outputAndError(self):
         def method(testcase):
             testcase.expectCommands(
-                Expect("command").stdout("stdout").stderr("stderr"))
+                Expect("command").stdout(b"stdout").stderr(b"stderr"))
             d = utils.getProcessOutputAndValue("command", ())
-            d.addCallback(testcase.assertEqual, ("stdout", 'stderr', 0))
+            d.addCallback(testcase.assertEqual, (b"stdout", b'stderr', 0))
             return d
         result = self.runTestMethod(method)
         self.assertSuccessful(result)
diff --git a/master/buildbot/test/util/gpo.py b/master/buildbot/test/util/gpo.py
--- a/master/buildbot/test/util/gpo.py
+++ b/master/buildbot/test/util/gpo.py
@@ -22,8 +22,8 @@
 
 
 class Expect(object):
-    _stdout = ""
-    _stderr = ""
+    _stdout = b""
+    _stderr = b""
     _exit = 0
     _path = None
 
@@ -32,10 +32,12 @@ def __init__(self, bin, *args):
         self._args = args
 
     def stdout(self, stdout):
+        assert(isinstance(stdout, bytes))
         self._stdout = stdout
         return self
 
     def stderr(self, stderr):
+        assert(isinstance(stderr, bytes))
         self._stderr = stderr
         return self