File: detect_crash.py

package info (click to toggle)
firefox 143.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,617,328 kB
  • sloc: cpp: 7,478,492; javascript: 6,417,157; ansic: 3,720,058; python: 1,396,372; xml: 627,523; asm: 438,677; java: 186,156; sh: 63,477; makefile: 19,171; objc: 13,059; perl: 12,983; yacc: 4,583; cs: 3,846; pascal: 3,405; lex: 1,720; ruby: 1,003; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 53; csh: 10
file content (112 lines) | stat: -rw-r--r-- 3,670 bytes parent folder | download | duplicates (13)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import json
import tempfile
import time
from copy import deepcopy
from pathlib import Path

import pytest
from webdriver import error


def test_content_process(configuration, geckodriver):
    def trigger_crash(driver):
        # The crash is delayed and happens after this command finished.
        driver.session.url = "about:crashcontent"

        # Bug 1943038: geckodriver fails to detect minidump files for content
        # crashes when the next command is sent immediately.
        time.sleep(1)

        # Send another command that should fail
        with pytest.raises(error.UnknownErrorException):
            driver.session.url

    run_crash_test(configuration, geckodriver, crash_callback=trigger_crash)


def test_parent_process(configuration, geckodriver):
    def trigger_crash(driver):
        with pytest.raises(error.UnknownErrorException):
            driver.session.url = "about:crashparent"

    run_crash_test(configuration, geckodriver, crash_callback=trigger_crash)


def run_crash_test(configuration, geckodriver, crash_callback):
    config = deepcopy(configuration)
    config["capabilities"]["webSocketUrl"] = True

    with tempfile.TemporaryDirectory() as tmpdirname:
        # Use a custom temporary minidump save path to only see
        # the minidump files related to this test
        driver = geckodriver(
            config=config, extra_env={"MINIDUMP_SAVE_PATH": tmpdirname}
        )

        driver.new_session()
        profile_minidump_path = (
            Path(driver.session.capabilities["moz:profile"]) / "minidumps"
        )

        crash_callback(driver)

        tmp_minidump_dir = Path(tmpdirname)
        file_map = verify_minidump_files(tmp_minidump_dir)

        # Check that for both Marionette and Remote Agent the annotations are present
        extra_data = read_extra_file(file_map[".extra"])
        assert (
            extra_data.get("Marionette") == "1"
        ), "Marionette entry is missing or invalid"
        assert (
            extra_data.get("RemoteAgent") == "1"
        ), "RemoteAgent entry is missing or invalid"

        # Remove original minidump files from the profile directory
        remove_files(profile_minidump_path, file_map.values())


def read_extra_file(path):
    """Read and parse the minidump's .extra file."""
    try:
        with path.open("rb") as file:
            data = file.read()

            # Try to decode first and replace invalid utf-8 characters.
            decoded = data.decode("utf-8", errors="replace")

            return json.loads(decoded)
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON in {path}: {e}")


def remove_files(directory, files):
    """Safely remove a list of files."""
    for file in files:
        file_path = Path(directory) / file.name
        try:
            file_path.unlink()
        except FileNotFoundError:
            print(f"File not found: {file_path}")
        except PermissionError as e:
            raise ValueError(f"Permission error removing {file_path}: {e}")


def verify_minidump_files(directory):
    """Verify that .dmp and .extra files exist and return their paths."""
    minidump_files = list(Path(directory).iterdir())
    assert len(minidump_files) == 2, f"Expected 2 files, found {minidump_files}."

    required_extensions = {".dmp", ".extra"}
    file_map = {
        file.suffix: file
        for file in minidump_files
        if file.suffix in required_extensions
    }

    missing_extensions = required_extensions - file_map.keys()
    assert (
        not missing_extensions
    ), f"Missing required files with extensions: {missing_extensions}"

    return file_map