From 7c0fd7b8c0e615c28b74937cd8e7254d34c96982 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 9 Jul 2020 02:57:53 +0100 Subject: [PATCH] Make it more backward compatible --- setup.cfg | 1 + src/pyscaffoldext/cookiecutter/extension.py | 44 +++++++++++++++++++-- tests/system/helpers.py | 24 ++++++++++- tests/system/test_common.py | 7 +++- tests/test_cookiecutter.py | 18 ++++++--- tox.ini | 4 +- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6e0d197..3090f99 100644 --- a/setup.cfg +++ b/setup.cfg @@ -85,6 +85,7 @@ norecursedirs = .tox testpaths = tests markers = + only: use '-m only' to run a specific test slow: mark tests as slow (deselect with '-m "not slow"') system: mark system tests diff --git a/src/pyscaffoldext/cookiecutter/extension.py b/src/pyscaffoldext/cookiecutter/extension.py index a5a9b9c..600aade 100644 --- a/src/pyscaffoldext/cookiecutter/extension.py +++ b/src/pyscaffoldext/cookiecutter/extension.py @@ -20,20 +20,56 @@ class Cookiecutter(Extension): mutually_exclusive = True + try: + # TODO: Remove this try/except block on PyScaffold >= 4.x + from pyscaffold.extensions import cookiecutter # Check if builtin existis + + del cookiecutter + + # WORKAROUND: + # + # This avoids raising an error by using `add_argument` with an + # option/flag that was already used and at the same time provides + # a unequivocal way of accessing the newest implementation in the + # tests via the `--x-` prefix. + # + # For the time being this is useful to run against an existing + # version of PyScaffold that have an old implementation of this + # extension built into the core of the system. + + @property + def xhelp(self): + return ("Newest version of `{}`, in development".format(self.flag),) + + @property + def xflag(self): + return "--x-" + self.flag.strip("-") + + except ImportError: + pass # Never mind, we are in a recent version of PyScaffold + def augment_cli(self, parser): """Add an option to parser that enables the Cookiecutter extension Args: parser (argparse.ArgumentParser): CLI parser object """ + # TODO: Simplify the x stuff for PyScaffold >= 4.x + flag = getattr(self, "xflag", self.flag) + help = getattr( + self, + "xhelp", + "additionally apply a Cookiecutter template. " + "Note that not all templates are suitable for PyScaffold. " + "Please refer to the docs for more information.", + ) + parser.add_argument( - self.flag, + flag, dest=self.name, action=create_cookiecutter_parser(self), metavar="TEMPLATE", - help="additionally apply a Cookiecutter template. " - "Note that not all templates are suitable for PyScaffold. " - "Please refer to the docs for more information.", + help=help, ) def activate(self, actions): diff --git a/tests/system/helpers.py b/tests/system/helpers.py index a973784..af2fc04 100644 --- a/tests/system/helpers.py +++ b/tests/system/helpers.py @@ -1,7 +1,15 @@ # -*- coding: utf-8 -*- import shlex +import sys +import traceback from os import environ -from subprocess import STDOUT, check_output +from subprocess import STDOUT, CalledProcessError, check_output +from uuid import uuid4 + + +def uniqstr(): + """Generates a unique random long string every time it is called""" + return str(uuid4()).replace("-", "") def run(*args, **kwargs): @@ -12,10 +20,22 @@ def run(*args, **kwargs): else: args = args[0] + if args[0] == "python" and sys.platform != "win32": + args[0] += str(sys.version_info[0]) + opts = dict(stderr=STDOUT, universal_newlines=True) opts.update(kwargs) - return check_output(args, **opts) + try: + output = check_output(args, **opts) + print("command: ", args, opts) + print("output: ", output) + return output + except CalledProcessError as ex: + traceback.print_exc() + msg = "******************** Terminal ($? = {}) ********************\n{}" + print(msg.format(ex.returncode, ex.output)) + raise def run_common_tasks(tests=True, flake8=True): diff --git a/tests/system/test_common.py b/tests/system/test_common.py index 62ca5f2..8acb875 100644 --- a/tests/system/test_common.py +++ b/tests/system/test_common.py @@ -4,12 +4,17 @@ import pytest +from pyscaffoldext.cookiecutter.extension import Cookiecutter + from .helpers import run, run_common_tasks pytestmark = [pytest.mark.slow, pytest.mark.system] COOKIECUTTER = "https://github.com/pyscaffold/cookiecutter-pypackage.git" +# TODO: Remove workaround for PyScaffold <= 4.x, see comments on class +FLAG = (lambda ext: getattr(ext, "xflag", ext.flag))(Cookiecutter("cookiecutter")) + def is_venv(): """Check if the tests are running inside a venv""" @@ -37,7 +42,7 @@ def test_ensure_inside_test_venv(): def test_namespace_cookiecutter(cwd): # Given pyscaffold is installed, # when we call putup with --namespace and --cookiecutter - run("putup myproj --namespace nested.ns --cookiecutter " + COOKIECUTTER) + run("putup myproj --namespace nested.ns {} {}".format(FLAG, COOKIECUTTER)) # then a very complicated module hierarchy should exist assert isdir("myproj/src/nested/ns/myproj") # and all the common tasks should run properly diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index b1be081..793eb8f 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -24,12 +24,17 @@ COOKIECUTTER_URL = "https://github.com/pyscaffold/cookiecutter-pypackage.git" COOKIECUTTER_FILES = ["proj/Makefile", "proj/.github/ISSUE_TEMPLATE.md"] +# TODO: Remove workaround for PyScaffold <= 4.x, see comments on class +FLAG = (lambda ext: getattr(ext, "xflag", ext.flag))(Cookiecutter("cookiecutter")) + @pytest.mark.slow def test_create_project_with_cookiecutter(tmpfolder): # Given options with the cookiecutter extension, opts = dict( project=PROJ_NAME, + package=PROJ_NAME, + version="0.0.1", cookiecutter=COOKIECUTTER_URL, extensions=[Cookiecutter("cookiecutter")], ) @@ -41,13 +46,14 @@ def test_create_project_with_cookiecutter(tmpfolder): for path in COOKIECUTTER_FILES: assert path_exists(path) # and also overwritable pyscaffold files (with the exact contents) - tmpfolder.join(PROJ_NAME).join("setup.py").read() == setup_py(opts) + existing_setup = tmpfolder.join(PROJ_NAME).join("setup.py").read() + assert existing_setup == setup_py(opts) def test_pretend_create_project_with_cookiecutter(tmpfolder, caplog): # Given options with the cookiecutter extension, caplog.set_level(logging.INFO) - opts = parse_args([PROJ_NAME, "--pretend", "--cookiecutter", COOKIECUTTER_URL]) + opts = parse_args([PROJ_NAME, "--pretend", FLAG, COOKIECUTTER_URL]) opts = process_opts(opts) # when the project is created, @@ -125,7 +131,7 @@ def test_create_project_cookiecutter_and_update(tmpfolder, capsys): @pytest.mark.slow def test_cli_with_cookiecutter(tmpfolder): # Given the command line with the cookiecutter option, - sys.argv = ["pyscaffold", PROJ_NAME, "--cookiecutter", COOKIECUTTER_URL] + sys.argv = ["pyscaffold", PROJ_NAME, FLAG, COOKIECUTTER_URL] # when pyscaffold runs, run() @@ -137,7 +143,7 @@ def test_cli_with_cookiecutter(tmpfolder): def test_cli_with_cookiecutter_but_no_template(tmpfolder, capsys): # Given the command line with the cookiecutter option, but no template - sys.argv = ["pyscaffold", PROJ_NAME, "--cookiecutter"] + sys.argv = ["pyscaffold", PROJ_NAME, FLAG] # when pyscaffold runs, # then an exception should be raised. @@ -146,7 +152,7 @@ def test_cli_with_cookiecutter_but_no_template(tmpfolder, capsys): # make sure the exception is related to the missing argument out, err = capsys.readouterr() - assert "--cookiecutter: expected one argument" in out + err + assert "{}: expected one argument".format(FLAG) in out + err def test_cli_without_cookiecutter(tmpfolder): @@ -167,7 +173,7 @@ def test_cli_with_cookiecutter_and_update(tmpfolder, capsys): # when the project is updated # with the cookiecutter extension, - sys.argv = ["pyscaffold", PROJ_NAME, "--update", "--cookiecutter", COOKIECUTTER_URL] + sys.argv = ["pyscaffold", PROJ_NAME, "--update", FLAG, COOKIECUTTER_URL] run() # then a warning should be displayed diff --git a/tox.ini b/tox.ini index 1be97b3..c38b353 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,8 @@ envlist = default [testenv] description = run test suite with pytest -setenv = TOXINIDIR = {toxinidir} +setenv = + TOXINIDIR = {toxinidir} passenv = HOME commands = @@ -18,6 +19,7 @@ extras = [testenv:docs] description = invoke sphinx-build to build the HTML docs +skipsdist = true deps = -r {toxinidir}/docs/requirements.txt whitelist_externals = make changedir = {toxinidir}/docs