Repository: buildbot/buildbot

Issue:
P4: bytes encoding error in TicketLoginProtocol
Extracted out of issue #5042 by @bschatt:

I don't recall the exact error message but I had some string encoding issues with the 3 variables (stdin, stderr and stdout) in the TicketLoginProtocol class. I made an easy fix and I can submit a PR if needed:

```
self.stdin = stdin.encode('ascii')
self.stdout = b''
self.stderr = b''
```


Discussion:

Resolving patch:
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
@@ -50,9 +50,9 @@ class TicketLoginProtocol(protocol.ProcessProtocol):
 
     def __init__(self, stdin, p4base):
         self.deferred = defer.Deferred()
-        self.stdin = stdin
-        self.stdout = ''
-        self.stderr = ''
+        self.stdin = stdin.encode('ascii')
+        self.stdout = b''
+        self.stderr = b''
         self.p4base = p4base
 
     def connectionMade(self):
@@ -194,8 +194,13 @@ def _acquireTicket(self, protocol):
 
         reactor.spawnProcess(protocol, self.p4bin, command, env=os.environ)
 
-    def _parseTicketPassword(self, text):
-        lines = text.splitlines()
+    def _parseTicketPassword(self, stdout):
+        try:
+            stdout = stdout.decode(self.encoding, errors='strict')
+        except Exception as e:
+            raise P4PollerError('Failed to parse P4 ticket: {}'.format(e))
+
+        lines = stdout.splitlines()
         if len(lines) < 2:
             return None
         return lines[-1].strip()


Test patch:
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
@@ -340,8 +340,8 @@ def spawnProcess(pp, cmd, argv, env):
             self.assertEqual([cmd, argv],
                              ['p4', [b'p4', b'login', b'-p']])
             pp.makeConnection(transport)
-            self.assertEqual('pass\n', transport.msg)
-            pp.outReceived('Enter password:\nSuccess:  Password verified.\nTICKET_ID_GOES_HERE\n')
+            self.assertEqual(b'pass\n', transport.msg)
+            pp.outReceived(b'Enter password:\nSuccess:  Password verified.\nTICKET_ID_GOES_HERE\n')
             so = error.ProcessDone(None)
             pp.processEnded(failure.Failure(so))
         self.patch(reactor, 'spawnProcess', spawnProcess)
@@ -370,8 +370,8 @@ def spawnProcess(pp, cmd, argv, env):
             self.assertEqual([cmd, argv],
                              ['p4', [b'p4', b'login', b'-p']])
             pp.makeConnection(transport)
-            self.assertEqual('pass\n', transport.msg)
-            pp.outReceived('Enter password:\nTICKET_ID_GOES_HERE\n')
+            self.assertEqual(b'pass\n', transport.msg)
+            pp.outReceived(b'Enter password:\nTICKET_ID_GOES_HERE\n')
             so = error.ProcessDone(None)
             pp.processEnded(failure.Failure(so))
         self.patch(reactor, 'spawnProcess', spawnProcess)
@@ -400,9 +400,9 @@ def spawnProcess(pp, cmd, argv, env):
             self.assertEqual([cmd, argv],
                              ['p4', [b'p4', b'login', b'-p']])
             pp.makeConnection(transport)
-            self.assertEqual('pass\n', transport.msg)
-            pp.outReceived('Enter password:\n')
-            pp.errReceived("Password invalid.\n'auth-check' validation failed: Incorrect password!\n")
+            self.assertEqual(b'pass\n', transport.msg)
+            pp.outReceived(b'Enter password:\n')
+            pp.errReceived(b"Password invalid.\n'auth-check' validation failed: Incorrect password!\n")
             so = error.ProcessDone(status=1)
             pp.processEnded(failure.Failure(so))
         self.patch(reactor, 'spawnProcess', spawnProcess)
@@ -412,6 +412,34 @@ def spawnProcess(pp, cmd, argv, env):
         self.assertEqual(
             self.changesource._ticket_passwd, None)
 
+    @defer.inlineCallbacks
+    def test_acquire_ticket_auth_invalid_encoding(self):
+        self.attachChangeSource(
+            P4Source(p4port=None, p4user=None, p4passwd='pass',
+                     p4base='//depot/myproject/',
+                     split_file=lambda x: x.split('/', 1),
+                     use_tickets=True))
+
+        transport = FakeTransport()
+
+        # p4poller uses only those arguments at the moment
+        def spawnProcess(pp, cmd, argv, env):
+            self.assertEqual([cmd, argv],
+                             ['p4', [b'p4', b'login', b'-p']])
+            pp.makeConnection(transport)
+            self.assertEqual(b'pass\n', transport.msg)
+            pp.outReceived(b'\xff\xff\xff\xff\xff')
+            so = error.ProcessDone(None)
+            pp.processEnded(failure.Failure(so))
+        self.patch(reactor, 'spawnProcess', spawnProcess)
+
+        yield self.changesource.poll()
+
+        errors = self.flushLoggedErrors(P4PollerError)
+        self.assertEqual(len(errors), 1)
+        self.assertIn('\'utf-8\' codec can\'t decode byte 0xff', errors[0].getErrorMessage())
+        self.assertIn('Failed to parse P4 ticket', errors[0].getErrorMessage())
+
     @defer.inlineCallbacks
     def test_poll_split_file(self):
         """Make sure split file works on branch only changes"""