style: get close to full coverage of spack style
				
					
				
			Previous tests of `spack style` didn't really run the tools -- they just ensure that the commands worked enough to get coverage. This adds several real tests and ensures that we hit the corner cases in `spack style`. This also tests sucess as well as failure cases.
This commit is contained in:
		| @@ -38,6 +38,9 @@ def grouper(iterable, n, fillvalue=None): | |||||||
|         yield filter(None, group) |         yield filter(None, group) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | #: directory where spack style started, for relativizing paths | ||||||
|  | initial_working_dir = None | ||||||
|  | 
 | ||||||
| #: List of directories to exclude from checks. | #: List of directories to exclude from checks. | ||||||
| exclude_directories = [spack.paths.external_path] | exclude_directories = [spack.paths.external_path] | ||||||
| 
 | 
 | ||||||
| @@ -183,34 +186,39 @@ def setup_parser(subparser): | |||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def cwd_relative(path): | ||||||
|  |     """Translate prefix-relative path to current working directory-relative.""" | ||||||
|  |     return os.path.relpath(os.path.join(spack.paths.prefix, path), initial_working_dir) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def rewrite_and_print_output( | def rewrite_and_print_output( | ||||||
|     output, args, re_obj=re.compile(r"^(.+):([0-9]+):"), replacement=r"{0}:{1}:" |     output, args, re_obj=re.compile(r"^(.+):([0-9]+):"), replacement=r"{0}:{1}:" | ||||||
| ): | ): | ||||||
|     """rewrite ouput with <file>:<line>: format to respect path args""" |     """rewrite ouput with <file>:<line>: format to respect path args""" | ||||||
|     if args.root_relative or re_obj is None: |     # print results relative to current working directory | ||||||
|         # print results relative to repo root. |     def translate(match): | ||||||
|         for line in output.split("\n"): |         return replacement.format( | ||||||
|             print("  " + output) |             cwd_relative(match.group(1)), *list(match.groups()[1:]) | ||||||
|     else: |         ) | ||||||
|         # print results relative to current working directory |  | ||||||
|         def cwd_relative(path): |  | ||||||
|             return replacement.format( |  | ||||||
|                 os.path.relpath( |  | ||||||
|                     os.path.join(spack.paths.prefix, path.group(1)), os.getcwd() |  | ||||||
|                 ), |  | ||||||
|                 *list(path.groups()[1:]) |  | ||||||
|             ) |  | ||||||
| 
 | 
 | ||||||
|         for line in output.split("\n"): |     for line in output.split("\n"): | ||||||
|             if not line: |         if not line: | ||||||
|                 continue |             continue | ||||||
|             print("  " + re_obj.sub(cwd_relative, line)) |         if not args.root_relative and re_obj: | ||||||
|  |             line = re_obj.sub(translate, line) | ||||||
|  |         print("  " + line) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def print_style_header(file_list, args): | def print_style_header(file_list, args): | ||||||
|     tools = [tool for tool in tool_order if getattr(args, tool)] |     tools = [tool for tool in tool_order if getattr(args, tool)] | ||||||
|     tty.msg("Running style checks on spack:", "selected: " + ", ".join(tools)) |     tty.msg("Running style checks on spack:", "selected: " + ", ".join(tools)) | ||||||
|     tty.msg("Modified files:", *[filename.strip() for filename in file_list]) | 
 | ||||||
|  |     # translate modified paths to cwd_relative if needed | ||||||
|  |     paths = [filename.strip() for filename in file_list] | ||||||
|  |     if not args.root_relative: | ||||||
|  |         paths = [cwd_relative(filename) for filename in paths] | ||||||
|  | 
 | ||||||
|  |     tty.msg("Modified files:", *paths) | ||||||
|     sys.stdout.flush() |     sys.stdout.flush() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -308,6 +316,10 @@ def run_black(black_cmd, file_list, args): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def style(parser, args): | def style(parser, args): | ||||||
|  |     # save initial working directory for relativizing paths later | ||||||
|  |     global initial_working_dir | ||||||
|  |     initial_working_dir = os.getcwd() | ||||||
|  | 
 | ||||||
|     file_list = args.files |     file_list = args.files | ||||||
|     if file_list: |     if file_list: | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -1,77 +0,0 @@ | |||||||
| # Copyright 2013-2021 Lawrence Livermore National Security, LLC and other |  | ||||||
| # Spack Project Developers. See the top-level COPYRIGHT file for details. |  | ||||||
| # |  | ||||||
| # SPDX-License-Identifier: (Apache-2.0 OR MIT) |  | ||||||
| 
 |  | ||||||
| import argparse |  | ||||||
| import os |  | ||||||
| import pytest |  | ||||||
| import sys |  | ||||||
| 
 |  | ||||||
| from llnl.util.filesystem import FileFilter |  | ||||||
| 
 |  | ||||||
| import spack.paths |  | ||||||
| from spack.cmd.style import style, setup_parser, changed_files |  | ||||||
| from spack.repo import Repo |  | ||||||
| from spack.util.executable import which |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @pytest.fixture(scope="module") |  | ||||||
| def parser(): |  | ||||||
|     """Returns the parser for the ``flake8`` command""" |  | ||||||
|     parser = argparse.ArgumentParser() |  | ||||||
|     setup_parser(parser) |  | ||||||
|     return parser |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @pytest.fixture(scope="module") |  | ||||||
| def flake8_package(): |  | ||||||
|     """Flake8 only checks files that have been modified. |  | ||||||
|     This fixture makes a small change to the ``flake8`` |  | ||||||
|     mock package, yields the filename, then undoes the |  | ||||||
|     change on cleanup. |  | ||||||
|     """ |  | ||||||
|     repo = Repo(spack.paths.mock_packages_path) |  | ||||||
|     filename = repo.filename_for_package_name("flake8") |  | ||||||
|     package = FileFilter(filename) |  | ||||||
| 
 |  | ||||||
|     # Make the change |  | ||||||
|     package.filter("state = 'unmodified'", "state = 'modified'", string=True) |  | ||||||
| 
 |  | ||||||
|     yield filename |  | ||||||
| 
 |  | ||||||
|     # Undo the change |  | ||||||
|     package.filter("state = 'modified'", "state = 'unmodified'", string=True) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_changed_files(parser, flake8_package): |  | ||||||
|     args = parser.parse_args([]) |  | ||||||
| 
 |  | ||||||
|     # changed_files returns file paths relative to the root |  | ||||||
|     # directory of Spack. Convert to absolute file paths. |  | ||||||
|     files = changed_files(args) |  | ||||||
|     files = [os.path.join(spack.paths.prefix, path) for path in files] |  | ||||||
| 
 |  | ||||||
|     # There will likely be other files that have changed |  | ||||||
|     # when these tests are run |  | ||||||
|     assert flake8_package in files |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # As of flake8 3.0.0, Python 2.6 and 3.3 are no longer supported |  | ||||||
| # http://flake8.pycqa.org/en/latest/release-notes/3.0.0.html |  | ||||||
| @pytest.mark.skipif( |  | ||||||
|     sys.version_info[:2] <= (2, 6) or (3, 0) <= sys.version_info[:2] <= (3, 3), |  | ||||||
|     reason="flake8 no longer supports Python 2.6 or 3.3 and older", |  | ||||||
| ) |  | ||||||
| @pytest.mark.skipif(not which("flake8"), reason="flake8 is not installed.") |  | ||||||
| def test_flake8(parser, flake8_package): |  | ||||||
|     # Only test the flake8_package that we modified |  | ||||||
|     # Otherwise, the unit tests would fail every time |  | ||||||
|     # the flake8 tests fail |  | ||||||
|     args = parser.parse_args(["--no-mypy", flake8_package]) |  | ||||||
|     style(parser, args) |  | ||||||
|     # Get even more coverage |  | ||||||
|     args = parser.parse_args( |  | ||||||
|         ["--no-mypy", "--output", "--root-relative", flake8_package] |  | ||||||
|     ) |  | ||||||
|     style(parser, args) |  | ||||||
							
								
								
									
										152
									
								
								lib/spack/spack/test/cmd/style.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								lib/spack/spack/test/cmd/style.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | # Copyright 2013-2021 Lawrence Livermore National Security, LLC and other | ||||||
|  | # Spack Project Developers. See the top-level COPYRIGHT file for details. | ||||||
|  | # | ||||||
|  | # SPDX-License-Identifier: (Apache-2.0 OR MIT) | ||||||
|  | 
 | ||||||
|  | import os | ||||||
|  | import shutil | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | import pytest | ||||||
|  | 
 | ||||||
|  | from llnl.util.filesystem import FileFilter | ||||||
|  | 
 | ||||||
|  | import spack.main | ||||||
|  | import spack.paths | ||||||
|  | import spack.repo | ||||||
|  | from spack.cmd.style import changed_files | ||||||
|  | from spack.util.executable import which | ||||||
|  | 
 | ||||||
|  | style = spack.main.SpackCommand("style") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.fixture(scope="function") | ||||||
|  | def flake8_package(): | ||||||
|  |     """Style only checks files that have been modified. This fixture makes a small | ||||||
|  |     change to the ``flake8`` mock package, yields the filename, then undoes the | ||||||
|  |     change on cleanup. | ||||||
|  |     """ | ||||||
|  |     repo = spack.repo.Repo(spack.paths.mock_packages_path) | ||||||
|  |     filename = repo.filename_for_package_name("flake8") | ||||||
|  |     tmp = filename + ".tmp" | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         shutil.copy(filename, tmp) | ||||||
|  |         package = FileFilter(filename) | ||||||
|  |         package.filter("state = 'unmodified'", "state = 'modified'", string=True) | ||||||
|  |         yield filename | ||||||
|  |     finally: | ||||||
|  |         shutil.move(tmp, filename) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.fixture | ||||||
|  | def flake8_package_with_errors(scope="function"): | ||||||
|  |     """A flake8 package with errors.""" | ||||||
|  |     repo = spack.repo.Repo(spack.paths.mock_packages_path) | ||||||
|  |     filename = repo.filename_for_package_name("flake8") | ||||||
|  |     tmp = filename + ".tmp" | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         shutil.copy(filename, tmp) | ||||||
|  |         package = FileFilter(filename) | ||||||
|  |         package.filter("state = 'unmodified'", "state    =    'modified'", string=True) | ||||||
|  |         yield filename | ||||||
|  |     finally: | ||||||
|  |         shutil.move(tmp, filename) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_changed_files(flake8_package): | ||||||
|  |     # changed_files returns file paths relative to the root | ||||||
|  |     # directory of Spack. Convert to absolute file paths. | ||||||
|  |     files = [os.path.join(spack.paths.prefix, path) for path in changed_files()] | ||||||
|  | 
 | ||||||
|  |     # There will likely be other files that have changed | ||||||
|  |     # when these tests are run | ||||||
|  |     assert flake8_package in files | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_changed_files_all_files(flake8_package): | ||||||
|  |     # it's hard to guarantee "all files", so do some sanity checks. | ||||||
|  |     files = set([ | ||||||
|  |         os.path.join(spack.paths.prefix, path) | ||||||
|  |         for path in changed_files(all_files=True) | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  |     # spack has a lot of files -- check that we're in the right ballpark | ||||||
|  |     assert len(files) > 6000 | ||||||
|  | 
 | ||||||
|  |     # a builtin package | ||||||
|  |     zlib = spack.repo.path.get_pkg_class("zlib") | ||||||
|  |     assert zlib.module.__file__ in files | ||||||
|  | 
 | ||||||
|  |     # a core spack file | ||||||
|  |     assert os.path.join(spack.paths.module_path, "spec.py") in files | ||||||
|  | 
 | ||||||
|  |     # a mock package | ||||||
|  |     assert flake8_package in files | ||||||
|  | 
 | ||||||
|  |     # this test | ||||||
|  |     assert __file__ in files | ||||||
|  | 
 | ||||||
|  |     # ensure externals are excluded | ||||||
|  |     assert not any(f.startswith(spack.paths.external_path) for f in files) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # As of flake8 3.0.0, Python 2.6 and 3.3 are no longer supported | ||||||
|  | # http://flake8.pycqa.org/en/latest/release-notes/3.0.0.html | ||||||
|  | skip_old_python = pytest.mark.skipif( | ||||||
|  |     sys.version_info[:2] <= (2, 6) or (3, 0) <= sys.version_info[:2] <= (3, 3), | ||||||
|  |     reason="flake8 no longer supports Python 2.6 or 3.3 and older", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @skip_old_python | ||||||
|  | @pytest.mark.skipif(not which("flake8"), reason="flake8 is not installed.") | ||||||
|  | def test_style(flake8_package, tmpdir): | ||||||
|  |     root_relative = os.path.relpath(flake8_package, spack.paths.prefix) | ||||||
|  | 
 | ||||||
|  |     # use a working directory to test cwd-relative paths, as tests run in | ||||||
|  |     # the spack prefix by default | ||||||
|  |     with tmpdir.as_cwd(): | ||||||
|  |         relative = os.path.relpath(flake8_package) | ||||||
|  | 
 | ||||||
|  |         # no args | ||||||
|  |         output = style() | ||||||
|  |         assert relative in output | ||||||
|  |         assert "spack style checks were clean" in output | ||||||
|  | 
 | ||||||
|  |         # one specific arg | ||||||
|  |         output = style(flake8_package) | ||||||
|  |         assert relative in output | ||||||
|  |         assert "spack style checks were clean" in output | ||||||
|  | 
 | ||||||
|  |         # specific file that isn't changed | ||||||
|  |         output = style(__file__) | ||||||
|  |         assert relative not in output | ||||||
|  |         assert __file__ in output | ||||||
|  |         assert "spack style checks were clean" in output | ||||||
|  | 
 | ||||||
|  |     # root-relative paths | ||||||
|  |     output = style("--root-relative", flake8_package) | ||||||
|  |     assert root_relative in output | ||||||
|  |     assert "spack style checks were clean" in output | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @skip_old_python | ||||||
|  | @pytest.mark.skipif(not which("flake8"), reason="flake8 is not installed.") | ||||||
|  | def test_style_with_errors(flake8_package_with_errors): | ||||||
|  |     root_relative = os.path.relpath(flake8_package_with_errors, spack.paths.prefix) | ||||||
|  |     output = style("--root-relative", flake8_package_with_errors, fail_on_error=False) | ||||||
|  |     assert root_relative in output | ||||||
|  |     assert style.returncode == 1 | ||||||
|  |     assert "spack style found errors" in output | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @skip_old_python | ||||||
|  | @pytest.mark.skipif(not which("flake8"), reason="flake8 is not installed.") | ||||||
|  | @pytest.mark.skipif(not which("black"), reason="black is not installed.") | ||||||
|  | def test_style_with_black(flake8_package_with_errors): | ||||||
|  |     output = style("--black", flake8_package_with_errors, fail_on_error=False) | ||||||
|  |     assert "black found errors" in output | ||||||
|  |     assert style.returncode == 1 | ||||||
|  |     assert "spack style found errors" in output | ||||||
		Reference in New Issue
	
	Block a user
	 Todd Gamblin
					Todd Gamblin