From 61427f60dc1094842d2da2c6c05ffb721d9629a3 Mon Sep 17 00:00:00 2001 From: Christo De Lange <111363591+christokur@users.noreply.github.com> Date: Sun, 5 Feb 2023 10:59:57 -0500 Subject: [PATCH] fix: Fix tests and improve coverage --- cookiecutter/context.py | 2 +- setup.cfg | 2 +- .../cookiecutter-no-requires.json | 154 ++++++ tests/test_context.py | 35 ++ tests/test_generate_context_v2.py | 443 ------------------ tests/test_read_user_dict.py | 14 +- 6 files changed, 202 insertions(+), 448 deletions(-) create mode 100644 tests/test-context/cookiecutter-no-requires.json diff --git a/cookiecutter/context.py b/cookiecutter/context.py index 9bf5a8fd8..960c8cfb1 100644 --- a/cookiecutter/context.py +++ b/cookiecutter/context.py @@ -479,7 +479,7 @@ def __init__(self, template, requires=None, extensions=None, **kwargs): self.extensions = extensions if self.requirements: - self.cookiecutter_version = self.requirements.get('cookiecutter') + self.cookiecutter_version = self.requirements.get('cookiecutter', None) if self.cookiecutter_version: validate_requirement( self.cookiecutter_version, diff --git a/setup.cfg b/setup.cfg index 34eedfd32..29242fa98 100644 --- a/setup.cfg +++ b/setup.cfg @@ -85,7 +85,7 @@ max-line-length = 88 [tool:pytest] testpaths = tests -addopts = -vvv --cov-report=html --cov-report=xml --cov-branch --cov-fail-under=100 --cov-report term-missing --cov=cookiecutter +addopts = --cov-report=html --cov-report=xml --cov-branch --cov-fail-under=100 --cov-report term-missing --cov=cookiecutter [doc8] # TODO: Remove current max-line-length ignore in follow-up and adopt black limit. diff --git a/tests/test-context/cookiecutter-no-requires.json b/tests/test-context/cookiecutter-no-requires.json new file mode 100644 index 000000000..32523b078 --- /dev/null +++ b/tests/test-context/cookiecutter-no-requires.json @@ -0,0 +1,154 @@ +{ + "version": "2.0", + "requires": { + "python": ">=3" + }, + "jinja": { + "optimized": true, + "extensions": [ + "cookiecutter.extensions.SlugifyExtension", + "jinja2_time.TimeExtension" + ] + }, + "template": { + "name": "cookiecutter-pytest-plugin", + "version": "0.1", + "description": "a cookiecutter to create pytest plugins with ease.", + "authors": [ + "Raphael Pierzina ", + "Audrey Roy Greenfeld " + ], + "license": "MIT", + "keywords": [ + "pytest", + "python", + "plugin" + ], + "url": "https://github.com/pytest-dev/cookiecutter-pytest-plugin", + "variables": [ + { + "name": "full_name", + "default": "Raphael Pierzina", + "prompt": "What's your full name?", + "description": "Please enter your full name. It will be displayed on the README file and used for the PyPI package definition.", + "type": "string" + }, + { + "name": "email", + "default": "raphael@hackebrot.de", + "prompt": "What's your email?", + "description": "Please enter an email address for the meta information in setup.py.", + "type": "string" + }, + { + "name": "secret_token", + "prompt": "Please enter your secret token", + "type": "string", + "hide_input": true + }, + { + "name": "plugin_name", + "default": "emoji", + "prompt": "What should be the name for your plugin?", + "description": "Please enter a name for your plugin. We will prepend the name with 'pytest-'", + "type": "string" + }, + { + "name": "module_name", + "default": "{{cookiecutter.plugin_name|lower|replace('-','_')}}", + "prompt": "Please enter a name for your base python module", + "type": "string", + "validation": "^[a-z_]+$" + }, + { + "name": "license", + "default": "MIT", + "prompt": "Please choose a license!", + "description": "Cookiecutter will add an according LICENSE file for you and set the according classifier in setup.py.", + "type": "string", + "choices": [ + "MIT", + "BSD-3", + "GNU GPL v3.0", + "Apache Software License 2.0", + "Mozilla Public License 2.0" + ] + }, + { + "name": "docs", + "default": false, + "prompt": "Do you want to generate a base for docs?", + "description": "Would you like to generate documentation for your plugin? You will be able to choose from a number of generators.", + "type": "yes_no" + }, + { + "name": "docs_tool", + "default": "mkdocs", + "prompt": "Which tool do you want to choose for generating docs?", + "description": "There are a number of options for documentation generators. Please choose one. We will create a separate folder for you", + "type": "string", + "choices": [ + "mkdocs", + "sphinx" + ], + "skip_if": "{{cookiecutter.docs == False}}" + }, + { + "name": "year", + "default": "{% now 'utc', '%Y' %}", + "prompt_user": false, + "type": "string" + }, + { + "name": "incept_year", + "default": 2017, + "prompt_user": false, + "type": "int" + }, + { + "name": "released", + "default": false, + "prompt_user": false, + "type": "boolean" + }, + { + "name": "temperature", + "default": 77.3, + "prompt_user": false, + "type": "float" + }, + { + "name": "Release-GUID", + "default": "04f5eaa9ee7345469dccffc538b27194", + "prompt_user": false, + "type": "uuid" + }, + { + "name": "copy_with_out_render", + "default": [ + "*.html", + "*not_rendered_dir", + "rendered_dir/not_rendered_file.ini" + ], + "prompt_user": false, + "type": "string" + }, + { + "name": "fixtures", + "default": { + "foo": { + "scope": "session", + "autouse": true + }, + "bar": { + "scope": "function", + "autouse": false + } + }, + "description": "Please enter a valid JSON string to set up fixtures for your plugin.", + "prompt_user": true, + "type": "json" + } + ] + } +} diff --git a/tests/test_context.py b/tests/test_context.py index c8e7711db..806421a05 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -90,6 +90,41 @@ def test_load_context_defaults(): ) +@pytest.mark.usefixtures('clean_system') +def test_load_context_defaults_no_requires(): + + cc = load_cookiecutter('tests/test-context/cookiecutter-no-requires.json') + cc_cfg = context.load_context(cc['cookiecutter-no-requires'], no_input=True, verbose=False) + + assert cc_cfg['full_name'] == 'Raphael Pierzina' + assert cc_cfg['email'] == 'raphael@hackebrot.de' + assert cc_cfg['plugin_name'] == 'emoji' + assert cc_cfg['module_name'] == 'emoji' + assert cc_cfg['license'] == 'MIT' + assert cc_cfg['docs'] is False + assert 'docs_tool' not in cc_cfg.keys() # skip_if worked + assert cc_cfg['year'] == time.strftime('%Y') + assert cc_cfg['incept_year'] == 2017 + assert cc_cfg['released'] is False + assert cc_cfg['temperature'] == 77.3 + assert cc_cfg['Release-GUID'] == UUID('04f5eaa9ee7345469dccffc538b27194').hex + assert cc_cfg['_extensions'] == [ + 'cookiecutter.extensions.SlugifyExtension', + 'jinja2_time.TimeExtension', + ] + assert cc_cfg['_jinja2_env_vars'] == {"optimized": True} + assert ( + cc_cfg['copy_with_out_render'] + == "['*.html', '*not_rendered_dir', 'rendered_dir/not_rendered_file.ini']" + ) + assert cc_cfg['fixtures'] == OrderedDict( + [ + ('foo', OrderedDict([('scope', 'session'), ('autouse', True)])), + ('bar', OrderedDict([('scope', 'function'), ('autouse', False)])), + ] + ) + + @pytest.mark.usefixtures('clean_system') def test_load_context_defaults_skips_branch(): """ diff --git a/tests/test_generate_context_v2.py b/tests/test_generate_context_v2.py index 4fbfa7eb9..d0c8228a4 100644 --- a/tests/test_generate_context_v2.py +++ b/tests/test_generate_context_v2.py @@ -597,449 +597,6 @@ def test_generate_context_extra_ctx_list_item_dict_no_name_field_match(): assert msg in str(excinfo.value) -def gen_context_data_inputs_expected(): - """ - Creates a generator of combination: - ((input file, additional params), expected output) - """ - context_with_valid_extra_0 = ( - { - 'context_file': 'tests/test-generate-context-v2/test.json', - 'extra_context': [ - { - 'name': 'email', - 'default': 'miles.davis@jazz.gone', - 'description': 'Enter jazzy email...', - 'extra_field': 'extra_field_value', - } - ], - }, - {"test": expected_file1_v4}, - ) - # Empty extra context precipitates no ill effect - context_with_valid_extra_1 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [], - }, - {"representative": expected_file2_v0}, - ) - - # Test the ability to change the variable's name field (since it is used - # to identify the variable to be modifed) with extra context and to remove - # a key from the context via the removal token: '<>' - context_with_valid_extra_2 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative-director.json', - 'extra_context': [ - { - 'name': 'director_credit::producer_credit', - 'prompt': 'Is there a producer credit on this film?', - 'description': 'There are usually a lot of producers...', - }, - { - 'name': 'director_name', - 'skip_if': '<>', - }, - ], - }, - {"representative": expected_file2_v1}, - ) - # Test the ability to change the variable's name field (since it is used - # to identify the variable to be modifed) with extra context and to also - context_with_valid_extra_2_B = ( - { - 'context_file': 'tests/test-generate-context-v2/representative_2B.json', - 'extra_context': [ - { - 'name': 'director_credit::producer_credit', - 'prompt': 'Is there a producer credit on this film?', - 'description': 'There are usually a lot of producers...', - }, - ], - }, - { - "representative_2B": OrderedDict( - [ - ("version", "2.0"), - ( - "requires", - OrderedDict([("cookiecutter", ">1"), ("python", ">=3.0")]), - ), - ( - "template", - OrderedDict( - [ - ("name", "cc-representative"), - ( - "variables", - [ - OrderedDict( - [ - ("name", "producer_credit"), - ("default", True), - ( - "prompt", - "Is there a producer credit on this film?", - ), - ( - "description", - "There are usually a lot of producers...", - ), - ("type", "boolean"), - ] - ), - OrderedDict( - [ - ("name", "director_name"), - ("default", "Allan Smithe"), - ( - "prompt", - "What's the Director's full name?", - ), - ("prompt_user", True), - ( - "description", - "The default director is not proud " - "of their work, we hope you are.", - ), - ("hide_input", False), - ( - "choices", - [ - "Allan Smithe", - "Ridley Scott", - "Victor Fleming", - "John Houston", - "{{cookiecutter.producer_credit}}", - ], - ), - ("validation", "^[a-z][A-Z]+$"), - ( - "validation_flags", - ["verbose", "ascii"], - ), - ( - "skip_if", - "{{cookiecutter.producer_credit == False}}", - ), - ("type", "string"), - ] - ), - ], - ), - ] - ), - ), - ] - ) - }, - ) - # test that any other references in other variables that might use the - # original variable name get updated as well. - - # Test changing variable's name field value, default field, prompt field, - # and changing the type - context_with_valid_extra_3 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [ - { - 'name': 'director_credit::producer_credits', - 'default': 2, - 'prompt': 'How many producers does this film have?', - 'description': 'There are usually a lot of producers...', - 'type': "int", - } - ], - }, - {"representative": expected_file2_v2}, - ) - # Test changing choices field without changing the default, but default - # does not change because the first position in choices matches default - context_with_valid_extra_4 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [ - { - 'name': 'director_name', - 'choices': [ - 'Allan Smithe', - 'Ridley Scott', - 'Victor Fleming', - 'John Houston', - 'John Ford', - 'Billy Wilder', - ], - } - ], - }, - {"representative": expected_file2_v3}, - ) - # Test changing choices field and changing the default - context_with_valid_extra_5 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [ - { - 'name': 'director_name', - 'default': 'John Ford', - 'choices': [ - 'Allan Smithe', - 'Ridley Scott', - 'Victor Fleming', - 'John Houston', - 'John Ford', - 'Billy Wilder', - ], - } - ], - }, - {"representative": expected_file2_v4}, - ) - # Test changing the default, but not the choices field, yet seeing choices field re-ordered - # to put default value in first location - context_with_valid_extra_6 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [ - { - 'name': 'director_name', - 'default': 'John Ford', - } - ], - }, - {"representative": expected_file2_v5}, - ) - # Test changing choices field without changing the default, but default - # does get changee because the first position in choices field chagned - context_with_valid_extra_7 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [ - { - 'name': 'director_name', - 'choices': [ - 'Billy Wilder', - 'Allan Smithe', - 'Ridley Scott', - 'Victor Fleming', - 'John Houston', - 'John Ford', - ], - } - ], - }, - {"representative": expected_file2_v6}, - ) - # Test changing the default value with a value that is not in choices, - # we should see the choice first position get updated. - context_with_valid_extra_8 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [ - { - 'name': 'director_name', - 'default': 'Peter Sellers', - } - ], - }, - {"representative": expected_file2_v7}, - ) - yield context_with_valid_extra_0 - yield context_with_valid_extra_1 - yield context_with_valid_extra_2 - yield context_with_valid_extra_2_B - yield context_with_valid_extra_3 - yield context_with_valid_extra_4 - yield context_with_valid_extra_5 - yield context_with_valid_extra_6 - yield context_with_valid_extra_7 - yield context_with_valid_extra_8 - - -def gen_context_data_inputs_expected_var(): - # Test the ability to change the variable's name field (since it is used - # to identify the variable to be modifed) with extra context and to remove - # a key from the context via the removal token: '<>' - context_with_valid_extra_2 = ( - { - 'context_file': 'tests/test-generate-context-v2/representative.json', - 'extra_context': [ - { - 'name': 'director_credit::producer_credit', - 'prompt': 'Is there a producer credit on this film?', - 'description': 'There are usually a lot of producers...', - }, - { - 'name': 'director_name', - 'skip_if': '<>', - }, - ], - }, - { - "representative": OrderedDict( - [ - ("name", "cc-representative"), - ("cookiecutter_version", "2.0.0"), - ( - "variables", - [ - OrderedDict( - [ - ("name", "producer_credit"), - ("default", True), - ( - "prompt", - "Is there a producer credit on this film?", - ), - ( - "description", - "There are usually a lot of producers...", - ), - ("type", "boolean"), - ] - ), - OrderedDict( - [ - ("name", "director_name"), - ("default", "Allan Smithe"), - ("prompt", "What's the Director's full name?"), - ("prompt_user", True), - ( - "description", - "The default director is not proud of their work, we hope you are.", - ), - ("hide_input", False), - ( - "choices", - [ - "Allan Smithe", - "Ridley Scott", - "Victor Fleming", - "John Houston", - ], - ), - ("validation", "^[a-z][A-Z]+$"), - ("validation_flags", ["verbose", "ascii"]), - ("type", "string"), - ] - ), - ], - ), - ] - ) - }, - ) - # Test the ability to change the variable's name field (since it is used - # to identify the variable to be modifed) with extra context and to also - # test that any other references in other variables that might use the - # original variable name get updated as well. - context_with_valid_extra_2_B = ( - { - 'context_file': 'tests/test-generate-context-v2/representative_2B.json', - 'extra_context': [ - { - 'name': 'director_credit::producer_credit', - 'prompt': 'Is there a producer credit on this film?', - 'description': 'There are usually a lot of producers...', - }, - ], - }, - { - "representative_2B": OrderedDict( - [ - ("name", "cc-representative"), - ("cookiecutter_version", "2.0.0"), - ( - "variables", - [ - OrderedDict( - [ - ("name", "producer_credit"), - ("default", True), - ( - "prompt", - "Is there a producer credit on this film?", - ), - ( - "description", - "There are usually a lot of producers...", - ), - ("type", "boolean"), - ] - ), - OrderedDict( - [ - ("name", "director_name"), - ("default", "Allan Smithe"), - ("prompt", "What's the Director's full name?"), - ("prompt_user", True), - ( - "description", - "The default director is not proud of their work, we hope you are.", - ), - ("hide_input", False), - ( - "choices", - [ - "Allan Smithe", - "Ridley Scott", - "Victor Fleming", - "John Houston", - "{{cookiecutter.producer_credit}}", - ], - ), - ("validation", "^[a-z][A-Z]+$"), - ("validation_flags", ["verbose", "ascii"]), - ( - "skip_if", - "{{cookiecutter.producer_credit == False}}", - ), - ("type", "string"), - ] - ), - ], - ), - ] - ) - }, - ) - - yield context_with_valid_extra_2 - yield context_with_valid_extra_2_B - - -@pytest.mark.usefixtures('clean_system') -@pytest.mark.parametrize( - 'input_params, expected_context', gen_context_data_inputs_expected() -) -def test_generate_context_with_extra_context_dictionary( - input_params, expected_context, monkeypatch -): - """ - Test the generated context with extra content overwrite to multiple fields, - with creation of new fields NOT allowed. - """ - assert OrderedDict(generate.generate_context(**input_params)) == OrderedDict( - expected_context - ) - - -@pytest.mark.usefixtures('clean_system') -@pytest.mark.parametrize( - 'input_params, expected_context', gen_context_data_inputs_expected_var() -) -def test_generate_context_with_extra_context_dictionary_var( - input_params, expected_context, monkeypatch -): - """ - Test the generated context with extra content overwrite to multiple fields, - with creation of new fields NOT allowed. - """ - assert generate.generate_context(**input_params) == expected_context - - @pytest.mark.usefixtures('clean_system') def test_raise_exception_when_attempting_to_remove_mandatory_field(): """ diff --git a/tests/test_read_user_dict.py b/tests/test_read_user_dict.py index 0ce50efc2..7d8a2063a 100644 --- a/tests/test_read_user_dict.py +++ b/tests/test_read_user_dict.py @@ -1,6 +1,6 @@ """Test `process_json`, `read_user_dict` functions in `cookiecutter.prompt`.""" -import click import pytest +import click.testing from cookiecutter.prompt import ( process_json, @@ -106,11 +106,19 @@ def test_should_not_load_json_from_sentinel(mocker): 'cookiecutter.prompt.json.loads', autospec=True, return_value={} ) + +def test_should_not_call_process_json_default_value(mocker, monkeypatch): + """Make sure that `process_json` is not called when using default value.""" + mock_process_json = mocker.patch( + 'cookiecutter.prompt.process_json', autospec=True, return_value='default') + runner = click.testing.CliRunner() - with runner.isolation(input="\n"): + with runner.isolation(input="\n") as streams: read_user_dict('name', {'project_slug': 'pytest-plugin'}) + stdout, stderr = streams + assert not stdout.getvalue().decode().strip() == 'name [default]:\n' - mock_json_loads.assert_not_called() + mock_process_json.assert_not_called() @pytest.mark.parametrize("input", ["\n", "default\n"])