tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

detect_crash.py (3670B)


      1 import json
      2 import tempfile
      3 import time
      4 from copy import deepcopy
      5 from pathlib import Path
      6 
      7 import pytest
      8 from webdriver import error
      9 
     10 
     11 def test_content_process(configuration, geckodriver):
     12    def trigger_crash(driver):
     13        # The crash is delayed and happens after this command finished.
     14        driver.session.url = "about:crashcontent"
     15 
     16        # Bug 1943038: geckodriver fails to detect minidump files for content
     17        # crashes when the next command is sent immediately.
     18        time.sleep(1)
     19 
     20        # Send another command that should fail
     21        with pytest.raises(error.UnknownErrorException):
     22            driver.session.url
     23 
     24    run_crash_test(configuration, geckodriver, crash_callback=trigger_crash)
     25 
     26 
     27 def test_parent_process(configuration, geckodriver):
     28    def trigger_crash(driver):
     29        with pytest.raises(error.UnknownErrorException):
     30            driver.session.url = "about:crashparent"
     31 
     32    run_crash_test(configuration, geckodriver, crash_callback=trigger_crash)
     33 
     34 
     35 def run_crash_test(configuration, geckodriver, crash_callback):
     36    config = deepcopy(configuration)
     37    config["capabilities"]["webSocketUrl"] = True
     38 
     39    with tempfile.TemporaryDirectory() as tmpdirname:
     40        # Use a custom temporary minidump save path to only see
     41        # the minidump files related to this test
     42        driver = geckodriver(
     43            config=config, extra_env={"MINIDUMP_SAVE_PATH": tmpdirname}
     44        )
     45 
     46        driver.new_session()
     47        profile_minidump_path = (
     48            Path(driver.session.capabilities["moz:profile"]) / "minidumps"
     49        )
     50 
     51        crash_callback(driver)
     52 
     53        tmp_minidump_dir = Path(tmpdirname)
     54        file_map = verify_minidump_files(tmp_minidump_dir)
     55 
     56        # Check that for both Marionette and Remote Agent the annotations are present
     57        extra_data = read_extra_file(file_map[".extra"])
     58        assert extra_data.get("Marionette") == "1", (
     59            "Marionette entry is missing or invalid"
     60        )
     61        assert extra_data.get("RemoteAgent") == "1", (
     62            "RemoteAgent entry is missing or invalid"
     63        )
     64 
     65        # Remove original minidump files from the profile directory
     66        remove_files(profile_minidump_path, file_map.values())
     67 
     68 
     69 def read_extra_file(path):
     70    """Read and parse the minidump's .extra file."""
     71    try:
     72        with path.open("rb") as file:
     73            data = file.read()
     74 
     75            # Try to decode first and replace invalid utf-8 characters.
     76            decoded = data.decode("utf-8", errors="replace")
     77 
     78            return json.loads(decoded)
     79    except json.JSONDecodeError as e:
     80        raise ValueError(f"Invalid JSON in {path}: {e}")
     81 
     82 
     83 def remove_files(directory, files):
     84    """Safely remove a list of files."""
     85    for file in files:
     86        file_path = Path(directory) / file.name
     87        try:
     88            file_path.unlink()
     89        except FileNotFoundError:
     90            print(f"File not found: {file_path}")
     91        except PermissionError as e:
     92            raise ValueError(f"Permission error removing {file_path}: {e}")
     93 
     94 
     95 def verify_minidump_files(directory):
     96    """Verify that .dmp and .extra files exist and return their paths."""
     97    minidump_files = list(Path(directory).iterdir())
     98    assert len(minidump_files) == 2, f"Expected 2 files, found {minidump_files}."
     99 
    100    required_extensions = {".dmp", ".extra"}
    101    file_map = {
    102        file.suffix: file
    103        for file in minidump_files
    104        if file.suffix in required_extensions
    105    }
    106 
    107    missing_extensions = required_extensions - file_map.keys()
    108    assert not missing_extensions, (
    109        f"Missing required files with extensions: {missing_extensions}"
    110    )
    111 
    112    return file_map