def test_include_remote_style_from_local_style(tmp_path): """Test include of remote style when there is only a local style.""" remote_style = "https://raw.githubusercontent.com/user/repo/branch/path/to/nitpick-style" url_with_extension = f"{remote_style}{TOML_EXTENSION}" body = """ ["tox.ini".section] key = "value" """ responses.add(responses.GET, url_with_extension, dedent(body), status=200) project = ProjectMock(tmp_path).style(f""" [nitpick.styles] include = [ "{remote_style}" ] """) project.assert_file_contents(TOX_INI, None).api_check_then_fix( Fuss(True, TOX_INI, 321, " was not found. Create it with this content:", "[section]\nkey = value")).assert_file_contents( TOX_INI, """ [section] key = value """, PYPROJECT_TOML, None, )
def project_remote(request, tmp_path): """Project with a remote style (loaded from a URL).""" from tests.helpers import ProjectMock, tomlstring remote_url = "https://example.com/remote-style.toml" remote_style = """ ["pyproject.toml".tool.black] line-length = 100 """ # https://docs.pytest.org/en/stable/fixture.html#using-markers-to-pass-data-to-fixtures marker = request.node.get_closest_marker("tool_nitpick") tool_nitpick = marker.args[0] if marker else "" with RequestsMock() as mocked_response: mocked_response.add(mocked_response.GET, remote_url, dedent(remote_style), status=200) project = ProjectMock(tmp_path) project.pyproject_toml(f""" [tool.nitpick] style = {tomlstring(remote_url)} {tool_nitpick} [tool.black] line-length = 100 """).remote(mocked_response, remote_url) yield project
def test_relative_style_on_urls(tmp_path): """Read styles from relative paths on URLs.""" base_url = "http://www.example.com/sub/folder" mapping = { "main": """ [nitpick.styles] include = "presets/python.toml" """, "presets/python": """ [nitpick.styles] include = [ "../styles/pytest.toml", "../styles/black.toml", ] """, "styles/pytest": """ ["pyproject.toml".tool.pytest] some-option = 123 """, "styles/black": """ ["pyproject.toml".tool.black] line-length = 99 missing = "value" """, } for filename, body in mapping.items(): responses.add(responses.GET, f"{base_url}/{filename}.toml", dedent(body), status=200) project = ProjectMock(tmp_path) common_pyproject = """ [tool.black] line-length = 99 [tool.pytest] some-option = 123 """ # Use full path on initial styles project.pyproject_toml(f""" [tool.nitpick] style = ["{base_url}/main"] {common_pyproject} """).api_check().assert_violations( Fuss( False, PYPROJECT_TOML, 318, " has missing values:", """ [tool.black] missing = "value" """, ))
def test_no_python_file_root_dir(request): """No Python file on the root dir.""" project = ProjectMock(request, setup_py=False).pyproject_toml("").save_file( "whatever.sh", "", lint=True).flake8() project.assert_single_error( "NIP102 No Python file was found on the root dir and subdir of {!r}". format(str(project.root_dir)))
def test_no_root_dir_with_python_file(tmp_path, shared_datadir): """No root dir with Python file.""" hello_py = tmp_path / "hello.py" shutil.copy(shared_datadir / "hello.py", hello_py) project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=False) project.files_to_lint.append(hello_py) error = f"NIP101 {ProjectViolations.NO_ROOT_DIR.message}" project.flake8().assert_single_error(error).cli_run( error, exit_code=2).cli_ls(error, exit_code=2)
def test_protocol_not_supported(tmp_path): """Test unsupported protocols.""" project = ProjectMock(tmp_path).pyproject_toml(""" [tool.nitpick] style = ["abc://www.example.com/style.toml"] """) with pytest.raises(RuntimeError) as exc_info: project.api_check() assert str(exc_info.value) == "URL protocol 'abc' is not supported"
def test_config_file_already_has_tool_nitpick_section(tmp_path, config_file): """Test if both config files already exist.""" project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True).save_file( config_file, f""" [{TOOL_NITPICK_KEY}] style = ['/this/should/not/be/validated-yet.toml'] """, ) project.cli_init( f"The config file {config_file} already has a [{TOOL_NITPICK_KEY}] section.", exit_code=1)
def test_each_builtin_style(tmp_path, datadir, builtin_style_path): """Test each built-in style (skip presets).""" style = BuiltinStyle.from_path(builtin_style_path) violations = [] name_contents = [] for filename in style.files: expected_path = datadir / style.path_from_resources_root / filename if not expected_path.exists(): # Creates empty files on datadir, to help with the task of adding new built-in styles # You just need to fill in the expected contents of each file fixture_path = Path( __file__ ).parent / "test_builtin" / style.path_from_resources_root / filename fixture_path.parent.mkdir(parents=True, exist_ok=True) fixture_path.touch(exist_ok=True) expected_contents = expected_path.read_text() code = BUILTIN_STYLE_CODES[filename] violations.append( Fuss(True, filename, code, " was not found. Create it with this content:", expected_contents)) name_contents.extend([filename, expected_contents]) violations.extend( BUILTIN_STYLE_EXTRA_VIOLATIONS.get(style.path_from_resources_root, [])) project = ProjectMock(tmp_path).save_file( DOT_NITPICK_TOML, f""" [tool.nitpick] style = "{style.py_url_without_ext}" """, ) # Run `nitpick fix` twice on the style # First time check: it should report violations and create new file(s) project.api_check_then_fix(*violations) if style.files: project.assert_file_contents(*name_contents) has_unfixed_violations = any(not fuss.fixed for fuss in violations) if has_unfixed_violations: # If some violations can't be fixed, we can't check for the second time and we must leave return # Second time check: it should not report any violation and should not change the existing file(s) project.api_check_then_fix() if style.files: project.assert_file_contents(*name_contents)
def test_has_one_config_file(tmp_path, config_file, caplog): """There is a root dir (setup.py) and a single config file.""" project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True) project.save_file("local.toml", "").save_file( config_file, """ [tool.nitpick] style = ["local.toml"] cache = "forever" """, ).api_check(offline=True) path = project.root_dir / config_file assert project.nitpick_instance.project.read_configuration( ) == Configuration(path, ["local.toml"], "forever") assert f"Config file: reading from {path}" in caplog.text
def test_suggest_initial_contents(request): """Suggest initial contents for missing pre-commit config file.""" ProjectMock(request).load_styles("isort", "black").pyproject_toml(""" [tool.nitpick] style = ["isort", "black"] """).flake8().assert_errors_contain(""" NIP331 File .pre-commit-config.yaml was not found. Create it with this content:\x1b[32m repos: - repo: https://github.com/asottile/seed-isort-config rev: v1.9.3 hooks: - id: seed-isort-config - repo: https://github.com/pre-commit/mirrors-isort rev: v4.3.21 hooks: - id: isort - repo: https://github.com/python/black rev: 19.10b0 hooks: - id: black args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs rev: v1.3.0 hooks: - id: blacken-docs additional_dependencies: [black==19.10b0]\x1b[0m """)
def test_invalid_nitpick_files(offline, tmp_path): """Invalid [nitpick.files] section.""" ProjectMock(tmp_path).named_style( "some_style", """ [xxx] wrong = "section" """, ).named_style( "wrong_files", """ [nitpick.files.whatever] wrong = "section" """, ).pyproject_toml(""" [tool.nitpick] style = ["some_style", "wrong_files"] """).flake8(offline=offline).assert_errors_contain(f""" NIP001 File some_style.toml has an incorrect style. Invalid config:{SUGGESTION_BEGIN} xxx: Unknown file. See {READ_THE_DOCS_URL}plugins.html.{SUGGESTION_END} """).assert_errors_contain( f""" NIP001 File wrong_files.toml has an incorrect style. Invalid config:{SUGGESTION_BEGIN} nitpick.files.whatever: Unknown file. See {READ_THE_DOCS_URL}nitpick_section.html#nitpick-files.{SUGGESTION_END} """, 2, )
def test_missing_hook_with_id(request): """Test missing hook with specific id.""" ProjectMock(request).style( ''' [["pre-commit-config.yaml".repos]] repo = "other" hooks = """ - id: black name: black entry: black """ ''' ).pre_commit( """ repos: - repo: other hooks: - id: isort """ ).lint().assert_single_error( """ NIP337 File .pre-commit-config.yaml: missing hook with id 'black': - id: black name: black entry: black """ )
def test_style_missing_id_in_hook(request): """Test style file is missing id in hook.""" ProjectMock(request).style( ''' [["pre-commit-config.yaml".repos]] repo = "another" hooks = """ - name: isort entry: isort -sp setup.cfg """ ''' ).pre_commit( """ repos: - repo: another hooks: - id: isort """ ).lint().assert_single_error( """ NIP336 File .pre-commit-config.yaml: style file is missing 'id' in hook: name: isort entry: isort -sp setup.cfg """ )
def test_simulate_parsing_error_when_saving(update_file, tmp_path): """Simulate a parsing error when saving an INI file.""" update_file.side_effect = ParsingError( source="simulating a captured error") original_file = """ [flake8] existing = value """ ProjectMock(tmp_path).style(f""" ["{SETUP_CFG}".flake8] new = "value" """).setup_cfg(original_file).api_fix().assert_violations( Fuss( True, SETUP_CFG, 324, ": section [flake8] has some missing key/value pairs. Use this:", """ [flake8] new = value """, ), Fuss( False, SETUP_CFG, Violations.PARSING_ERROR.code, ": parsing error (ParsingError): Source contains parsing errors: 'simulating a captured error'", ), ).assert_file_contents(SETUP_CFG, original_file)
def test_root_values_on_existing_file(request): """Test values on the root of the config file when there is a file.""" ProjectMock(request).style( """ ["pre-commit-config.yaml"] fail_fast = true blabla = "what" something = true another_thing = "yep" """ ).pre_commit( """ repos: - hooks: - id: whatever something: false another_thing: "nope" """ ).lint().assert_errors_contain_unordered( """ NIP338 File .pre-commit-config.yaml has missing values:\x1b[92m blabla: what fail_fast: true\x1b[0m """ ).assert_errors_contain( """ NIP339 File .pre-commit-config.yaml has different values. Use this:\x1b[92m another_thing: yep something: true\x1b[0m """ )
def test_when_no_config_file_the_default_style_is_requested(tmp_path, caplog): """There is a root dir (setup.py), but no config file.""" project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True).api_check(offline=True) assert project.nitpick_instance.project.read_configuration( ) == Configuration(None, [], "") assert "Config file: none found" in caplog.text
def test_suggest_initial_contents(request): """Suggest contents when setup.cfg does not exist.""" ProjectMock(request).style( """ [nitpick.files."setup.cfg"] "missing_message" = "Do something here" ["setup.cfg".mypy] ignore_missing_imports = true ["setup.cfg".isort] line_length = 120 ["setup.cfg".flake8] max-line-length = 120 """ ).lint().assert_single_error( """ NIP321 File setup.cfg was not found. Do something here. Create it with this content:\x1b[92m [flake8] max-line-length = 120 [isort] line_length = 120 [mypy] ignore_missing_imports = True\x1b[0m """ )
def test_suggest_initial_contents(tmp_path, datadir): """Suggest initial contents for missing pre-commit config file.""" warnings.simplefilter("ignore") # "repos.yaml" key ProjectMock(tmp_path).named_style( "isort", datadir / "1-isort.toml").named_style( "black", datadir / "1-black.toml").pyproject_toml(""" [tool.nitpick] style = ["isort", "black"] """).api_check_then_fix( Fuss( True, PRE_COMMIT_CONFIG_YAML, 361, " was not found. Create it with this content:", """ repos: [] """, ), partial_names=[PRE_COMMIT_CONFIG_YAML], ).assert_file_contents( PRE_COMMIT_CONFIG_YAML, """ repos: [] """, )
def test_minimum_version(mocked_version, offline, request): """Stamp a style file with a minimum required version, to indicate new features or breaking changes.""" assert_conditions(mocked_version == "0.5.3") ProjectMock(request).named_style( "parent", """ [nitpick.styles] include = "child.toml" ["pyproject.toml".tool.black] line-length = 100 """, ).named_style( "child", """ [nitpick] minimum_version = "1.0" """, ).pyproject_toml(""" [tool.nitpick] style = "parent" [tool.black] line-length = 100 """).flake8(offline=offline).assert_single_error( "NIP203 The style file you're using requires nitpick>=1.0 (you have 0.5.3). Please upgrade" )
def test_missing_hook_with_id(tmp_path): """Test missing hook with specific id.""" ProjectMock(tmp_path).style(''' [[".pre-commit-config.yaml".repos]] repo = "other" hooks = """ - id: black name: black entry: black """ ''').pre_commit(""" repos: - repo: other hooks: - id: isort """).api_check_then_fix( Fuss( True, PRE_COMMIT_CONFIG_YAML, 368, " has missing values:", """ repos: - repo: other hooks: "- id: black\\n name: black\\n entry: black\\n" """, ))
def test_style_missing_id_in_hook(tmp_path): """Test style file is missing id in hook. Read the warning on :py:class:`nitpick.plugins.yaml.YamlPlugin`.""" ProjectMock(tmp_path).style(f''' [[".pre-commit-config.yaml".repos]] repo = "another" hooks = """ - name: isort entry: isort -sp {SETUP_CFG} """ ''').pre_commit(""" repos: - repo: another hooks: - id: isort """).api_check_then_fix( Fuss( True, PRE_COMMIT_CONFIG_YAML, 368, " has missing values:", 'repos:\n - repo: another\n hooks: "- name: isort\\n entry: isort -sp setup.cfg\\n"', )).assert_file_contents( PRE_COMMIT_CONFIG_YAML, r""" repos: - repo: another hooks: - id: isort - repo: another hooks: "- name: isort\n entry: isort -sp setup.cfg\n" """, )
def test_missing_repo_key(tmp_path): """Test missing repo key on the style file.""" ProjectMock(tmp_path).style(""" [[".pre-commit-config.yaml".repos]] grepo = "glocal" """).pre_commit(""" repos: - hooks: - id: whatever """).api_check_then_fix( Fuss( True, PRE_COMMIT_CONFIG_YAML, 368, " has missing values:", """ repos: - grepo: glocal """, ), ).assert_file_contents( PRE_COMMIT_CONFIG_YAML, """ repos: - hooks: - id: whatever - grepo: glocal """, )
def test_comma_separated_keys_on_style_file(tmp_path): """Comma separated keys on the style file.""" ProjectMock(tmp_path).style(f""" [nitpick.files."{SETUP_CFG}"] comma_separated_values = ["food.eat", "food.drink"] ["{SETUP_CFG}".food] eat = "salt,ham,eggs" drink = "water,bier,wine" """).setup_cfg(""" [food] eat = spam,eggs,cheese drink = wine , bier , water """).api_check_then_fix( Fuss( True, SETUP_CFG, Violations.MISSING_VALUES_IN_LIST.code, " has missing values in the 'eat' key. Include those values:", """ [food] eat = (...),ham,salt """, )).assert_file_contents( SETUP_CFG, """ [food] eat = spam,eggs,cheese,ham,salt drink = wine , bier , water """, )
def test_missing_sections(request): """Test missing sections.""" ProjectMock(request).setup_cfg( """ [mypy] ignore_missing_imports = true """ ).style( """ ["setup.cfg".mypy] ignore_missing_imports = true ["setup.cfg".isort] line_length = 120 ["setup.cfg".flake8] max-line-length = 120 """ ).lint().assert_single_error( """ NIP321 File setup.cfg has some missing sections. Use this:\x1b[92m [flake8] max-line-length = 120 [isort] line_length = 120\x1b[0m """ )
def test_invalid_configuration_comma_separated_values(tmp_path): """Test an invalid configuration for comma_separated_values.""" ProjectMock(tmp_path).style(f""" ["{SETUP_CFG}".flake8] max-line-length = 85 max-complexity = 12 ignore = "D100,D101,D102,D103,D104,D105,D106,D107,D202,E203,W503" select = "E241,C,E,F,W,B,B9" [nitpick.files."{SETUP_CFG}"] comma_separated_values = ["flake8.ignore", "flake8.exclude"] """).api_check().assert_violations( Fuss( False, SETUP_CFG, 321, " was not found. Create it with this content:", """ [flake8] max-line-length = 85 max-complexity = 12 ignore = D100,D101,D102,D103,D104,D105,D106,D107,D202,E203,W503 select = E241,C,E,F,W,B,B9 """, ))
def test_comma_separated_keys_on_style_file(request): """Comma separated keys on the style file.""" project = ( ProjectMock(request) .style( """ [nitpick.files."setup.cfg"] comma_separated_values = ["food.eat"] ["setup.cfg".food] eat = "salt,ham,eggs" """ ) .setup_cfg( """ [food] eat = spam,eggs,cheese """ ) .lint() ) project.assert_single_error( """ NIP322 File setup.cfg has missing values in the 'eat' key. Include those values:\x1b[92m [food] eat = (...),ham,salt\x1b[0m """ )
def test_suggest_initial_contents(request): """Suggest contents when setup.cfg does not exist.""" ProjectMock(request).style(""" [nitpick.files.present] "setup.cfg" = "Do something here" ["setup.cfg".mypy] ignore_missing_imports = true ["setup.cfg".isort] line_length = 120 ["setup.cfg".flake8] max-line-length = 120 """).flake8().assert_errors_contain( """ NIP321 File setup.cfg was not found. Create it with this content:\x1b[32m [flake8] max-line-length = 120 [isort] line_length = 120 [mypy] ignore_missing_imports = True\x1b[0m """, 2, ).assert_errors_contain( "NIP103 File setup.cfg should exist: Do something here")
def test_create_basic_dot_nitpick_toml(tmp_path): """If no config file is found, create a basic .nitpick.toml.""" project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True) url = StyleManager.get_default_style_url() project.cli_init( f"A [{TOOL_NITPICK_KEY}] section was created in the config file: {DOT_NITPICK_TOML}" ).assert_file_contents( DOT_NITPICK_TOML, f""" [{TOOL_NITPICK_KEY}] # Generated by the 'nitpick init' command # More info at {READ_THE_DOCS_URL}configuration.html style = ['{url}'] """, ) assert url.scheme == Scheme.PY
def test_invalid_nitpick_files(offline, request): """Invalid [nitpick.files] section.""" ProjectMock(request).named_style( "some_style", """ [xxx] wrong = "section" """, ).named_style( "wrong_files", """ [nitpick.files.whatever] wrong = "section" """, ).pyproject_toml(""" [tool.nitpick] style = ["some_style", "wrong_files"] """).flake8(offline=offline).assert_errors_contain(""" NIP001 File some_style.toml has an incorrect style. Invalid config:\x1b[32m xxx: Unknown file. See https://nitpick.rtfd.io/en/latest/config_files.html.\x1b[0m """).assert_errors_contain( """ NIP001 File wrong_files.toml has an incorrect style. Invalid config:\x1b[32m nitpick.files.whatever: Unknown file. See {}nitpick_section.html#nitpick-files.\x1b[0m """.format(READ_THE_DOCS_URL), 2, )
def test_different_missing_keys(request): """Test different and missing keys.""" ProjectMock(request).setup_cfg( """ [mypy] ignore_missing_imports = true [isort] line_length = 30 [flake8] xxx = "aaa" """ ).style( """ ["setup.cfg".mypy] ignore_missing_imports = true ["setup.cfg".isort] line_length = 110 ["setup.cfg".flake8] max-line-length = 112 """ ).lint().assert_errors_contain( """ NIP323 File setup.cfg: [isort]line_length is 30 but it should be like this:\x1b[92m [isort] line_length = 110\x1b[0m """ ).assert_errors_contain( """ NIP324 File setup.cfg: section [flake8] has some missing key/value pairs. Use this:\x1b[92m [flake8] max-line-length = 112\x1b[0m """ )