5 from collections
import defaultdict
8 from mozbuild
.base
import MozbuildObject
9 from mozlint
.parser
import Parser
10 from mozlint
.pathutils
import findobject
11 from mozlint
.result
import ResultSummary
12 from mozlog
.structuredlog
import StructuredLogger
13 from mozpack
import path
15 here
= path
.abspath(path
.dirname(__file__
))
16 build
= MozbuildObject
.from_environment(cwd
=here
, virtualenv_name
="python-test")
18 lintdir
= path
.dirname(here
)
19 sys
.path
.insert(0, lintdir
)
20 logger
= logging
.getLogger("mozlint")
23 def pytest_generate_tests(metafunc
):
24 """Finds, loads and returns the config for the linter name specified by the
25 LINTER global variable in the calling module.
27 This implies that each test file (that uses this fixture) should only be
28 used to test a single linter. If no LINTER variable is defined, the test
31 if "config" in metafunc
.fixturenames
:
32 if not hasattr(metafunc
.module
, "LINTER"):
34 "'config' fixture used from a module that didn't set the LINTER variable"
37 name
= metafunc
.module
.LINTER
38 config_path
= path
.join(lintdir
, "{}.yml".format(name
))
39 parser
= Parser(build
.topsrcdir
)
40 configs
= parser
.parse(config_path
)
41 config_names
= {config
["name"] for config
in configs
}
43 marker
= metafunc
.definition
.get_closest_marker("lint_config")
45 config_name
= marker
.kwargs
["name"]
46 if config_name
not in config_names
:
47 pytest
.fail(f
"lint config {config_name} not present in {name}.yml")
49 config
for config
in configs
if config
["name"] == marker
.kwargs
["name"]
52 ids
= [config
["name"] for config
in configs
]
53 metafunc
.parametrize("config", configs
, ids
=ids
)
56 @pytest.fixture(scope
="module")
58 """Return the root directory for the files of the linter under test.
60 For example, with LINTER=flake8 this would be tools/lint/test/files/flake8.
62 if not hasattr(request
.module
, "LINTER"):
64 "'root' fixture used from a module that didn't set the LINTER variable"
66 return path
.join(here
, "files", request
.module
.LINTER
)
69 @pytest.fixture(scope
="module")
71 """Return a function that can resolve file paths relative to the linter
74 Can be used like `paths('foo.py', 'bar/baz')`. This will return a list of
75 absolute paths under the `root` files directory.
81 return [path
.normpath(path
.join(root
, p
)) for p
in paths
]
86 @pytest.fixture(autouse
=True)
87 def run_setup(config
):
88 """Make sure that if the linter named in the LINTER global variable has a
89 setup function, it gets called before running the tests.
91 if "setup" not in config
:
94 if config
["name"] == "clang-format":
95 # Skip the setup for the clang-format linter, as it requires a Mach context
96 # (which we may not have if pytest is invoked directly).
99 log
= logging
.LoggerAdapter(
100 logger
, {"lintname": config
.get("name"), "pid": os
.getpid()}
103 func
= findobject(config
["setup"])
106 virtualenv_manager
=build
.virtualenv_manager
,
107 virtualenv_bin_path
=build
.virtualenv_manager
.bin_path
,
113 def lint(config
, root
, request
):
114 """Find and return the 'lint' function for the external linter named in the
115 LINTER global variable.
117 This will automatically pass in the 'config' and 'root' arguments if not
121 if hasattr(request
.module
, "fixed"):
122 request
.module
.fixed
= 0
125 func
= findobject(config
["payload"])
126 except (ImportError, ValueError):
128 "could not resolve a lint function from '{}'".format(config
["payload"])
131 ResultSummary
.root
= root
133 def wrapper(paths
, config
=config
, root
=root
, collapse_results
=False, **lintargs
):
134 logger
.setLevel(logging
.DEBUG
)
135 lintargs
["log"] = logging
.LoggerAdapter(
136 logger
, {"lintname": config
.get("name"), "pid": os
.getpid()}
139 results
= func(paths
, config
, root
=root
, **lintargs
)
140 if hasattr(request
.module
, "fixed") and isinstance(results
, dict):
141 request
.module
.fixed
+= results
["fixed"]
143 if isinstance(results
, dict):
144 results
= results
["results"]
146 if isinstance(results
, (list, tuple)):
147 results
= sorted(results
)
149 if not collapse_results
:
152 ret
= defaultdict(list)
154 ret
[r
.relpath
].append(r
)
161 def structuredlog_lint(config
, root
, logger
=None):
162 """Find and return the 'lint' function for the external linter named in the
163 LINTER global variable. This variant of the lint function is for linters that
164 use the 'structuredlog' type.
166 This will automatically pass in the 'config' and 'root' arguments if not
170 func
= findobject(config
["payload"])
171 except (ImportError, ValueError):
173 "could not resolve a lint function from '{}'".format(config
["payload"])
176 ResultSummary
.root
= root
179 logger
= structured_logger()
186 collapse_results
=False,
189 lintargs
["log"] = logging
.LoggerAdapter(
190 logger
, {"lintname": config
.get("name"), "pid": os
.getpid()}
192 results
= func(paths
, config
, root
=root
, logger
=logger
, **lintargs
)
193 if not collapse_results
:
196 ret
= defaultdict(list)
198 ret
[r
.path
].append(r
)
205 def global_lint(config
, root
, request
):
207 func
= findobject(config
["payload"])
208 except (ImportError, ValueError):
210 "could not resolve a lint function from '{}'".format(config
["payload"])
213 ResultSummary
.root
= root
215 def wrapper(config
=config
, root
=root
, collapse_results
=False, **lintargs
):
216 logger
.setLevel(logging
.DEBUG
)
217 lintargs
["log"] = logging
.LoggerAdapter(
218 logger
, {"lintname": config
.get("name"), "pid": os
.getpid()}
220 results
= func(config
, root
=root
, **lintargs
)
221 if hasattr(request
.module
, "fixed") and isinstance(results
, dict):
222 request
.module
.fixed
+= results
["fixed"]
224 if isinstance(results
, dict):
225 results
= results
["results"]
227 if isinstance(results
, (list, tuple)):
228 results
= sorted(results
)
230 if not collapse_results
:
233 ret
= defaultdict(list)
235 ret
[r
.relpath
].append(r
)
242 def create_temp_file(tmpdir
):
243 def inner(contents
, name
=None):
244 name
= name
or "temp.py"
245 path
= tmpdir
.join(name
)
253 def structured_logger():
254 return StructuredLogger("logger")
258 def perfdocs_sample():
259 from test_perfdocs
import (
260 DYNAMIC_SAMPLE_CONFIG
,
263 SAMPLE_METRICS_CONFIG
,
269 with
temp_dir() as tmpdir
:
270 suite_dir
= pathlib
.Path(tmpdir
, "suite")
271 raptor_dir
= pathlib
.Path(tmpdir
, "raptor")
272 raptor_suitedir
= pathlib
.Path(tmpdir
, "raptor", "suite")
273 raptor_another_suitedir
= pathlib
.Path(tmpdir
, "raptor", "another_suite")
274 perfdocs_dir
= pathlib
.Path(tmpdir
, "perfdocs")
276 perfdocs_dir
.mkdir(parents
=True, exist_ok
=True)
277 suite_dir
.mkdir(parents
=True, exist_ok
=True)
278 raptor_dir
.mkdir(parents
=True, exist_ok
=True)
279 raptor_suitedir
.mkdir(parents
=True, exist_ok
=True)
280 raptor_another_suitedir
.mkdir(parents
=True, exist_ok
=True)
283 "perftest.toml", tempdir
=suite_dir
, content
='["perftest_sample.js"]'
284 ) as tmpmanifest
, temp_file(
285 "raptor_example1.ini", tempdir
=raptor_suitedir
, content
=SAMPLE_INI
286 ) as tmpexample1manifest
, temp_file(
287 "raptor_example2.ini", tempdir
=raptor_another_suitedir
, content
=SAMPLE_INI
288 ) as tmpexample2manifest
, temp_file(
289 "perftest_sample.js", tempdir
=suite_dir
, content
=SAMPLE_TEST
290 ) as tmptest
, temp_file(
291 "config.yml", tempdir
=perfdocs_dir
, content
=SAMPLE_CONFIG
292 ) as tmpconfig
, temp_file(
293 "config_2.yml", tempdir
=perfdocs_dir
, content
=DYNAMIC_SAMPLE_CONFIG
294 ) as tmpconfig_2
, temp_file(
295 "config_metrics.yml", tempdir
=perfdocs_dir
, content
=SAMPLE_METRICS_CONFIG
296 ) as tmpconfig_metrics
, temp_file(
298 tempdir
=perfdocs_dir
,
299 content
="{metrics_rst_name}{documentation}",
303 "manifest": {"path": tmpmanifest
},
304 "example1_manifest": tmpexample1manifest
,
305 "example2_manifest": tmpexample2manifest
,
308 "config_2": tmpconfig_2
,
309 "config_metrics": tmpconfig_metrics
,