Fix Example Tests

This commit is contained in:
BJ Dierkes 2018-08-13 16:44:47 -05:00
parent ea8d46c4a1
commit 6e7f192fe5
36 changed files with 77 additions and 671 deletions

View File

@ -1,39 +0,0 @@
---
exclude:
- '^(.*)[\/\\\\]project[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$'
variables:
- name: label
prompt: "App Label"
case: "lower"
default: "myapp"
- name: name
prompt: "App Name"
default: "My Application"
- name: class_name
prompt: "App Class Name"
validate: "^[a-zA-Z0-9]+$"
default: "MyApp"
- name: description
prompt: "App Description"
default: "MyApp Does Amazing Things!"
- name: creator
prompt: "Creator Name"
default: "John Doe"
- name: creator_email
prompt: "Creator Email"
default: "john.doe@example.com"
- name: url
prompt: "Project URL"
default: "https://github.com/johndoe/myapp/"
- name: license
prompt: "License"
default: "unlicensed"

View File

@ -1,105 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
coverage-report/
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/

View File

@ -1,15 +0,0 @@
FROM python:3.6-alpine
MAINTAINER {{ creator }} <{{ creator_email }}>
WORKDIR /app
COPY . /app
RUN apk update \
&& apk add git \
&& pip install --no-cache-dir -r requirements.txt \
&& rm -f /usr/local/lib/python3.6/site-packages/cement.egg-link \
&& cd src/cement \
&& python setup.py install \
&& cd /app \
&& python setup.py install \
&& rm -rf /app
WORKDIR /
ENTRYPOINT ["{{ label }}"]

View File

@ -1,4 +0,0 @@
recursive-include *.py
include setup.cfg
include README.md CHANGELOG.md LICENSE.md
include *.txt

View File

@ -1,30 +0,0 @@
.PHONY: clean dev test docker dist dist-upload
clean:
find . -name '*.py[co]' -delete
rm -rf docs/build
dev:
docker-compose up -d
docker-compose exec {{ label }} pip install -r requirements-dev.txt
docker-compose exec {{ label }} python setup.py develop
docker-compose exec {{ label }} /bin/bash
test:
python -m pytest \
-v \
--cov={{ label }} \
--cov-report=term \
--cov-report=html:coverage-report \
tests/
docker: clean
docker build -t {{ label }}:latest .
dist: clean
rm -rf dist/*
python setup.py sdist
python setup.py bdist_wheel
dist-upload:
twine upload dist/*

View File

@ -1,85 +0,0 @@
# {{ description }}
## Installation
```
$ pip install -r requirements.txt
$ pip install setup.py
```
## Development
### Environment Setup
This project includes a basic Docker Compose configuration that will setup a local development environment with all dependencies, and services required for development and testing.
```
$ make dev
[...]
|> {{ label }} <| app #
```
The `{{ label }}` command line application is installed in `develop` mode, therefore all changes will be live and can be tested immediately as code is modified.
```
|> {{ label }} <| app # {{ label }} --help
```
### Running Tests
Execute tests from within the development environment:
```
|> {{ label }} <| app # make test
```
### Releasing to PyPi
Before releasing to PyPi, you must configure your login credentials:
**~/.pypirc**:
```
[pypi]
username = YOUR_USERNAME
password = YOUR_PASSWORD
```
Then use the included helper function via the `Makefile`:
```
$ make dist
$ make dist-upload
```
## Deployments
### Docker
Included is a basic `Dockerfile` for building and distributing `{{ name }}`,
and can be built with the included `make` helper:
```
$ make docker
$ docker run -it {{ label }} --help
usage: {{ label }} [-h] [--debug] [--quiet] [-o {json,yaml}] [-v] {command1} ...
{{ description }}
optional arguments:
-h, --help show this help message and exit
--debug toggle debug output
--quiet suppress all output
-o {json,yaml} output handler
-v, --version show program's version number and exit
sub-commands:
{command1}
command1 example sub command1
Usage: {{ title }} command1 --foo bar
```

View File

@ -1,46 +0,0 @@
### {{ name }} Configuration Settings
---
{{ label }}:
### Toggle application level debug (does not toggle framework debugging)
# debug: false
### Where external (third-party) plugins are loaded from
# plugin_dir: /var/lib/{{ label }}/plugins/
### Where all plugin configurations are loaded from
# plugin_config_dir: /etc/{{ label }}/plugins.d/
### Where external templates are loaded from
# template_dir: /var/lib/{{ label }}/templates/
### The log handler label
# log_handler: colorlog
### The output handler label
# output_handler: jinja2
### sample foo option
# foo: bar
log.colorlog:
### Where the log file lives (no log file by default)
# file: null
### The level for which to log. One of: info, warning, error, fatal, debug
# level: info
### Whether or not to log to console
# to_console: true
### Whether or not to rotate the log file when it reaches `max_bytes`
# rotate: false
### Max size in bytes that a log file can grow until it is rotated.
# max_bytes: 512000
### The maximun number of log files to maintain when rotating
# max_files: 4

View File

@ -1,12 +0,0 @@
version: "3"
services:
{{ label }}:
image: "{{ label }}:dev"
build:
context: .
dockerfile: docker/Dockerfile.dev
hostname: cement
stdin_open: true
tty: true
volumes:
- ".:/app"

View File

@ -1,18 +0,0 @@
FROM python:3.6-alpine
MAINTAINER {{ creator }} <{{ creator_email }}>
ENV PS1="\[\e[0;33m\]|> {{ label }} <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
WORKDIR /app
COPY requirements.txt requirements-dev.txt /app/
RUN apk update \
&& apk add git make bash vim \
&& ln -sf /usr/bin/vim /usr/bin/vi \
&& pip install -r requirements-dev.txt \
&& rm -f /usr/local/lib/python3.6/site-packages/cement.egg-link \
&& cd src/cement \
&& python setup.py install \
&& cd /app \
&& rm -rf src/cement
COPY . /app
RUN python setup.py develop
CMD ["/bin/bash"]

View File

@ -1,15 +0,0 @@
-r requirements.txt
pytest
pytest-cov
mock
coverage
sphinx
pep8
autopep8
# version cap due to bug: https://gitlab.com/pycqa/flake8/issues/406
pycodestyle<2.4
flake8
twine>=1.11.0
setuptools>=38.6.0
wheel>=0.31.0

View File

@ -1,6 +0,0 @@
### FIXME: Replace with 'cement==3.0.0' once it is stable
-e git+https://github.com/datafolklabs/cement.git@portland#egg=cement
jinja2
pyyaml
colorlog

View File

@ -1,28 +0,0 @@
from setuptools import setup, find_packages
from {{ label }}.core.version import get_version
VERSION = get_version()
f = open('README.md', 'r')
LONG_DESCRIPTION = f.read()
f.close()
setup(
name='{{ label }}',
version=VERSION,
description='{{ description }}',
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
author='{{ creator }}',
author_email='{{ creator_email }}',
url='{{ url }}',
license='{{ license }}',
packages=find_packages(exclude=['ez_setup', 'tests*']),
package_data={'{{ label }}': ['templates/*']},
include_package_data=True,
entry_points="""
[console_scripts]
{{ label }} = {{ label }}.main:main
""",
)

View File

@ -1,27 +0,0 @@
"""
PyTest Fixtures.
"""
import os
import shutil
import pytest
from tempfile import mkstemp, mkdtemp
@pytest.fixture(scope="function")
def tmp(request):
"""
Create a `tmp` object that geneates a unique temporary directory, and file
for each test function that requires it.
"""
class Tmp(object):
def __init__(self):
self.dir = mkdtemp()
_, self.file = mkstemp(dir=self.dir)
t = Tmp()
yield t
# cleanup
if os.path.exists(t.dir):
shutil.rmtree(t.dir)

View File

@ -1,13 +0,0 @@
from {{ label }}.main import {{ class_name}}Test
def test_{{ label }}(tmp):
with {{ class_name }}Test() as app:
res = app.run()
print(res)
raise Exception
def test_command1(tmp):
argv = ['command1']
with {{ class_name }}Test(argv=argv) as app:
app.run()

View File

@ -1,5 +0,0 @@
from .controllers.base import Base
def load(app):
app.handler.register(Base)

View File

@ -1,61 +0,0 @@
from cement import Controller, ex
from ..core.version import get_version
VERSION_BANNER = """
{{ description }}
Version: %s
Created by: {{ creator }}
URL: {{ url }}
""" % get_version()
class Base(Controller):
class Meta:
label = 'base'
# text displayed at the top of --help output
description = '{{ description }}'
# text displayed at the bottom of --help output
epilog = 'Usage: {{ label }} command1 --foo bar'
# controller level arguments. ex: '{{ label }} --version'
arguments = [
### add a version banner
( [ '-v', '--version' ],
{ 'action' : 'version',
'version' : VERSION_BANNER } ),
]
def _default(self):
"""Default action if no sub-command is passed."""
self.app.args.print_help()
@ex(
help='example sub command1',
# sub-command level arguments. ex: '{{ label }} command1 --foo bar'
arguments=[
### add a sample foo option under subcommand namespace
( [ '-f', '--foo' ],
{ 'help' : 'notorious foo option',
'action' : 'store',
'dest' : 'foo' } ),
],
)
def command1(self):
"""Example sub-command."""
data = {
'foo' : 'bar',
}
### do something with arguments
if self.app.pargs.foo is not None:
data['foo'] = self.app.pargs.foo
self.app.render(data, 'command1.jinja2')

View File

@ -1,13 +0,0 @@
class {{ class_name }}Error(Exception):
"""Generic errors."""
def __init__(self, msg):
Exception.__init__(self)
self.msg = msg
def __str__(self):
return self.msg
def __repr__(self):
return "<{{ class_name }}Error - %s>" % self.msg

View File

@ -1,7 +0,0 @@
from cement.utils.version import get_version as cement_get_version
VERSION = (0, 0, 1, 'alpha', 0)
def get_version(version=VERSION):
return cement_get_version(version)

View File

@ -1,90 +0,0 @@
from cement import App, init_defaults
from cement.core.exc import CaughtSignal
from .core.exc import {{ class_name }}Error
# configuration defaults
DEFAULTS = init_defaults('{{ label }}')
DEFAULTS['{{ label }}']['{{ foo }}'] = 'bar'
class {{ class_name }}(App):
"""{{ name }} primary application."""
class Meta:
label = '{{ label }}'
# offload handler/hook registration to a separate module
bootstrap = '{{ label }}.bootstrap'
# configuration defaults
config_defaults = DEFAULTS
# load additional framework extensions
extensions = [
'json',
'yaml',
'colorlog',
'jinja2',
]
# configuration handler
config_handler = 'yaml'
# configuration file suffix
config_file_suffix = '.yml'
# set the log handler
log_handler = 'colorlog'
# set the output handler
output_handler = 'jinja2'
# call sys.exit() on close
close_on_exit = True
class {{ class_name }}Test({{ class_name}}):
"""A test app that is better suited for testing."""
class Meta:
# default argv to empty (don't use sys.argv)
argv = []
# don't look for config files (could break tests)
config_files = []
# don't call sys.exit() when app.close() is called in tests
exit_on_close = False
def main():
with {{ class_name }}() as app:
try:
app.run()
except AssertionError as e:
print('AssertionError > %s' % e.args[0])
app.exit_code = 1
if app.debug is True:
import traceback
traceback.print_exc()
except {{ class_name}}Error:
print('{{ class_name }}Error > %s' % e.args[0])
app.exit_code = 1
if app.debug is True:
import traceback
traceback.print_exc()
except CaughtSignal as e:
# Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error)
print('\n%s' % e)
app.exit_code = 0
if __name__ == '__main__':
main()

View File

@ -1 +0,0 @@
Example template: Foo => {{ foo }}

View File

@ -2,11 +2,8 @@
PyTest Fixtures.
"""
import os
import shutil
import pytest
from tempfile import mkstemp, mkdtemp
from cement import fs
@pytest.fixture(scope="function")
def tmp(request):
@ -14,17 +11,6 @@ def tmp(request):
Create a `tmp` object that geneates a unique temporary directory, and file
for each test function that requires it.
"""
class Tmp(object):
cleanup = True
def __init__(self):
self.dir = mkdtemp()
_, self.file = mkstemp(dir=self.dir)
t = Tmp()
t = fs.Tmp()
yield t
# cleanup
if os.path.exists(t.dir) and cleanup is True:
shutil.rmtree(t.dir)
t.remove()

View File

@ -1,13 +0,0 @@
from {{ label }}.main import {{ class_name}}Test
def test_{{ label }}(tmp):
with {{ class_name }}Test() as app:
res = app.run()
print(res)
raise Exception
def test_command1(tmp):
argv = ['command1']
with {{ class_name }}Test(argv=argv) as app:
app.run()

View File

@ -0,0 +1,36 @@
from pytest import raises
from {{ label }}.main import {{ class_name }}Test
def test_{{ label }}():
# test {{ label }} without any subcommands or arguments
with {{ class_name }}Test() as app:
app.run()
assert app.exit_code == 0
def test_{{ label }}_debug():
# test that debug mode is functional
argv = ['--debug']
with {{ class_name }}Test(argv=argv) as app:
app.run()
assert app.debug is True
def test_command1():
# test command1 without arguments
argv = ['command1']
with {{ class_name }}Test(argv=argv) as app:
app.run()
data,output = app.last_rendered
assert data['foo'] == 'bar'
assert output.find('Foo => bar')
# test command1 with arguments
argv = ['command1', '--foo', 'not-bar']
with {{ class_name }}Test(argv=argv) as app:
app.run()
data,output = app.last_rendered
assert data['foo'] == 'not-bar'
assert output.find('Foo => not-bar')

View File

@ -1,13 +1,16 @@
from cement import App, init_defaults
from cement import App, TestApp
from cement.core.exc import CaughtSignal
from .core.exc import {{ class_name }}Error
from .controllers.base import Base
# configuration defaults
DEFAULTS = init_defaults('{{ label }}')
DEFAULTS['{{ label }}']['foo'] = 'bar'
CONFIG = {
'{{ label }}': {
'foo': 'bar',
}
}
class {{ class_name }}(App):
"""{{ name }} primary application."""
@ -16,7 +19,7 @@ class {{ class_name }}(App):
label = '{{ label }}'
# configuration defaults
config_defaults = DEFAULTS
config_defaults = CONFIG
# call sys.exit() on close
close_on_exit = True
@ -46,18 +49,11 @@ class {{ class_name }}(App):
]
class {{ class_name }}Test({{ class_name}}):
"""A sub-class of {{ class_name}} that is better suited for testing."""
class {{ class_name }}Test(TestApp,{{ class_name }}):
"""A sub-class of {{ class_name }} that is better suited for testing."""
class Meta:
# default argv to empty (don't use sys.argv)
argv = []
# don't look for config files (could break tests)
config_files = []
# don't call sys.exit() when app.close() is called in tests
exit_on_close = False
label = '{{ label }}'
def main():

View File

@ -1693,7 +1693,16 @@ class TestApp(App):
class Meta:
label = "app-%s" % misc.rando()[:12]
config_files = []
argv = []
arguments = []
core_system_config_files = []
core_user_config_files = []
config_files = []
core_system_config_dirs = []
core_user_config_dirs = []
config_dirs = []
core_system_template_dirs = []
core_user_template_dirs = []
core_system_plugin_dirs = []
core_user_plugin_dirs = []
plugin_dirs = []
exit_on_close = False

View File

@ -51,12 +51,19 @@ class Tmp(object):
prefix=prefix,
dir=self.dir)
def remove(self):
"""
Remove the temporary directory (and file) if it exists, and
``self.cleanup`` is ``True``.
"""
if self.cleanup is True and os.path.exists(self.dir):
shutil.rmtree(self.dir)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
if self.cleanup is True and os.path.exists(self.dir):
shutil.rmtree(self.dir)
self.remove()
def abspath(path):

View File

@ -315,8 +315,14 @@ def test_no_handler():
def test_config_files_is_none():
# verify the autogenerated config files list
with TestApp('test-app', config_files=None) as app:
# verify the autogenerated config files list... create a unique
# test app here since testapp removes all the config path settings
class ThisApp(App):
class Meta:
argv = []
exit_on_close = False
with ThisApp('test-app', config_files=None) as app:
user_home = fs.abspath(fs.HOME_DIR)
files = [
os.path.join('/', 'etc', app.label, '%s.conf' % app.label),