diff options
Diffstat (limited to '')
-rw-r--r-- | testing/mozbase/mozcrash/tests/conftest.py | 127 | ||||
-rw-r--r-- | testing/mozbase/mozcrash/tests/manifest.ini | 7 | ||||
-rw-r--r-- | testing/mozbase/mozcrash/tests/test_basic.py | 43 | ||||
-rw-r--r-- | testing/mozbase/mozcrash/tests/test_java_exception.py | 51 | ||||
-rw-r--r-- | testing/mozbase/mozcrash/tests/test_save_path.py | 68 | ||||
-rw-r--r-- | testing/mozbase/mozcrash/tests/test_stackwalk.py | 42 | ||||
-rw-r--r-- | testing/mozbase/mozcrash/tests/test_symbols_path.py | 97 |
7 files changed, 435 insertions, 0 deletions
diff --git a/testing/mozbase/mozcrash/tests/conftest.py b/testing/mozbase/mozcrash/tests/conftest.py new file mode 100644 index 0000000000..4827515723 --- /dev/null +++ b/testing/mozbase/mozcrash/tests/conftest.py @@ -0,0 +1,127 @@ +# coding=UTF-8 + +import uuid + +import mozcrash +import pytest +from py._path.common import fspath + + +@pytest.fixture(scope="session") +def stackwalk(tmpdir_factory): + stackwalk = tmpdir_factory.mktemp("stackwalk_binary").join("stackwalk") + stackwalk.write("fake binary") + stackwalk.chmod(0o744) + return stackwalk + + +@pytest.fixture +def check_for_crashes(tmpdir, stackwalk, monkeypatch): + monkeypatch.delenv("MINIDUMP_SAVE_PATH", raising=False) + + def wrapper( + dump_directory=fspath(tmpdir), + symbols_path="symbols_path", + stackwalk_binary=fspath(stackwalk), + dump_save_path=None, + test_name=None, + quiet=True, + ): + return mozcrash.check_for_crashes( + dump_directory, + symbols_path, + stackwalk_binary, + dump_save_path, + test_name, + quiet, + ) + + return wrapper + + +@pytest.fixture +def check_for_java_exception(): + def wrapper(logcat=None, test_name=None, quiet=True): + return mozcrash.check_for_java_exception(logcat, test_name, quiet) + + return wrapper + + +def minidump_files(request, tmpdir): + files = [] + + for i in range(getattr(request, "param", 1)): + name = uuid.uuid4() + + dmp = tmpdir.join("{}.dmp".format(name)) + dmp.write("foo") + + extra = tmpdir.join("{}.extra".format(name)) + + extra.write_text( + u""" +{ + "ContentSandboxLevel":"2", + "TelemetryEnvironment":"{🍪}", + "EMCheckCompatibility":"true", + "ProductName":"Firefox", + "ContentSandboxCapabilities":"119", + "TelemetryClientId":"", + "Vendor":"Mozilla", + "InstallTime":"1000000000", + "Theme":"classic/1.0", + "ReleaseChannel":"default", + "ServerURL":"https://crash-reports.mozilla.com", + "SafeMode":"0", + "ContentSandboxCapable":"1", + "useragent_locale":"en-US", + "Version":"55.0a1", + "BuildID":"20170512114708", + "ProductID":"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "MozCrashReason": "MOZ_CRASH()", + "TelemetryServerURL":"", + "DOMIPCEnabled":"1", + "Add-ons":"", + "CrashTime":"1494582646", + "UptimeTS":"14.9179586", + "ContentSandboxEnabled":"1", + "ProcessType":"content", + "StartupTime":"1000000000", + "URL":"about:home" +} + + """, + encoding="utf-8", + ) + + files.append({"dmp": dmp, "extra": extra}) + + return files + + +@pytest.fixture(name="minidump_files") +def minidump_files_fixture(request, tmpdir): + return minidump_files(request, tmpdir) + + +@pytest.fixture(autouse=True) +def mock_popen(monkeypatch): + """Generate a class that can mock subprocess.Popen. + + :param stdouts: Iterable that should return an iterable for the + stdout of each process in turn. + """ + + class MockPopen(object): + def __init__(self, args, *args_rest, **kwargs): + # all_popens.append(self) + self.args = args + self.returncode = 0 + + def communicate(self): + return (u"Stackwalk command: {}".format(" ".join(self.args)), "") + + def wait(self): + return self.returncode + + monkeypatch.setattr(mozcrash.mozcrash.subprocess, "Popen", MockPopen) diff --git a/testing/mozbase/mozcrash/tests/manifest.ini b/testing/mozbase/mozcrash/tests/manifest.ini new file mode 100644 index 0000000000..5a7e75b832 --- /dev/null +++ b/testing/mozbase/mozcrash/tests/manifest.ini @@ -0,0 +1,7 @@ +[DEFAULT] +subsuite = mozbase +[test_basic.py] +[test_java_exception.py] +[test_save_path.py] +[test_stackwalk.py] +[test_symbols_path.py] diff --git a/testing/mozbase/mozcrash/tests/test_basic.py b/testing/mozbase/mozcrash/tests/test_basic.py new file mode 100644 index 0000000000..157a5785c1 --- /dev/null +++ b/testing/mozbase/mozcrash/tests/test_basic.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# coding=UTF-8 + +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() diff --git a/testing/mozbase/mozcrash/tests/test_java_exception.py b/testing/mozbase/mozcrash/tests/test_java_exception.py new file mode 100644 index 0000000000..f48ce2cc3e --- /dev/null +++ b/testing/mozbase/mozcrash/tests/test_java_exception.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# coding=UTF-8 + +import mozunit +import pytest + + +@pytest.fixture +def test_log(): + return [ + "01-30 20:15:41.937 E/GeckoAppShell( 1703): >>> " + 'REPORTING UNCAUGHT EXCEPTION FROM THREAD 9 ("GeckoBackgroundThread")', + "01-30 20:15:41.937 E/GeckoAppShell( 1703): java.lang.NullPointerException", + "01-30 20:15:41.937 E/GeckoAppShell( 1703):" + " at org.mozilla.gecko.GeckoApp$21.run(GeckoApp.java:1833)", + "01-30 20:15:41.937 E/GeckoAppShell( 1703):" + " at android.os.Handler.handleCallback(Handler.java:587)", + ] + + +def test_uncaught_exception(check_for_java_exception, test_log): + """Test for an exception which should be caught.""" + assert 1 == check_for_java_exception(test_log) + + +def test_truncated_exception(check_for_java_exception, test_log): + """Test for an exception which should be caught which was truncated.""" + truncated_log = list(test_log) + truncated_log[0], truncated_log[1] = truncated_log[1], truncated_log[0] + + assert 1 == check_for_java_exception(truncated_log) + + +def test_unchecked_exception(check_for_java_exception, test_log): + """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() diff --git a/testing/mozbase/mozcrash/tests/test_save_path.py b/testing/mozbase/mozcrash/tests/test_save_path.py new file mode 100644 index 0000000000..fad83ab71b --- /dev/null +++ b/testing/mozbase/mozcrash/tests/test_save_path.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +import os + +import mozunit +import pytest +from conftest import fspath + + +def test_save_path_not_present(check_for_crashes, minidump_files, tmpdir): + """Test that dump_save_path works when the directory doesn't exist.""" + save_path = tmpdir.join("saved") + + assert 1 == check_for_crashes(dump_save_path=fspath(save_path)) + + assert save_path.join(minidump_files[0]["dmp"].basename).check() + assert save_path.join(minidump_files[0]["extra"].basename).check() + + +def test_save_path(check_for_crashes, minidump_files, tmpdir): + """Test that dump_save_path works.""" + save_path = tmpdir.mkdir("saved") + + assert 1 == check_for_crashes(dump_save_path=fspath(save_path)) + + assert save_path.join(minidump_files[0]["dmp"].basename).check() + assert save_path.join(minidump_files[0]["extra"].basename).check() + + +def test_save_path_isfile(check_for_crashes, minidump_files, tmpdir): + """Test that dump_save_path works when the path is a file and not a directory.""" + save_path = tmpdir.join("saved") + save_path.write("junk") + + assert 1 == check_for_crashes(dump_save_path=fspath(save_path)) + + assert save_path.join(minidump_files[0]["dmp"].basename).check() + assert save_path.join(minidump_files[0]["extra"].basename).check() + + +def test_save_path_envvar(check_for_crashes, minidump_files, tmpdir): + """Test that the MINDUMP_SAVE_PATH environment variable works.""" + save_path = tmpdir.mkdir("saved") + + os.environ["MINIDUMP_SAVE_PATH"] = fspath(save_path) + try: + assert 1 == check_for_crashes(dump_save_path=None) + finally: + del os.environ["MINIDUMP_SAVE_PATH"] + + assert save_path.join(minidump_files[0]["dmp"].basename).check() + assert save_path.join(minidump_files[0]["extra"].basename).check() + + +@pytest.mark.parametrize("minidump_files", [3], indirect=True) +def test_save_multiple(check_for_crashes, minidump_files, tmpdir): + """Test that all minidumps are saved.""" + save_path = tmpdir.mkdir("saved") + + assert 3 == check_for_crashes(dump_save_path=fspath(save_path)) + + for i in range(3): + assert save_path.join(minidump_files[i]["dmp"].basename).check() + assert save_path.join(minidump_files[i]["extra"].basename).check() + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozcrash/tests/test_stackwalk.py b/testing/mozbase/mozcrash/tests/test_stackwalk.py new file mode 100644 index 0000000000..7be2d82c10 --- /dev/null +++ b/testing/mozbase/mozcrash/tests/test_stackwalk.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# coding=UTF-8 + +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() diff --git a/testing/mozbase/mozcrash/tests/test_symbols_path.py b/testing/mozbase/mozcrash/tests/test_symbols_path.py new file mode 100644 index 0000000000..9aa281bd31 --- /dev/null +++ b/testing/mozbase/mozcrash/tests/test_symbols_path.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# coding=UTF-8 + +import zipfile + +import mozhttpd +import mozunit +from conftest import fspath +from six import BytesIO +from six.moves.urllib.parse import urlunsplit + + +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(): + zdata = BytesIO() + z = zipfile.ZipFile(zdata, "w") + z.writestr("symbols.txt", "abc/xyz") + z.close() + return zdata.getvalue() + + def get_symbols(req): + data["retrieved"] = True + + headers = {} + return (200, headers, make_zipfile()) + + httpd = mozhttpd.MozHttpd( + port=0, + urlhandlers=[{"method": "GET", "path": "/symbols", "function": get_symbols}], + ) + httpd.start() + symbol_url = urlunsplit( + ("http", "%s:%d" % httpd.httpd.server_address, "/symbols", "", "") + ) + + assert 1 == check_for_crashes(symbols_path=symbol_url) + assert data["retrieved"] + + +def test_symbols_retry(check_for_crashes, minidump_files): + """Test that passing a URL as symbols_path succeeds on retry after temporary HTTP failure.""" + data = {"retrieved": False} + get_symbols_calls = 0 + + def make_zipfile(): + zdata = BytesIO() + z = zipfile.ZipFile(zdata, "w") + z.writestr("symbols.txt", "abc/xyz") + z.close() + return zdata.getvalue() + + def get_symbols(req): + nonlocal get_symbols_calls + data["retrieved"] = True + if get_symbols_calls > 0: + ret = 200 + else: + ret = 504 + get_symbols_calls += 1 + + headers = {} + return (ret, headers, make_zipfile()) + + httpd = mozhttpd.MozHttpd( + port=0, + urlhandlers=[{"method": "GET", "path": "/symbols", "function": get_symbols}], + ) + httpd.start() + symbol_url = urlunsplit( + ("http", "%s:%d" % httpd.httpd.server_address, "/symbols", "", "") + ) + + assert 1 == check_for_crashes(symbols_path=symbol_url) + assert data["retrieved"] + assert 2 == get_symbols_calls + + +if __name__ == "__main__": + mozunit.main() |