Bug 1441287 - [mozcrash] Add support for unicode paths. draft
authorHenrik Skupin <mail@hskupin.info>
Tue, 20 Mar 2018 11:54:04 +0100
changeset 769955 c72dc8e3e4baab5e2a1566255111fd3e8088f843
parent 769951 7b0c774810646d6279b6652a8468736665fc3bbb
push id103269
push userbmo:hskupin@gmail.com
push dateTue, 20 Mar 2018 13:54:20 +0000
bugs1441287
milestone61.0a1
Bug 1441287 - [mozcrash] Add support for unicode paths. To let mozcrash handle minidump files located in profile paths with unicode characters, support for that has to be added. It also applies to the locations for the stackwalk binary, minidump save path, and symbols. MozReview-Commit-ID: EROVmK21a5Y
testing/mozbase/mozcrash/mozcrash/mozcrash.py
testing/mozbase/mozcrash/tests/test_basic.py
testing/mozbase/mozcrash/tests/test_java_exception.py
testing/mozbase/mozcrash/tests/test_stackwalk.py
testing/mozbase/mozcrash/tests/test_symbols_path.py
--- a/testing/mozbase/mozcrash/mozcrash/mozcrash.py
+++ b/testing/mozbase/mozcrash/mozcrash/mozcrash.py
@@ -89,30 +89,33 @@ def check_for_crashes(dump_directory,
 
     crash_info = CrashInfo(dump_directory, symbols_path, dump_save_path=dump_save_path,
                            stackwalk_binary=stackwalk_binary)
 
     crash_count = 0
     for info in crash_info:
         crash_count += 1
         if not quiet:
-            stackwalk_output = ["Crash dump filename: %s" % info.minidump_path]
+            stackwalk_output = [u"Crash dump filename: {}".format(info.minidump_path)]
             if info.stackwalk_stderr:
                 stackwalk_output.append("stderr from minidump_stackwalk:")
                 stackwalk_output.append(info.stackwalk_stderr)
             elif info.stackwalk_stdout is not None:
                 stackwalk_output.append(info.stackwalk_stdout)
             if info.stackwalk_retcode is not None and info.stackwalk_retcode != 0:
-                stackwalk_output.append("minidump_stackwalk exited with return code %d" %
-                                        info.stackwalk_retcode)
+                stackwalk_output.append("minidump_stackwalk exited with return code {}".format(
+                                        info.stackwalk_retcode))
             signature = info.signature if info.signature else "unknown top frame"
-            print("PROCESS-CRASH | %s | application crashed [%s]" % (test_name,
-                                                                     signature))
-            print('\n'.join(stackwalk_output))
-            print('\n'.join(info.stackwalk_errors))
+
+            output = u"PROCESS-CRASH | {name} | application crashed [{sig}]\n{out}\n{err}".format(
+                name=test_name,
+                sig=signature,
+                out="\n".join(stackwalk_output),
+                err="\n".join(info.stackwalk_errors))
+            print(output.encode("utf-8"))
 
     return crash_count
 
 
 def log_crashes(logger,
                 dump_directory,
                 symbols_path,
                 process=None,
@@ -242,17 +245,17 @@ class CrashInfo(object):
             os.path.exists(self.stackwalk_binary) and
                 os.access(self.stackwalk_binary, os.X_OK)):
 
             command = [
                 self.stackwalk_binary,
                 path,
                 self.symbols_path
             ]
-            self.logger.info('Copy/paste: ' + ' '.join(command))
+            self.logger.info(u"Copy/paste: {}".format(' '.join(command)))
             # run minidump_stackwalk
             p = subprocess.Popen(
                 command,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE
             )
             (out, err) = p.communicate()
             retcode = p.returncode
@@ -308,23 +311,25 @@ class CrashInfo(object):
             os.unlink(self.dump_save_path)
         if not os.path.isdir(self.dump_save_path):
             try:
                 os.makedirs(self.dump_save_path)
             except OSError:
                 pass
 
         shutil.move(path, self.dump_save_path)
-        self.logger.info("Saved minidump as %s" %
-                         os.path.join(self.dump_save_path, os.path.basename(path)))
+        self.logger.info(u"Saved minidump as {}".format(
+            os.path.join(self.dump_save_path, os.path.basename(path))
+        ))
 
         if os.path.isfile(extra):
             shutil.move(extra, self.dump_save_path)
-            self.logger.info("Saved app info as %s" %
-                             os.path.join(self.dump_save_path, os.path.basename(extra)))
+            self.logger.info(u"Saved app info as {}".format(
+                os.path.join(self.dump_save_path, os.path.basename(extra))
+            ))
 
 
 def check_for_java_exception(logcat, test_name=None, quiet=False):
     """
     Print a summary of a fatal Java exception, if present in the provided
     logcat output.
 
     Example:
@@ -365,22 +370,25 @@ def check_for_java_exception(logcat, tes
                 logre = re.compile(r".*\): \t?(.*)")
                 m = logre.search(logcat[i + 1])
                 if m and m.group(1):
                     exception_type = m.group(1)
                 m = logre.search(logcat[i + 2])
                 if m and m.group(1):
                     exception_location = m.group(1)
                 if not quiet:
-                    print("PROCESS-CRASH | %s | java-exception %s %s" % (test_name,
-                                                                         exception_type,
-                                                                         exception_location))
+                    output = u"PROCESS-CRASH | {name} | java-exception {type} {loc}".format(
+                        name=test_name,
+                        type=exception_type,
+                        loc=exception_location
+                    )
+                    print(output.encode("utf-8"))
             else:
-                print("Automation Error: java exception in logcat at line "
-                      "%d of %d: %s" % (i, len(logcat), line))
+                print(u"Automation Error: java exception in logcat at line "
+                      "{0} of {1}: {2}".format(i, len(logcat), line))
             break
 
     return found_exception
 
 
 if mozinfo.isWin:
     import ctypes
     import uuid
@@ -411,20 +419,20 @@ if mozinfo.isWin:
                 utility_path):
             # We're not going to be able to write a minidump with ctypes if our
             # python process was compiled for a different architecture than
             # firefox, so we invoke the minidumpwriter utility program.
 
             log = get_logger()
             minidumpwriter = os.path.normpath(os.path.join(utility_path,
                                                            "minidumpwriter.exe"))
-            log.info("Using %s to write a dump to %s for [%d]" %
-                     (minidumpwriter, file_name, pid))
+            log.info(u"Using {} to write a dump to {} for [{}]".format(
+                minidumpwriter, file_name, pid))
             if not os.path.exists(minidumpwriter):
-                log.error("minidumpwriter not found in %s" % utility_path)
+                log.error(u"minidumpwriter not found in {}".format(utility_path))
                 return
 
             if isinstance(file_name, unicode):
                 # Convert to a byte string before sending to the shell.
                 file_name = file_name.encode(sys.getfilesystemencoding())
 
             status = subprocess.Popen([minidumpwriter, str(pid), file_name]).wait()
             if status:
--- a/testing/mozbase/mozcrash/tests/test_basic.py
+++ b/testing/mozbase/mozcrash/tests/test_basic.py
@@ -1,21 +1,48 @@
 #!/usr/bin/env python
+# coding=UTF-8
 
 from __future__ import absolute_import
 
 import mozunit
 import pytest
 
+from conftest import fspath
+
 
 def test_no_dump_files(check_for_crashes):
     """Test that check_for_crashes returns 0 if no dumps are present."""
     assert 0 == check_for_crashes()
 
 
 @pytest.mark.parametrize('minidump_files', [3], indirect=True)
 def test_dump_count(check_for_crashes, minidump_files):
     """Test that check_for_crashes returns the number of crash dumps."""
     assert 3 == check_for_crashes()
 
 
+def test_dump_directory_unicode(request, check_for_crashes, tmpdir, capsys):
+    """Test that check_for_crashes can handle unicode in dump_directory."""
+    from conftest import minidump_files
+
+    tmpdir = tmpdir.ensure(u"🍪", dir=1)
+    minidump_files = minidump_files(request, tmpdir)
+
+    assert 1 == check_for_crashes(dump_directory=fspath(tmpdir),
+                                  quiet=False)
+
+    out, _ = capsys.readouterr()
+    assert fspath(minidump_files[0]["dmp"]) in out
+    assert u"🍪" in out
+
+
+def test_test_name_unicode(check_for_crashes, minidump_files, capsys):
+    """Test that check_for_crashes can handle unicode in dump_directory."""
+    assert 1 == check_for_crashes(test_name=u"🍪",
+                                  quiet=False)
+
+    out, err = capsys.readouterr()
+    assert u"| 🍪 |" in out
+
+
 if __name__ == '__main__':
     mozunit.main()
--- a/testing/mozbase/mozcrash/tests/test_java_exception.py
+++ b/testing/mozbase/mozcrash/tests/test_java_exception.py
@@ -1,9 +1,10 @@
 #!/usr/bin/env python
+# coding=UTF-8
 
 from __future__ import absolute_import
 
 import mozunit
 import pytest
 
 
 @pytest.fixture
@@ -36,10 +37,17 @@ def test_unchecked_exception(check_for_j
     """Test for an exception which should not be caught."""
     passable_log = list(test_log)
     passable_log[0] = "01-30 20:15:41.937 E/GeckoAppShell( 1703):" \
         " >>> NOT-SO-BAD EXCEPTION FROM THREAD 9 (\"GeckoBackgroundThread\")"
 
     assert 0 == check_for_java_exception(passable_log)
 
 
+def test_test_name_unicode(check_for_java_exception, test_log):
+    """Test that check_for_crashes can handle unicode in dump_directory."""
+    assert 1 == check_for_java_exception(test_log,
+                                         test_name=u"🍪",
+                                         quiet=False)
+
+
 if __name__ == '__main__':
     mozunit.main()
--- a/testing/mozbase/mozcrash/tests/test_stackwalk.py
+++ b/testing/mozbase/mozcrash/tests/test_stackwalk.py
@@ -1,22 +1,47 @@
 #!/usr/bin/env python
+# coding=UTF-8
 
 from __future__ import absolute_import
 
 import os
 
 import mozunit
 
 from conftest import fspath
 
 
+def test_stackwalk_not_found(check_for_crashes, minidump_files, tmpdir, capsys):
+    """Test that check_for_crashes can handle unicode in dump_directory."""
+    stackwalk = tmpdir.join('stackwalk')
+
+    assert 1 == check_for_crashes(stackwalk_binary=fspath(stackwalk),
+                                  quiet=False)
+
+    out, _ = capsys.readouterr()
+    assert "MINIDUMP_STACKWALK binary not found" in out
+
+
 def test_stackwalk_envvar(check_for_crashes, minidump_files, stackwalk):
     """Test that check_for_crashes uses the MINIDUMP_STACKWALK environment var."""
     os.environ['MINIDUMP_STACKWALK'] = fspath(stackwalk)
     try:
         assert 1 == check_for_crashes(stackwalk_binary=None)
     finally:
         del os.environ['MINIDUMP_STACKWALK']
 
 
+def test_stackwalk_unicode(check_for_crashes, minidump_files, tmpdir, capsys):
+    """Test that check_for_crashes can handle unicode in dump_directory."""
+    stackwalk = tmpdir.mkdir(u"🍪").join('stackwalk')
+    stackwalk.write('fake binary')
+    stackwalk.chmod(0o744)
+
+    assert 1 == check_for_crashes(stackwalk_binary=fspath(stackwalk),
+                                  quiet=False)
+
+    out, err = capsys.readouterr()
+    assert fspath(stackwalk) in out
+
+
 if __name__ == '__main__':
     mozunit.main()
--- a/testing/mozbase/mozcrash/tests/test_symbols_path.py
+++ b/testing/mozbase/mozcrash/tests/test_symbols_path.py
@@ -1,25 +1,39 @@
 #!/usr/bin/env python
+# coding=UTF-8
 
 from __future__ import absolute_import
 
 import urlparse
 import zipfile
 import StringIO
 
 import mozhttpd
 import mozunit
 
+from conftest import fspath
+
 
 def test_symbols_path_not_present(check_for_crashes, minidump_files):
     """Test that no symbols path let mozcrash try to find the symbols."""
     assert 1 == check_for_crashes(symbols_path=None)
 
 
+def test_symbols_path_unicode(check_for_crashes, minidump_files, tmpdir, capsys):
+    """Test that check_for_crashes can handle unicode in dump_directory."""
+    symbols_path = tmpdir.mkdir(u"🍪")
+
+    assert 1 == check_for_crashes(symbols_path=fspath(symbols_path),
+                                  quiet=False)
+
+    out, _ = capsys.readouterr()
+    assert fspath(symbols_path) in out
+
+
 def test_symbols_path_url(check_for_crashes, minidump_files):
     """Test that passing a URL as symbols_path correctly fetches the URL."""
     data = {"retrieved": False}
 
     def make_zipfile():
         data = StringIO.StringIO()
         z = zipfile.ZipFile(data, 'w')
         z.writestr("symbols.txt", "abc/xyz")