diff --git a/buildozer/__init__.py b/buildozer/__init__.py index 907df4abf..87e179f7c 100644 --- a/buildozer/__init__.py +++ b/buildozer/__init__.py @@ -327,7 +327,7 @@ def cmd(self, command, **kwargs): ret_stdout.append(chunk) if show_output: if IS_PY3: - stderr.write(chunk.decode('utf-8', 'replace')) + stdout.write(chunk.decode('utf-8', 'replace')) else: stdout.write(chunk) if fd_stderr in readx: diff --git a/tests/test_buildozer.py b/tests/test_buildozer.py index 31738c730..d81076bd8 100644 --- a/tests/test_buildozer.py +++ b/tests/test_buildozer.py @@ -4,7 +4,7 @@ import mock import unittest import buildozer as buildozer_module -from buildozer import Buildozer +from buildozer import Buildozer, IS_PY3 from six import StringIO import tempfile @@ -158,3 +158,45 @@ def test_android_ant_path(self): with mock.patch.object(Buildozer, 'file_exists', return_value=True): ant_path = target._install_apache_ant() assert ant_path == my_ant_path + + def test_cmd_unicode_decode(self): + """ + Verifies Buildozer.cmd() can properly handle non-unicode outputs. + refs: https://github.com/kivy/buildozer/issues/857 + """ + buildozer = Buildozer() + command = 'uname' + kwargs = { + 'show_output': True, + 'get_stdout': True, + 'get_stderr': True, + } + command_output = b'\x80 cannot decode \x80' + # showing the point that we can't decode it + with self.assertRaises(UnicodeDecodeError): + command_output.decode('utf-8') + with mock.patch('buildozer.Popen') as m_popen, \ + mock.patch('buildozer.select') as m_select, \ + mock.patch('buildozer.stdout') as m_stdout: + m_select.select().__getitem__.return_value = [0] + # makes sure fcntl.fcntl() gets what it expects so it doesn't crash + m_popen().stdout.fileno.return_value = 0 + m_popen().stderr.fileno.return_value = 2 + # Buildozer.cmd() is iterating through command output "chunk" until + # one chunk is None + m_popen().stdout.read.side_effect = [command_output, None] + m_popen().returncode = 0 + stdout, stderr, returncode = buildozer.cmd(command, **kwargs) + # when get_stdout is True, the command output also gets returned + assert stdout == command_output.decode('utf-8', 'ignore') + assert stderr is None + assert returncode == 0 + # Python2 and Python3 have different approaches for decoding the output + if IS_PY3: + assert m_stdout.write.call_args_list == [ + mock.call(command_output.decode('utf-8', 'replace')) + ] + else: + assert m_stdout.write.call_args_list == [ + mock.call(command_output) + ]