Commit 8d1952f3 by Gabriel Falcão

Merge pull request #349 from infoxchange/use-test-database-setting

LETTUCE_USE_TEST_DATABASE
parents 8ce39114 20e1e1a6
......@@ -8,6 +8,7 @@ build/
dist/
.DS_Store
*.swp
tests/integration/django/dill/garden-db.sqlite
tests/integration/django/grocery/grocery-db.sqlite
.tox
./_public
......@@ -50,7 +50,8 @@ class Command(BaseCommand):
make_option('-S', '--no-server', action='store_true', dest='no_server', default=False,
help="will not run django's builtin HTTP server"),
make_option('-T', '--test-server', action='store_true', dest='test_database', default=False,
make_option('-T', '--test-server', action='store_true', dest='test_database',
default=getattr(settings, "LETTUCE_USE_TEST_DATABASE", False),
help="will run django's builtin HTTP server using the test databases"),
make_option('-P', '--port', type='int', dest='port',
......
"""
Step definitions for use with Django.
"""
"""
Step definitions for working with Django models.
"""
from datetime import datetime
import re
from django.core.management import call_command
from django.core.management.color import no_style
from django.db import connection
from django.db.models.loading import get_models
from django.utils.functional import curry
from lettuce import step
STEP_PREFIX = r'(?:Given|And|Then|When) '
def _models_generator():
"""
Build a hash of model verbose names to models
"""
for model in get_models():
yield (unicode(model._meta.verbose_name), model)
yield (unicode(model._meta.verbose_name_plural), model)
MODELS = dict(_models_generator())
_CREATE_MODEL = {}
def creates_models(model):
"""
Register a model-specific creation function.
"""
def decorated(func):
"""
Decorator for the creation function.
"""
_CREATE_MODEL[model] = func
return func
return decorated
_MODEL_EXISTS = {}
def checks_existence(model):
"""
Register a model-specific existence check function.
"""
def decorated(func):
"""
Decorator for the existence function.
"""
_MODEL_EXISTS[model] = func
return func
return decorated
def hash_data(hash_):
"""
Convert strings from a Lettuce hash to appropriate types
"""
res = {}
for key, value in hash_.items():
if type(value) in (str, unicode):
if value == "true":
value = True
elif value == "false":
value = False
elif value == "null":
value = None
elif value.isdigit() and not re.match("^0[0-9]+", value):
value = int(value)
elif re.match(r'^\d{4}-\d{2}-\d{2}$', value):
value = datetime.strptime(value, "%Y-%m-%d")
res[key] = value
return res
def hashes_data(step):
"""
Convert strings from step hashes to appropriate types
"""
return [hash_data(hash_) for hash_ in step.hashes]
def get_model(model):
"""
Convert a model's verbose name to the model class. This allows us to
use the models verbose name in steps.
"""
name = model.lower()
model = MODELS.get(model, None)
assert model, "Could not locate model by name '%s'" % name
return model
def reset_sequence(model):
"""
Reset the ID sequence for a model.
"""
sql = connection.ops.sequence_reset_sql(no_style(), [model])
for cmd in sql:
connection.cursor().execute(cmd)
def create_models(model, data):
"""
Create models for each data hash.
"""
if hasattr(data, 'hashes'):
data = hashes_data(data)
for hash_ in data:
model.objects.create(**hash_)
reset_sequence(model)
def _dump_model(model, attrs=None):
"""
Dump the model fields for debugging.
"""
for field in model._meta.fields:
print '%s=%s,' % (field.name, str(getattr(model, field.name))),
if attrs is not None:
for attr in attrs:
print '%s=%s,' % (attr, str(getattr(model, attr))),
for field in model._meta.many_to_many:
vals = getattr(model, field.name)
print '%s=%s (%i),' % (
field.name,
', '.join(map(str, vals.all())),
vals.count()),
print
def models_exist(model, data, queryset=None):
"""
Check whether the models defined by @data exist in the @queryset.
"""
if hasattr(data, 'hashes'):
data = hashes_data(data)
if not queryset:
queryset = model.objects
failed = 0
try:
for hash_ in data:
fields = {}
extra_attrs = {}
for k, v in hash_.iteritems():
if k.startswith('@'):
# this is an attribute
extra_attrs[k[1:]] = v
else:
fields[k] = v
filtered = queryset.filter(**fields)
match = False
if filtered.exists():
for obj in filtered.all():
if all(getattr(obj, k) == v \
for k, v in extra_attrs.iteritems()):
match = True
break
assert match, \
"%s does not exist: %s\n%s" % (
model.__name__, hash_, filtered.query)
except AssertionError as exc:
print exc
failed += 1
if failed:
print "Rows in DB are:"
for model in queryset.all():
_dump_model(model, extra_attrs.keys())
raise AssertionError("%i rows missing" % failed)
@step(r'I have(?: an?)? ([a-z][a-z0-9_ ]*) in the database:')
def create_models_generic(step, model):
"""
And I have foos in the database:
| name | bar |
| Baz | Quux |
The generic method can be overridden for a specific model by defining a
function create_badgers(step), which creates the Badger model.
"""
model = get_model(model)
try:
func = _CREATE_MODEL[model]
except KeyError:
func = curry(create_models, model)
func(step)
@step(STEP_PREFIX + r'([A-Z][a-z0-9_ ]*) with ([a-z]+) "([^"]*)"' +
r' has(?: an?)? ([A-Z][a-z0-9_ ]*) in the database:')
def create_models_for_relation(step, rel_model_name,
rel_key, rel_value, model):
"""
And project with name "Ball Project" has goals in the database:
| description |
| To have fun playing with balls of twine |
"""
lookup = {rel_key: rel_value}
rel_model = get_model(rel_model_name).objects.get(**lookup)
for hash_ in step.hashes:
hash_['%s' % rel_model_name] = rel_model
create_models_generic(step, model)
@step(STEP_PREFIX + r'(?:an? )?([A-Z][a-z0-9_ ]*) should be present ' +
r'in the database')
def models_exist_generic(step, model):
"""
And objectives should be present in the database:
| description |
| Make a mess |
"""
model = get_model(model)
try:
func = _MODEL_EXISTS[model]
except KeyError:
func = curry(models_exist, model)
func(step)
@step(r'There should be (\d+) ([a-z][a-z0-9_ ]*) in the database')
def model_count(step, count, model):
"""
Then there should be 0 goals in the database
"""
model = get_model(model)
expected = int(count)
found = model.objects.count()
assert found == expected, "Expected %d %s, found %d." % \
(expected, model._meta.verbose_name_plural, found)
def clean_db(scenario):
"""
Clean the DB after each scenario
Usage: after.each_scenario(clean_db)
"""
call_command('flush', interactive=False)
Feature: Create models
Scenario: Create a nice farm
Given I have a garden in the database:
| name | area | raining |
| Secret Garden | 45 | false |
And I have gardens in the database:
| name | area | raining |
| Octopus's Garden | 120 | true |
| Covent Garden | 200 | true |
And garden with name "Secret Garden" has fruit in the database:
| name | ripe_by |
| Apple | 2013-07-02 |
And I have geese in the database:
| name |
| Grey |
And I have harvesters in the database:
| make |
| Frank |
| Crank |
Then the database dump is as follows:
"""
"[
" {
" "pk": 1,
" "model": "leaves.garden",
" "fields": { "raining": false, "name": "Secret Garden", "area": 45 }
" },
" {
" "pk": 2,
" "model": "leaves.garden",
" "fields": { "raining": true, "name": "Octopus's Garden", "area": 120 }
" },
" {
" "pk": 3,
" "model": "leaves.garden",
" "fields": { "raining": true, "name": "Covent Garden", "area": 200 }
" },
" {
" "pk": 1,
" "model": "leaves.fruit",
" "fields": { "ripe_by": "2013-07-02", "name": "Apple", "garden": 1 }
" },
" {
" "pk": 1,
" "model": "leaves.goose",
" "fields": { "name": "Grey" }
" },
" {
" "pk": 1,
" "model": "leaves.harvester",
" "fields": { "rego": "FRA001", "make": "Frank" }
" },
" {
" "pk": 2,
" "model": "leaves.harvester",
" "fields": { "rego": "CRA001", "make": "Crank" }
" }
"]
"""
Feature: Check models existence
Background:
Given I have a garden in the database:
| name | area | raining |
| Secret Garden | 45 | false |
And I have gardens in the database:
| name | area | raining |
| Octopus's Garden | 120 | true |
| Covent Garden | 200 | true |
And garden with name "Secret Garden" has fruit in the database:
| name | ripe_by |
| Apple | 2013-07-02 |
And I have geese in the database:
| name |
| Grey |
And I have harvesters in the database:
| make |
| Frank |
| Crank |
Scenario: Positive checks
Given I have populated the database
Then a garden should be present in the database:
| name |
| Secret Garden |
And gardens should be present in the database:
| name | area |
| Covent Garden | 200 |
And gardens should be present in the database:
| @howbig | raining |
| small | false |
| medium | true |
| big | true |
And there should be 2 harvesters in the database
And harvesters should be present in the database:
| rego |
| fra001 |
Scenario: Negative check
Given I have populated the database
Then a garden should be present in the database:
| name |
| Botanic Gardens |
Scenario: Negative check with attributes
Given I have populated the database
Then gardens should be present in the database:
| name | @howbig |
| Secret Garden | huge |
Scenario: Negative count check
Given I have populated the database
Then there should be 2 geese in the database
import json
from django.core.management import call_command
from leaves.models import *
from lettuce import after, step
from lettuce.django.steps.models import *
from nose.tools import assert_equals
after.each_scenario(clean_db)
max_rego = 0
@creates_models(Harvester)
def create_with_rego(step):
data = hashes_data(step)
for hash_ in data:
hash_['rego'] = hash_['make'][:3].upper() + "001"
create_models(Harvester, data)
@checks_existence(Harvester)
def check_with_rego(step):
data = hashes_data(step)
for hash_ in data:
try:
hash_['rego'] = hash_['rego'].upper()
except KeyError:
pass
models_exist(Harvester, data)
@step(r'The database dump is as follows')
def database_dump(step):
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
output = StringIO()
call_command('dumpdata', stdout=output, indent=2)
output = output.getvalue()
assert_equals(json.loads(output), json.loads(step.multiline))
@step(r'I have populated the database')
def database_populated(step):
pass
@step(r'I count the harvesters')
def count_harvesters(step):
print "Harvester count: %d" % Harvester.objects.count()
Feature: Test running with the test database
Scenario: Test running with the test database
Given I have a harvester in the database:
| make |
| Frank |
Then I count the harvesters
from django.db import models
class Garden(models.Model):
name = models.CharField(max_length=100)
area = models.IntegerField()
raining = models.BooleanField()
@property
def howbig(self):
if self.area < 50:
return 'small'
elif self.area < 150:
return 'medium'
else:
return 'big'
class Fruit(models.Model):
name = models.CharField(max_length=100)
garden = models.ForeignKey(Garden)
ripe_by = models.DateField()
class Goose(models.Model):
name = models.CharField(max_length=100)
class Meta:
verbose_name_plural = "geese"
class Harvester(models.Model):
make = models.CharField(max_length=100)
rego = models.CharField(max_length=100)
# -*- coding: utf-8 -*-
# <Lettuce - Behaviour Driven Development for python>
# Copyright (C) <2010-2012> Gabriel Falcão <gabriel@nacaolivre.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.http import HttpResponse
def index(request):
return HttpResponse('OK')
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)
DEBUG = True
ROOT_URLCONF = 'urls'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'garden-db.sqlite', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
INSTALLED_APPS = (
'lettuce.django',
'leaves',
)
SECRET_KEY = 'secret'
STATIC_URL = '/static/'
from settings import *
LETTUCE_USE_TEST_DATABASE = True
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'', 'leaves.views.index'),
)
# -*- coding: utf-8 -*-
# <Lettuce - Behaviour Driven Development for python>
# Copyright (C) <2010-2012> Gabriel Falcão <gabriel@nacaolivre.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import commands
from nose.tools import assert_equals, assert_not_equals
from lettuce.fs import FileSystem
current_directory = FileSystem.dirname(__file__)
def test_model_creation():
'Models are created through Lettuce steps'
FileSystem.pushd(current_directory, "django", "dill")
status, out = commands.getstatusoutput(
"python manage.py harvest -T leaves/features/create.feature")
assert_equals(status, 0, out)
FileSystem.popd()
def test_model_existence_check():
'Model existence is checked through Lettuce steps'
def run_scenario(scenario):
return commands.getstatusoutput(
"python manage.py harvest -v 3 -T " +
"leaves/features/existence.feature -s %d" % scenario)
FileSystem.pushd(current_directory, "django", "dill")
status, out = run_scenario(1)
assert_equals(status, 0, out)
status, out = run_scenario(2)
assert_not_equals(status, 0)
assert "Garden does not exist: {u'name': u'Botanic Gardens'}" in out
gardens = "\n".join([
"Rows in DB are:",
"id=1, name=Secret Garden, area=45, raining=False,",
"id=2, name=Octopus's Garden, area=120, raining=True,",
"id=3, name=Covent Garden, area=200, raining=True,",
])
assert gardens in out
assert "AssertionError: 1 rows missing" in out
status, out = run_scenario(3)
assert_not_equals(status, 0)
assert "Garden does not exist: {u'name': u'Secret Garden', " \
"u'@howbig': u'huge'}" in out
gardens = "\n".join([
"Rows in DB are:",
"id=1, name=Secret Garden, area=45, raining=False, howbig=small,",
"id=2, name=Octopus's Garden, area=120, raining=True, howbig=medium,",
"id=3, name=Covent Garden, area=200, raining=True, howbig=big,",
])
assert gardens in out
assert "AssertionError: 1 rows missing" in out
status, out = run_scenario(4)
assert_not_equals(status, 0)
assert "Expected 2 geese, found 1" in out
FileSystem.popd()
def test_use_test_database_setting():
'Test database is recreated each time if LETTUCE_USE_TEST_DATABASE is set'
FileSystem.pushd(current_directory, "django", "dill")
for i in range(1, 2):
status, out = commands.getstatusoutput(
"python manage.py harvest --settings=testdbsettings " +
"leaves/features/testdb.feature")
assert_equals(status, 0, out)
assert "Harvester count: 1" in out, out
FileSystem.popd()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment