tor-browser

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

test_roller.py (12864B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import os
      6 import platform
      7 import signal
      8 import subprocess
      9 import sys
     10 import time
     11 from itertools import chain
     12 
     13 import mozunit
     14 import pytest
     15 
     16 from mozlint.errors import LintersNotConfigured, NoValidLinter
     17 from mozlint.result import Issue, ResultSummary
     18 from mozlint.roller import LintRoller
     19 
     20 here = os.path.abspath(os.path.dirname(__file__))
     21 
     22 
     23 def test_roll_no_linters_configured(lint, files):
     24    with pytest.raises(LintersNotConfigured):
     25        lint.roll(files)
     26 
     27 
     28 def test_roll_successful(lint, linters, files):
     29    lint.read(linters("string", "regex", "external"))
     30 
     31    result = lint.roll(files)
     32    assert len(result.issues) == 1
     33    assert result.failed == set([])
     34 
     35    path = list(result.issues.keys())[0]
     36    assert os.path.basename(path) == "foobar.js"
     37 
     38    errors = result.issues[path]
     39    assert isinstance(errors, list)
     40    assert len(errors) == 6
     41 
     42    container = errors[0]
     43    assert isinstance(container, Issue)
     44    assert container.rule == "no-foobar"
     45 
     46 
     47 def test_roll_from_subdir(lint, linters):
     48    lint.read(linters("string", "regex", "external"))
     49 
     50    oldcwd = os.getcwd()
     51    try:
     52        os.chdir(os.path.join(lint.root, "files"))
     53 
     54        # Path relative to cwd works
     55        result = lint.roll("foobar.js")
     56        assert len(result.issues) == 1
     57        assert len(result.failed) == 0
     58        assert result.returncode == 1
     59 
     60        # Path relative to root doesn't work
     61        result = lint.roll(os.path.join("files", "foobar.js"))
     62        assert len(result.issues) == 0
     63        assert len(result.failed) == 0
     64        assert result.returncode == 0
     65 
     66        # Paths from vcs are always joined to root instead of cwd
     67        lint.mock_vcs([os.path.join("files", "foobar.js")])
     68        result = lint.roll(outgoing=True)
     69        assert len(result.issues) == 1
     70        assert len(result.failed) == 0
     71        assert result.returncode == 1
     72 
     73        result = lint.roll(workdir=True)
     74        assert len(result.issues) == 1
     75        assert len(result.failed) == 0
     76        assert result.returncode == 1
     77 
     78        result = lint.roll(rev='not public() and keyword("dummy revset expression")')
     79        assert len(result.issues) == 1
     80        assert len(result.failed) == 0
     81        assert result.returncode == 1
     82    finally:
     83        os.chdir(oldcwd)
     84 
     85 
     86 def test_roll_catch_exception(lint, linters, files, capfd):
     87    lint.read(linters("raises"))
     88 
     89    lint.roll(files)  # assert not raises
     90    out, err = capfd.readouterr()
     91    assert "LintException" in err
     92 
     93 
     94 def test_roll_with_global_excluded_path(lint, linters, files):
     95    lint.exclude = ["**/foobar.js"]
     96    lint.read(linters("string", "regex", "external"))
     97    result = lint.roll(files)
     98 
     99    assert len(result.issues) == 0
    100    assert result.failed == set([])
    101 
    102 
    103 def test_roll_with_local_excluded_path(lint, linters, files):
    104    lint.read(linters("excludes"))
    105    result = lint.roll(files)
    106 
    107    assert "**/foobar.js" in lint.linters[0]["local_exclude"]
    108    assert len(result.issues) == 0
    109    assert result.failed == set([])
    110 
    111 
    112 def test_roll_with_no_files_to_lint(lint, linters, capfd):
    113    lint.read(linters("string", "regex", "external"))
    114    lint.mock_vcs([])
    115    result = lint.roll([], workdir=True)
    116    assert isinstance(result, ResultSummary)
    117    assert len(result.issues) == 0
    118    assert len(result.failed) == 0
    119 
    120    out, err = capfd.readouterr()
    121    assert "WARNING: no files linted" in err
    122 
    123 
    124 def test_roll_with_invalid_extension(lint, linters, filedir):
    125    lint.read(linters("external"))
    126    result = lint.roll(os.path.join(filedir, "foobar.py"))
    127    assert len(result.issues) == 0
    128    assert result.failed == set([])
    129 
    130 
    131 def test_roll_with_failure_code(lint, linters, files):
    132    lint.read(linters("badreturncode"))
    133 
    134    result = lint.roll(files, num_procs=1)
    135    assert len(result.issues) == 0
    136    assert result.failed == set(["BadReturnCodeLinter"])
    137 
    138 
    139 def test_roll_warnings(lint, linters, files):
    140    lint.read(linters("warning"))
    141    result = lint.roll(files)
    142    assert len(result.issues) == 0
    143    assert result.total_issues == 0
    144    assert len(result.suppressed_warnings) == 1
    145    assert result.total_suppressed_warnings == 2
    146 
    147    lint.lintargs["show_warnings"] = True
    148    result = lint.roll(files)
    149    assert len(result.issues) == 1
    150    assert result.total_issues == 2
    151    assert len(result.suppressed_warnings) == 0
    152    assert result.total_suppressed_warnings == 0
    153 
    154 
    155 def test_roll_code_review(monkeypatch, linters, files):
    156    monkeypatch.setenv("CODE_REVIEW", "1")
    157    lint = LintRoller(root=here, show_warnings=False)
    158    lint.read(linters("warning"))
    159    result = lint.roll(files)
    160    assert len(result.issues) == 1
    161    assert result.total_issues == 2
    162    assert len(result.suppressed_warnings) == 0
    163    assert result.total_suppressed_warnings == 0
    164    assert result.returncode == 1
    165 
    166 
    167 def test_roll_code_review_warnings_disabled(monkeypatch, linters, files):
    168    monkeypatch.setenv("CODE_REVIEW", "1")
    169    lint = LintRoller(root=here, show_warnings=False)
    170    lint.read(linters("warning_no_code_review"))
    171    result = lint.roll(files)
    172    assert len(result.issues) == 0
    173    assert result.total_issues == 0
    174    assert lint.result.fail_on_warnings is True
    175    assert len(result.suppressed_warnings) == 1
    176    assert result.total_suppressed_warnings == 2
    177    assert result.returncode == 0
    178 
    179 
    180 def test_roll_code_review_warnings_soft(linters, files):
    181    lint = LintRoller(root=here, show_warnings="soft")
    182    lint.read(linters("warning_no_code_review"))
    183    result = lint.roll(files)
    184    assert len(result.issues) == 1
    185    assert result.total_issues == 2
    186    assert lint.result.fail_on_warnings is False
    187    assert len(result.suppressed_warnings) == 0
    188    assert result.total_suppressed_warnings == 0
    189    assert result.returncode == 0
    190 
    191 
    192 def fake_run_worker(config, paths, **lintargs):
    193    result = ResultSummary(lintargs["root"])
    194    result.issues["count"].append(1)
    195    return result
    196 
    197 
    198 @pytest.mark.skipif(
    199    platform.system() == "Windows",
    200    reason="monkeypatch issues with multiprocessing on Windows",
    201 )
    202 @pytest.mark.parametrize("num_procs", [1, 4, 8, 16])
    203 def test_number_of_jobs(monkeypatch, lint, linters, files, num_procs):
    204    monkeypatch.setattr(sys.modules[lint.__module__], "_run_worker", fake_run_worker)
    205 
    206    linters = linters("string", "regex", "external")
    207    lint.read(linters)
    208    num_jobs = len(lint.roll(files, num_procs=num_procs).issues["count"])
    209 
    210    if len(files) >= num_procs:
    211        assert num_jobs == num_procs * len(linters)
    212    else:
    213        assert num_jobs == len(files) * len(linters)
    214 
    215 
    216 @pytest.mark.skipif(
    217    platform.system() == "Windows",
    218    reason="monkeypatch issues with multiprocessing on Windows",
    219 )
    220 @pytest.mark.parametrize("max_paths,expected_jobs", [(1, 12), (4, 6), (16, 6)])
    221 def test_max_paths_per_job(monkeypatch, lint, linters, files, max_paths, expected_jobs):
    222    monkeypatch.setattr(sys.modules[lint.__module__], "_run_worker", fake_run_worker)
    223 
    224    files = files[:4]
    225    assert len(files) == 4
    226 
    227    linters = linters("string", "regex", "external")[:3]
    228    assert len(linters) == 3
    229 
    230    lint.MAX_PATHS_PER_JOB = max_paths
    231    lint.read(linters)
    232    num_jobs = len(lint.roll(files, num_procs=2).issues["count"])
    233    assert num_jobs == expected_jobs
    234 
    235 
    236 @pytest.mark.skipif(
    237    platform.system() == "Windows",
    238    reason="monkeypatch issues with multiprocessing on Windows",
    239 )
    240 @pytest.mark.parametrize("num_procs", [1, 4, 8, 16])
    241 def test_number_of_jobs_global(monkeypatch, lint, linters, files, num_procs):
    242    monkeypatch.setattr(sys.modules[lint.__module__], "_run_worker", fake_run_worker)
    243 
    244    linters = linters("global")
    245    lint.read(linters)
    246    num_jobs = len(lint.roll(files, num_procs=num_procs).issues["count"])
    247 
    248    assert num_jobs == 1
    249 
    250 
    251 @pytest.mark.skipif(
    252    platform.system() == "Windows",
    253    reason="monkeypatch issues with multiprocessing on Windows",
    254 )
    255 @pytest.mark.parametrize("max_paths", [1, 4, 16])
    256 def test_max_paths_per_job_global(monkeypatch, lint, linters, files, max_paths):
    257    monkeypatch.setattr(sys.modules[lint.__module__], "_run_worker", fake_run_worker)
    258 
    259    files = files[:4]
    260    assert len(files) == 4
    261 
    262    linters = linters("global")[:1]
    263    assert len(linters) == 1
    264 
    265    lint.MAX_PATHS_PER_JOB = max_paths
    266    lint.read(linters)
    267    num_jobs = len(lint.roll(files, num_procs=2).issues["count"])
    268    assert num_jobs == 1
    269 
    270 
    271 @pytest.mark.skipif(
    272    platform.system() == "Windows",
    273    reason="signal.CTRL_C_EVENT isn't causing a KeyboardInterrupt on Windows",
    274 )
    275 def test_keyboard_interrupt():
    276    # We use two linters so we'll have two jobs. One (string.yml) will complete
    277    # quickly. The other (slow.yml) will run slowly.  This way the first worker
    278    # will be be stuck blocking on the ProcessPoolExecutor._call_queue when the
    279    # signal arrives and the other still be doing work.
    280    cmd = [sys.executable, "runcli.py", "-l=string", "-l=slow", "files/foobar.js"]
    281    env = os.environ.copy()
    282    env["PYTHONPATH"] = os.pathsep.join(sys.path)
    283    proc = subprocess.Popen(
    284        cmd,
    285        stdout=subprocess.PIPE,
    286        stderr=subprocess.STDOUT,
    287        cwd=here,
    288        env=env,
    289        universal_newlines=True,
    290    )
    291    time.sleep(1)
    292    proc.send_signal(signal.SIGINT)
    293 
    294    out = proc.communicate()[0]
    295    print(out)
    296    assert "WARNING: \nnot all files were linted" in out
    297    assert "2 problems" in out
    298    assert "Traceback" not in out
    299 
    300 
    301 def test_support_files(lint, linters, filedir, monkeypatch, files):
    302    jobs = []
    303 
    304    # Replace the original _generate_jobs with a new one that simply
    305    # adds jobs to a list (and then doesn't return anything).
    306    orig_generate_jobs = lint._generate_jobs
    307 
    308    def fake_generate_jobs(*args, **kwargs):
    309        jobs.extend([job[1] for job in orig_generate_jobs(*args, **kwargs)])
    310        return []
    311 
    312    monkeypatch.setattr(lint, "_generate_jobs", fake_generate_jobs)
    313 
    314    linter_path = linters("support_files")[0]
    315    lint.read(linter_path)
    316    lint.root = filedir
    317 
    318    # Modified support files only lint entire root if --outgoing or --workdir
    319    # are used.
    320    path = os.path.join(filedir, "foobar.js")
    321    vcs_path = os.path.join(filedir, "foobar.py")
    322 
    323    lint.mock_vcs([vcs_path])
    324    lint.roll(path)
    325    actual_files = sorted(chain(*jobs))
    326    assert actual_files == [path]
    327 
    328    expected_files = sorted(files)
    329 
    330    jobs = []
    331    lint.roll(path, workdir=True)
    332    actual_files = sorted(chain(*jobs))
    333    assert actual_files == expected_files
    334 
    335    jobs = []
    336    lint.roll(path, outgoing=True)
    337    actual_files = sorted(chain(*jobs))
    338    assert actual_files == expected_files
    339 
    340    jobs = []
    341    lint.roll(path, rev='draft() and keyword("dummy revset expression")')
    342    actual_files = sorted(chain(*jobs))
    343    assert actual_files == expected_files
    344 
    345    # Lint config file is implicitly added as a support file
    346    lint.mock_vcs([linter_path])
    347    jobs = []
    348    lint.roll(path, outgoing=True, workdir=True)
    349    actual_files = sorted(chain(*jobs))
    350    assert actual_files == expected_files
    351 
    352    # Avoid linting the entire root when `--fix` is passed.
    353    lint.mock_vcs([vcs_path])
    354    lint.lintargs["fix"] = True
    355 
    356    jobs = []
    357    lint.roll(path, outgoing=True)
    358    actual_files = sorted(chain(*jobs))
    359    assert actual_files == sorted([path, vcs_path]), (
    360        "`--fix` with `--outgoing` on a `support-files` change should "
    361        "avoid linting the entire root."
    362    )
    363 
    364    jobs = []
    365    lint.roll(path, workdir=True)
    366    actual_files = sorted(chain(*jobs))
    367    assert actual_files == sorted([path, vcs_path]), (
    368        "`--fix` with `--workdir` on a `support-files` change should "
    369        "avoid linting the entire root."
    370    )
    371 
    372    jobs = []
    373    lint.roll(path, rev='draft() and keyword("dummy revset expression")')
    374    actual_files = sorted(chain(*jobs))
    375    assert actual_files == sorted([path, vcs_path]), (
    376        "`--fix` with `--rev` on a `support-files` change should "
    377        "avoid linting the entire root."
    378    )
    379 
    380 
    381 def test_setup(lint, linters, filedir, capfd):
    382    with pytest.raises(NoValidLinter):
    383        lint.setup()
    384 
    385    lint.read(linters("setup", "setupfailed", "setupraised", "setupskipped"))
    386    ret = lint.setup()
    387    assert ret == 1
    388 
    389    out, err = capfd.readouterr()
    390    assert "oh no setup failed" in err
    391    assert "ERROR: problem with lint setup, skipping" in err
    392    assert lint.result.failed_setup == set(["SetupFailedLinter", "SetupRaisedLinter"])
    393 
    394 
    395 def test_setup_all_skipped(lint, linters, filedir, capfd):
    396    lint.read(linters("setupskipped"))
    397    ret = lint.setup()
    398    assert ret == 1
    399 
    400    out, err = capfd.readouterr()
    401    assert "ERROR: all linters were skipped due to setup, nothing to do!" in err
    402    assert lint.result.failed_setup == set()
    403 
    404 
    405 if __name__ == "__main__":
    406    mozunit.main()