Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
lettuce
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
lettuce
Commits
86dd4e89
Commit
86dd4e89
authored
Jul 31, 2011
by
Gabriel Falcão
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #161 from chris-morgan/master
Code style improvements
parents
4e5d9e59
37d5a910
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
199 additions
and
239 deletions
+199
-239
lettuce/core.py
+31
-33
lettuce/decorators.py
+1
-0
lettuce/django/apps.py
+7
-0
lettuce/django/management/commands/harvest.py
+3
-1
lettuce/django/server.py
+4
-10
lettuce/exceptions.py
+4
-2
lettuce/fs.py
+3
-2
lettuce/lettuce_cli.py
+3
-3
lettuce/plugins/colored_shell_output.py
+16
-18
lettuce/plugins/dots.py
+10
-17
lettuce/plugins/non_verbose.py
+10
-12
lettuce/plugins/scenario_names.py
+13
-21
lettuce/plugins/shell_output.py
+12
-17
lettuce/plugins/xunit_output.py
+3
-3
lettuce/registry.py
+24
-43
lettuce/strings.py
+23
-5
lettuce/terminal.py
+9
-6
lettuce/terrain.py
+23
-46
No files found.
lettuce/core.py
View file @
86dd4e89
...
...
@@ -32,6 +32,7 @@ from lettuce.exceptions import LettuceSyntaxError
fs
=
FileSystem
()
class
HashList
(
list
):
__base_msg
=
'The step "
%
s" have no table defined, so '
\
'that you can
\'
t use step.hashes.
%
s'
...
...
@@ -64,6 +65,7 @@ class HashList(list):
raise
AssertionError
(
self
.
__base_msg
%
(
self
.
step
.
sentence
,
'last'
))
class
Language
(
object
):
code
=
'en'
name
=
'English'
...
...
@@ -73,6 +75,7 @@ class Language(object):
examples
=
'Examples|Scenarios'
scenario_outline
=
'Scenario Outline'
scenario_separator
=
'Scenario( Outline)?'
def
__init__
(
self
,
code
=
u'en'
):
self
.
code
=
code
for
attr
,
value
in
languages
.
LANGUAGES
[
code
]
.
items
():
...
...
@@ -98,6 +101,7 @@ class Language(object):
return
instance
class
StepDefinition
(
object
):
"""A step definition is a wrapper for user-defined callbacks. It
gets a few metadata from file, such as filename and line number"""
...
...
@@ -122,6 +126,7 @@ class StepDefinition(object):
return
ret
class
StepDescription
(
object
):
"""A simple object that holds filename and line number of a step
description (step within feature file)"""
...
...
@@ -132,6 +137,7 @@ class StepDescription(object):
self
.
line
=
line
class
ScenarioDescription
(
object
):
"""A simple object that holds filename and line number of a scenario
description (scenario within feature file)"""
...
...
@@ -146,6 +152,7 @@ class ScenarioDescription(object):
self
.
line
=
pline
+
1
break
class
FeatureDescription
(
object
):
"""A simple object that holds filename and line number of a feature
description"""
...
...
@@ -168,6 +175,7 @@ class FeatureDescription(object):
self
.
description_at
=
tuple
(
described_at
)
class
Step
(
object
):
""" Object that represents each step on feature files."""
has_definition
=
False
...
...
@@ -198,8 +206,8 @@ class Step(object):
method_name
=
sentence
groups
=
[
(
'"'
,
re
.
compile
(
r'("[^"]+")'
)),
# double quotes
(
"'"
,
re
.
compile
(
r"('[^']+')"
)),
# single quotes
(
'"'
,
re
.
compile
(
r'("[^"]+")'
)),
# double quotes
(
"'"
,
re
.
compile
(
r"('[^']+')"
)),
# single quotes
]
attribute_names
=
[]
...
...
@@ -217,14 +225,11 @@ class Step(object):
method_name
=
method_name
.
replace
(
match
,
group_name
)
attribute_names
.
append
(
group_name
)
method_name
=
unicodedata
.
normalize
(
'NFKD'
,
method_name
)
\
.
encode
(
'ascii'
,
'ignore'
)
method_name
=
'
%
s(step
%
s)'
%
(
"_"
.
join
(
re
.
findall
(
"
\
w+"
,
method_name
))
.
lower
(),
attribute_names
and
(
",
%
s"
%
", "
.
join
(
attribute_names
))
or
""
)
attribute_names
and
(
",
%
s"
%
", "
.
join
(
attribute_names
))
or
""
)
return
method_name
,
sentence
...
...
@@ -232,6 +237,7 @@ class Step(object):
sentence
=
self
.
sentence
hashes
=
self
.
hashes
[:]
# deep copy
for
k
,
v
in
data
.
items
():
def
evaluate
(
stuff
):
return
stuff
.
replace
(
u'<
%
s>'
%
unicode
(
k
),
unicode
(
v
))
...
...
@@ -387,7 +393,7 @@ class Step(object):
return
True
@staticmethod
def
run_all
(
steps
,
outline
=
None
,
run_callbacks
=
False
,
ignore_case
=
True
):
def
run_all
(
steps
,
outline
=
None
,
run_callbacks
=
False
,
ignore_case
=
True
):
"""Runs each step in the given list of steps.
Returns a tuple of five lists:
...
...
@@ -434,7 +440,7 @@ class Step(object):
return
(
all_steps
,
steps_passed
,
steps_failed
,
steps_undefined
,
reasons_to_fail
)
@classmethod
def
many_from_lines
(
klass
,
lines
,
filename
=
None
,
original_string
=
None
):
def
many_from_lines
(
klass
,
lines
,
filename
=
None
,
original_string
=
None
):
"""Parses a set of steps from lines of input.
This will correctly parse and produce a list of steps from lines without
...
...
@@ -652,9 +658,8 @@ class Scenario(object):
steps_skipped
=
filter
(
skip
,
all_steps
)
if
outline
:
call_hook
(
'outline'
,
'scenario'
,
self
,
order
,
outline
,
reasons_to_fail
)
call_hook
(
'outline'
,
'scenario'
,
self
,
order
,
outline
,
reasons_to_fail
)
return
ScenarioResult
(
self
,
...
...
@@ -662,8 +667,7 @@ class Scenario(object):
steps_failed
,
steps_skipped
,
steps_undefined
,
True
)
True
)
if
self
.
outlines
:
first
=
True
...
...
@@ -757,14 +761,15 @@ class Scenario(object):
with_file
=
with_file
,
original_string
=
original_string
,
language
=
language
,
tags
=
tags
)
tags
=
tags
)
return
scenario
class
Feature
(
object
):
""" Object that represents a feature."""
described_at
=
None
def
__init__
(
self
,
name
,
remaining_lines
,
with_file
,
original_string
,
language
=
None
,
tags
=
None
):
...
...
@@ -778,8 +783,7 @@ class Feature(object):
self
.
scenarios
,
self
.
description
=
self
.
_parse_remaining_lines
(
remaining_lines
,
original_string
,
with_file
)
with_file
)
self
.
original_string
=
original_string
...
...
@@ -841,17 +845,12 @@ class Feature(object):
found
=
len
(
re
.
findall
(
r'
%
s:[ ]*\w+'
%
language
.
feature
,
"
\n
"
.
join
(
lines
),
re
.
U
))
if
found
>
1
:
raise
LettuceSyntaxError
(
with_file
,
'A feature file must contain ONLY ONE feature!'
)
raise
LettuceSyntaxError
(
with_file
,
'A feature file must contain ONLY ONE feature!'
)
elif
found
==
0
:
raise
LettuceSyntaxError
(
with_file
,
'Features must have a name. e.g: "Feature: This is my name"'
)
raise
LettuceSyntaxError
(
with_file
,
'Features must have a name. e.g: "Feature: This is my name"'
)
while
lines
:
matched
=
re
.
search
(
r'
%
s:(.*)'
%
language
.
feature
,
lines
[
0
],
re
.
I
)
...
...
@@ -898,15 +897,12 @@ class Feature(object):
parts
=
strings
.
split_scenarios
(
lines
,
scenario_prefix
)
scenario_strings
=
[
u"
%
s"
%
(
s
)
for
s
in
parts
if
s
.
strip
()
]
scenario_strings
=
[
u"
%
s"
%
(
s
)
for
s
in
parts
if
s
.
strip
()]
kw
=
dict
(
original_string
=
original_string
,
with_file
=
with_file
,
language
=
self
.
language
,
tags
=
self
.
tags
)
tags
=
self
.
tags
)
scenarios
=
[
Scenario
.
from_string
(
s
,
**
kw
)
for
s
in
scenario_strings
]
...
...
@@ -932,6 +928,7 @@ class Feature(object):
call_hook
(
'after_each'
,
'feature'
,
self
)
return
FeatureResult
(
self
,
*
scenarios_ran
)
class
FeatureResult
(
object
):
"""Object that holds results of each scenario ran from within a feature"""
def
__init__
(
self
,
feature
,
*
scenario_results
):
...
...
@@ -942,6 +939,7 @@ class FeatureResult(object):
def
passed
(
self
):
return
all
([
result
.
passed
for
result
in
self
.
scenario_results
])
class
ScenarioResult
(
object
):
"""Object that holds results of each step ran from within a scenario"""
def
__init__
(
self
,
scenario
,
steps_passed
,
steps_failed
,
steps_skipped
,
...
...
@@ -966,6 +964,7 @@ class ScenarioResult(object):
def
failed
(
self
):
return
len
(
self
.
steps_failed
)
>
0
class
TotalResult
(
object
):
def
__init__
(
self
,
feature_results
):
self
.
feature_results
=
feature_results
...
...
@@ -973,7 +972,7 @@ class TotalResult(object):
self
.
steps_passed
=
0
self
.
steps_failed
=
0
self
.
steps_skipped
=
0
self
.
steps_undefined
=
0
self
.
steps_undefined
=
0
self
.
_proposed_definitions
=
[]
self
.
steps
=
0
for
feature_result
in
self
.
feature_results
:
...
...
@@ -986,7 +985,6 @@ class TotalResult(object):
self
.
steps
+=
scenario_result
.
total_steps
self
.
_proposed_definitions
.
extend
(
scenario_result
.
steps_undefined
)
def
_filter_proposed_definitions
(
self
):
sentences
=
[]
for
step
in
self
.
_proposed_definitions
:
...
...
lettuce/decorators.py
View file @
86dd4e89
...
...
@@ -18,6 +18,7 @@ import re
from
lettuce.core
import
STEP_REGISTRY
from
lettuce.exceptions
import
StepLoadingError
def
step
(
regex
):
"""Decorates a function, so that it will become a new step
definition.
...
...
lettuce/django/apps.py
View file @
86dd4e89
...
...
@@ -19,11 +19,13 @@ from os.path import join, dirname
from
django.utils.importlib
import
import_module
from
django.conf
import
settings
def
_filter_bultins
(
module
):
"returns only those apps that are not builtin django.contrib"
name
=
module
.
__name__
return
not
name
.
startswith
(
"django.contrib"
)
and
name
!=
'lettuce.django'
def
_filter_configured_apps
(
module
):
"returns only those apps that are in django.conf.settings.LETTUCE_APPS"
app_found
=
True
...
...
@@ -35,6 +37,7 @@ def _filter_configured_apps(module):
return
app_found
def
_filter_configured_avoids
(
module
):
"returns apps that are not within django.conf.settings.LETTUCE_AVOID_APPS"
run_app
=
False
...
...
@@ -45,9 +48,11 @@ def _filter_configured_avoids(module):
return
not
run_app
def
get_apps
():
return
map
(
import_module
,
settings
.
INSTALLED_APPS
)
def
harvest_lettuces
(
only_the_apps
=
None
,
avoid_apps
=
None
,
path
=
"features"
):
"""gets all installed apps that are not from django.contrib
returns a list of tuples with (path_to_app, app_module)
...
...
@@ -56,6 +61,7 @@ def harvest_lettuces(only_the_apps=None, avoid_apps=None, path="features"):
apps
=
get_apps
()
if
isinstance
(
only_the_apps
,
tuple
)
and
any
(
only_the_apps
):
def
_filter_only_specified
(
module
):
return
module
.
__name__
in
only_the_apps
apps
=
filter
(
_filter_only_specified
,
apps
)
...
...
@@ -65,6 +71,7 @@ def harvest_lettuces(only_the_apps=None, avoid_apps=None, path="features"):
apps
=
filter
(
_filter_configured_avoids
,
apps
)
if
isinstance
(
avoid_apps
,
tuple
)
and
any
(
avoid_apps
):
def
_filter_avoid
(
module
):
return
module
.
__name__
not
in
avoid_apps
...
...
lettuce/django/management/commands/harvest.py
View file @
86dd4e89
...
...
@@ -28,6 +28,7 @@ from lettuce import registry
from
lettuce.django
import
server
from
lettuce.django
import
harvest_lettuces
class
Command
(
BaseCommand
):
help
=
u'Run lettuce tests all along installed apps'
args
=
'[PATH to feature file or folder]'
...
...
@@ -59,6 +60,7 @@ class Command(BaseCommand):
make_option
(
'--xunit-file'
,
action
=
'store'
,
dest
=
'xunit_file'
,
default
=
None
,
help
=
'Write JUnit XML to this file. Defaults to lettucetests.xml'
),
)
def
stopserver
(
self
,
failed
=
False
):
raise
SystemExit
(
int
(
failed
))
...
...
@@ -71,7 +73,7 @@ class Command(BaseCommand):
else
:
paths
=
args
else
:
paths
=
harvest_lettuces
(
apps_to_run
,
apps_to_avoid
)
# list of tuples with (path, app_module)
paths
=
harvest_lettuces
(
apps_to_run
,
apps_to_avoid
)
# list of tuples with (path, app_module)
return
paths
...
...
lettuce/django/server.py
View file @
86dd4e89
...
...
@@ -128,18 +128,12 @@ class ThreadedServer(multiprocessing.Process):
self
.
lock
.
acquire
()
def
should_serve_static_files
(
self
):
conditions
=
[
StaticFilesHandler
is
not
None
,
getattr
(
settings
,
'STATIC_URL'
,
False
),
]
return
all
(
conditions
)
return
(
StaticFilesHandler
is
not
None
and
getattr
(
settings
,
'STATIC_URL'
,
False
))
def
should_serve_admin_media
(
self
):
conditions
=
[
'django.contrib.admin'
in
settings
.
INSTALLED_APPS
,
getattr
(
settings
,
'LETTUCE_SERVE_ADMIN_MEDIA'
,
False
),
]
return
any
(
conditions
)
return
(
'django.contrib.admin'
in
settings
.
INSTALLED_APPS
or
getattr
(
settings
,
'LETTUCE_SERVE_ADMIN_MEDIA'
,
False
))
def
run
(
self
):
self
.
lock
.
acquire
()
...
...
lettuce/exceptions.py
View file @
86dd4e89
...
...
@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import
traceback
class
NoDefinitionFound
(
Exception
):
""" Exception raised by lettuce.core.Step, when trying to solve a
Step, but does not find a suitable step definition.
...
...
@@ -26,8 +27,8 @@ class NoDefinitionFound(Exception):
def
__init__
(
self
,
step
):
self
.
step
=
step
super
(
NoDefinitionFound
,
self
)
.
__init__
(
'The step r"
%
s" is not defined'
%
self
.
step
.
sentence
)
'The step r"
%
s" is not defined'
%
self
.
step
.
sentence
)
class
ReasonToFail
(
object
):
""" Exception that contains detailed information about a
...
...
@@ -39,6 +40,7 @@ class ReasonToFail(object):
self
.
cause
=
unicode
(
exc
)
self
.
traceback
=
traceback
.
format_exc
(
exc
)
class
LettuceSyntaxError
(
SyntaxError
):
def
__init__
(
self
,
filename
,
string
):
self
.
filename
=
filename
...
...
lettuce/fs.py
View file @
86dd4e89
...
...
@@ -25,6 +25,7 @@ import zipfile
from
glob
import
glob
from
os.path
import
abspath
,
join
,
dirname
,
curdir
,
exists
class
FeatureLoader
(
object
):
"""Loader class responsible for findind features and step
definitions along a given path on filesystem"""
...
...
@@ -47,13 +48,14 @@ class FeatureLoader(object):
else
:
raise
e
reload
(
module
)
# always take fresh meat :)
reload
(
module
)
# always take fresh meat :)
sys
.
path
.
remove
(
root
)
def
find_feature_files
(
self
):
paths
=
FileSystem
.
locate
(
self
.
base_dir
,
"*.feature"
)
return
paths
class
FileSystem
(
object
):
"""File system abstraction, mainly used for indirection, so that
lettuce can be well unit-tested :)
...
...
@@ -229,4 +231,3 @@ class FileSystem(object):
path
=
cls
.
current_dir
(
name
)
return
open
(
path
,
mode
)
lettuce/lettuce_cli.py
View file @
86dd4e89
...
...
@@ -21,12 +21,12 @@ import optparse
import
lettuce
def
main
(
args
=
sys
.
argv
[
1
:]):
base_path
=
os
.
path
.
join
(
os
.
path
.
dirname
(
os
.
curdir
),
'features'
)
parser
=
optparse
.
OptionParser
(
usage
=
"
%
prog or type
%
prog -h (--help) for help"
,
version
=
lettuce
.
version
)
version
=
lettuce
.
version
)
parser
.
add_option
(
"-v"
,
"--verbosity"
,
dest
=
"verbosity"
,
...
...
@@ -74,7 +74,7 @@ def main(args=sys.argv[1:]):
verbosity
=
options
.
verbosity
,
enable_xunit
=
options
.
enable_xunit
,
xunit_filename
=
options
.
xunit_file
,
run_controller
=
run_controller
)
run_controller
=
run_controller
)
result
=
runner
.
run
()
if
not
result
or
result
.
steps
!=
result
.
steps_passed
:
...
...
lettuce/plugins/colored_shell_output.py
View file @
86dd4e89
...
...
@@ -17,8 +17,6 @@
import
os
import
re
import
sys
import
platform
import
struct
from
lettuce
import
core
from
lettuce
import
strings
...
...
@@ -27,12 +25,15 @@ from lettuce import terminal
from
lettuce.terrain
import
after
from
lettuce.terrain
import
before
def
wrt
(
what
):
sys
.
stdout
.
write
(
what
.
encode
(
'utf-8'
))
def
wrap_file_and_line
(
string
,
start
,
end
):
return
re
.
sub
(
r'([#] [^:]+[:]\d+)'
,
'
%
s
\
g<1>
%
s'
%
(
start
,
end
),
string
)
def
wp
(
l
):
if
l
.
startswith
(
"
\033
[1;32m"
):
l
=
l
.
replace
(
" |"
,
"
\033
[1;37m |
\033
[1;32m"
)
...
...
@@ -47,9 +48,11 @@ def wp(l):
return
l
def
write_out
(
what
):
wrt
(
wp
(
what
))
@before.each_step
def
print_step_running
(
step
):
if
not
step
.
defined_at
:
...
...
@@ -67,6 +70,7 @@ def print_step_running(step):
for
line
in
step
.
represent_hashes
()
.
splitlines
():
write_out
(
"
\033
[1;30m
%
s
\033
[0m
\n
"
%
line
)
@after.each_step
def
print_step_ran
(
step
):
if
step
.
scenario
.
outlines
:
...
...
@@ -80,7 +84,6 @@ def print_step_ran(step):
if
not
step
.
failed
:
string
=
wrap_file_and_line
(
string
,
'
\033
[1;30m'
,
'
\033
[0m'
)
prefix
=
'
\033
[A'
width
,
height
=
terminal
.
get_size
()
lines_up
=
len
(
string
)
/
float
(
width
)
...
...
@@ -123,12 +126,14 @@ def print_step_ran(step):
wrt
(
"
\033
[0m
\n
"
)
@before.each_scenario
def
print_scenario_running
(
scenario
):
string
=
scenario
.
represented
()
string
=
wrap_file_and_line
(
string
,
'
\033
[1;30m'
,
'
\033
[0m'
)
write_out
(
"
\n\033
[1;37m
%
s"
%
string
)
@after.outline
def
print_outline
(
scenario
,
order
,
outline
,
reasons_to_fail
):
table
=
strings
.
dicts_to_string
(
scenario
.
outlines
,
scenario
.
keys
)
...
...
@@ -155,6 +160,7 @@ def print_outline(scenario, order, outline, reasons_to_fail):
wrt
(
"
\033
[0m
\n
"
)
@before.each_feature
def
print_feature_running
(
feature
):
string
=
feature
.
represented
()
...
...
@@ -165,6 +171,7 @@ def print_feature_running(feature):
line
=
wrap_file_and_line
(
line
,
'
\033
[1;30m'
,
'
\033
[0m'
)
write_out
(
"
\033
[1;37m
%
s
\n
"
%
line
)
@after.all
def
print_end
(
total
):
write_out
(
"
\n
"
)
...
...
@@ -179,9 +186,7 @@ def print_end(total):
total
.
features_ran
,
word
,
color
,
total
.
features_passed
)
)
total
.
features_passed
))
color
=
"
\033
[1;32m"
if
total
.
scenarios_passed
is
0
:
...
...
@@ -192,9 +197,7 @@ def print_end(total):
total
.
scenarios_ran
,
word
,
color
,
total
.
scenarios_passed
)
)
total
.
scenarios_passed
))
steps_details
=
[]
kinds_and_colors
=
{
...
...
@@ -203,14 +206,11 @@ def print_end(total):
'undefined'
:
'
\033
[0;33m'
}
for
kind
,
color
in
kinds_and_colors
.
items
():
attr
=
'steps_
%
s'
%
kind
stotal
=
getattr
(
total
,
attr
)
if
stotal
:
steps_details
.
append
(
"
%
s
%
d
%
s"
%
(
color
,
stotal
,
kind
)
)
steps_details
.
append
(
"
%
s
%
d
%
s"
%
(
color
,
stotal
,
kind
))
steps_details
.
append
(
"
\033
[1;32m
%
d passed
\033
[1;37m"
%
total
.
steps_passed
)
word
=
total
.
steps
>
1
and
"steps"
or
"step"
...
...
@@ -220,9 +220,7 @@ def print_end(total):
write_out
(
"
\033
[1;37m
%
d
%
s (
%
s)
\033
[0m
\n
"
%
(
total
.
steps
,
word
,
content
)
)
content
))
if
total
.
proposed_definitions
:
wrt
(
"
\n\033
[0;33mYou can implement step definitions for undefined steps with these snippets:
\n\n
"
)
...
...
@@ -240,6 +238,7 @@ def print_end(total):
wrt
(
"
\n
"
)
def
print_no_features_found
(
where
):
where
=
core
.
fs
.
relpath
(
where
)
if
not
where
.
startswith
(
os
.
sep
):
...
...
@@ -248,5 +247,4 @@ def print_no_features_found(where):
write_out
(
'
\033
[1;31mOops!
\033
[0m
\n
'
)
write_out
(
'
\033
[1;37mcould not find features at '
'
\033
[1;33m
%
s
\033
[0m
\n
'
%
where
)
'
\033
[1;33m
%
s
\033
[0m
\n
'
%
where
)
lettuce/plugins/dots.py
View file @
86dd4e89
...
...
@@ -23,9 +23,11 @@ from lettuce.terrain import after
failed_scenarios
=
[]
scenarios_and_its_fails
=
{}
def
wrt
(
string
):
sys
.
stdout
.
write
(
string
)
@after.each_step
def
print_scenario_ran
(
step
):
if
not
step
.
failed
:
...
...
@@ -40,6 +42,7 @@ def print_scenario_ran(step):
else
:
wrt
(
"E"
)
@after.all
def
print_end
(
total
):
if
total
.
scenarios_passed
<
total
.
scenarios_ran
:
...
...
@@ -54,35 +57,28 @@ def print_end(total):
wrt
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
features_ran
,
word
,
total
.
features_passed
)
)
total
.
features_passed
))
word
=
total
.
scenarios_ran
>
1
and
"scenarios"
or
"scenario"
wrt
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
scenarios_ran
,
word
,
total
.
scenarios_passed
)
)
total
.
scenarios_passed
))
steps_details
=
[]
for
kind
in
(
"failed"
,
"skipped"
,
"undefined"
)
:
for
kind
in
"failed"
,
"skipped"
,
"undefined"
:
attr
=
'steps_
%
s'
%
kind
stotal
=
getattr
(
total
,
attr
)
if
stotal
:
steps_details
.
append
(
"
%
d
%
s"
%
(
stotal
,
kind
)
)
steps_details
.
append
(
"
%
d
%
s"
%
(
stotal
,
kind
))
steps_details
.
append
(
"
%
d passed"
%
total
.
steps_passed
)
word
=
total
.
steps
>
1
and
"steps"
or
"step"
wrt
(
"
%
d
%
s (
%
s)
\n
"
%
(
total
.
steps
,
word
,
", "
.
join
(
steps_details
)
)
)
", "
.
join
(
steps_details
)))
def
print_no_features_found
(
where
):
where
=
core
.
fs
.
relpath
(
where
)
...
...
@@ -90,7 +86,4 @@ def print_no_features_found(where):
where
=
'.
%
s
%
s'
%
(
os
.
sep
,
where
)
wrt
(
'Oops!
\n
'
)
wrt
(
'could not find features at '
'
%
s
\n
'
%
where
)
wrt
(
'could not find features at
%
s
\n
'
%
where
)
lettuce/plugins/non_verbose.py
View file @
86dd4e89
...
...
@@ -20,24 +20,29 @@ from lettuce import core
from
lettuce.terrain
import
after
from
lettuce.terrain
import
before
@before.each_step
def
print_step_running
(
step
):
logging
.
info
(
step
.
represent_string
(
step
.
sentence
))
@after.each_step
def
print_step_ran
(
step
):
logging
.
info
(
"
\033
[A"
+
step
.
represent_string
(
step
.
sentence
))
@before.each_scenario
def
print_scenario_running
(
scenario
):
logging
.
info
(
scenario
.
represented
())
@before.each_feature
def
print_feature_running
(
feature
):
logging
.
info
(
"
\n
"
)
logging
.
info
(
feature
.
represented
())
logging
.
info
(
"
\n
"
)
@after.all
def
print_end
(
total
):
logging
.
info
(
"
\n
"
)
...
...
@@ -45,25 +50,20 @@ def print_end(total):
logging
.
info
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
features_ran
,
word
,
total
.
features_passed
)
)
total
.
features_passed
))
word
=
total
.
scenarios_ran
>
1
and
"scenarios"
or
"scenario"
logging
.
info
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
scenarios_ran
,
word
,
total
.
scenarios_passed
)
)
total
.
scenarios_passed
))
word
=
total
.
steps
>
1
and
"steps"
or
"step"
logging
.
info
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
steps
,
word
,
total
.
steps_passed
)
)
total
.
steps_passed
))
def
print_no_features_found
(
where
):
where
=
core
.
fs
.
relpath
(
where
)
...
...
@@ -73,6 +73,4 @@ def print_no_features_found(where):
logging
.
info
(
'
\033
[1;31mOops!
\033
[0m
\n
'
)
logging
.
info
(
'
\033
[1;37mcould not find features at '
'
\033
[1;33m
%
s
\033
[0m
\n
'
%
where
)
'
\033
[1;33m
%
s
\033
[0m
\n
'
%
where
)
lettuce/plugins/scenario_names.py
View file @
86dd4e89
...
...
@@ -24,13 +24,16 @@ from lettuce.terrain import before
failed_scenarios
=
[]
scenarios_and_its_fails
=
{}
def
wrt
(
string
):
sys
.
stdout
.
write
(
string
.
encode
(
'utf-8'
))
@before.each_scenario
def
print_scenario_running
(
scenario
):
wrt
(
'
%
s ... '
%
scenario
.
name
)
@after.each_scenario
def
print_scenario_ran
(
scenario
):
if
scenario
.
passed
:
...
...
@@ -42,16 +45,18 @@ def print_scenario_ran(scenario):
else
:
print
"ERROR"
@after.each_step
def
save_step_failed
(
step
):
if
step
.
failed
and
step
.
scenario
not
in
failed_scenarios
:
scenarios_and_its_fails
[
step
.
scenario
]
=
step
.
why
failed_scenarios
.
append
(
step
.
scenario
)
@after.all
def
print_end
(
total
):
if
total
.
scenarios_passed
<
total
.
scenarios_ran
:
print
# just a line to separate things here
print
# just a line to separate things here
for
scenario
in
failed_scenarios
:
reason
=
scenarios_and_its_fails
[
scenario
]
wrt
(
reason
.
traceback
)
...
...
@@ -61,35 +66,25 @@ def print_end(total):
wrt
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
features_ran
,
word
,
total
.
features_passed
)
)
total
.
features_passed
))
word
=
total
.
scenarios_ran
>
1
and
"scenarios"
or
"scenario"
wrt
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
scenarios_ran
,
word
,
total
.
scenarios_passed
)
)
total
.
scenarios_passed
))
steps_details
=
[]
for
kind
in
(
"failed"
,
"skipped"
,
"undefined"
)
:
for
kind
in
"failed"
,
"skipped"
,
"undefined"
:
attr
=
'steps_
%
s'
%
kind
stotal
=
getattr
(
total
,
attr
)
if
stotal
:
steps_details
.
append
(
"
%
d
%
s"
%
(
stotal
,
kind
)
)
steps_details
.
append
(
"
%
d
%
s"
%
(
stotal
,
kind
))
steps_details
.
append
(
"
%
d passed"
%
total
.
steps_passed
)
word
=
total
.
steps
>
1
and
"steps"
or
"step"
wrt
(
"
%
d
%
s (
%
s)
\n
"
%
(
total
.
steps
,
word
,
", "
.
join
(
steps_details
)
)
)
wrt
(
"
%
d
%
s (
%
s)
\n
"
%
(
total
.
steps
,
word
,
", "
.
join
(
steps_details
)))
def
print_no_features_found
(
where
):
where
=
core
.
fs
.
relpath
(
where
)
...
...
@@ -97,7 +92,4 @@ def print_no_features_found(where):
where
=
'.
%
s
%
s'
%
(
os
.
sep
,
where
)
wrt
(
'Oops!
\n
'
)
wrt
(
'could not find features at '
'
%
s
\n
'
%
where
)
wrt
(
'could not find features at
%
s
\n
'
%
where
)
lettuce/plugins/shell_output.py
View file @
86dd4e89
...
...
@@ -22,9 +22,11 @@ from lettuce import strings
from
lettuce.terrain
import
after
from
lettuce.terrain
import
before
def
wrt
(
what
):
sys
.
stdout
.
write
(
what
.
encode
(
'utf-8'
))
@after.each_step
def
print_step_running
(
step
):
wrt
(
step
.
represent_string
(
step
.
original_sentence
)
.
rstrip
())
...
...
@@ -41,11 +43,13 @@ def print_step_running(step):
for
line
in
step
.
why
.
traceback
.
splitlines
():
print_spaced
(
line
)
@before.each_scenario
def
print_scenario_running
(
scenario
):
wrt
(
'
\n
'
)
wrt
(
scenario
.
represented
())
@after.outline
def
print_outline
(
scenario
,
order
,
outline
,
reasons_to_fail
):
table
=
strings
.
dicts_to_string
(
scenario
.
outlines
,
scenario
.
keys
)
...
...
@@ -66,11 +70,13 @@ def print_outline(scenario, order, outline, reasons_to_fail):
for
line
in
elines
:
print_spaced
(
line
)
@before.each_feature
def
print_feature_running
(
feature
):
wrt
(
"
\n
"
)
wrt
(
feature
.
represented
())
@after.all
def
print_end
(
total
):
wrt
(
"
\n
"
)
...
...
@@ -78,35 +84,27 @@ def print_end(total):
wrt
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
features_ran
,
word
,
total
.
features_passed
)
)
total
.
features_passed
))
word
=
total
.
scenarios_ran
>
1
and
"scenarios"
or
"scenario"
wrt
(
"
%
d
%
s (
%
d passed)
\n
"
%
(
total
.
scenarios_ran
,
word
,
total
.
scenarios_passed
)
)
total
.
scenarios_passed
))
steps_details
=
[]
for
kind
in
(
"failed"
,
"skipped"
,
"undefined"
):
attr
=
'steps_
%
s'
%
kind
stotal
=
getattr
(
total
,
attr
)
if
stotal
:
steps_details
.
append
(
"
%
d
%
s"
%
(
stotal
,
kind
)
)
steps_details
.
append
(
"
%
d
%
s"
%
(
stotal
,
kind
))
steps_details
.
append
(
"
%
d passed"
%
total
.
steps_passed
)
word
=
total
.
steps
>
1
and
"steps"
or
"step"
wrt
(
"
%
d
%
s (
%
s)
\n
"
%
(
total
.
steps
,
word
,
", "
.
join
(
steps_details
)
)
)
", "
.
join
(
steps_details
)))
if
total
.
proposed_definitions
:
wrt
(
"
\n
You can implement step definitions for undefined steps with these snippets:
\n\n
"
)
...
...
@@ -118,14 +116,11 @@ def print_end(total):
wrt
(
"def
%
s:
\n
"
%
method_name
)
wrt
(
" assert False, 'This step must be implemented'
\n
"
)
def
print_no_features_found
(
where
):
where
=
core
.
fs
.
relpath
(
where
)
if
not
where
.
startswith
(
os
.
sep
):
where
=
'.
%
s
%
s'
%
(
os
.
sep
,
where
)
wrt
(
'Oops!
\n
'
)
wrt
(
'could not find features at '
'
%
s
\n
'
%
where
)
wrt
(
'could not find features at
%
s
\n
'
%
where
)
lettuce/plugins/xunit_output.py
View file @
86dd4e89
...
...
@@ -15,7 +15,6 @@
# 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
sys
from
datetime
import
datetime
from
lettuce.terrain
import
after
from
lettuce.terrain
import
before
...
...
@@ -27,9 +26,11 @@ def wrt_output(filename, content):
f
.
write
(
content
.
encode
(
'utf-8'
))
f
.
close
()
def
total_seconds
(
td
):
return
(
td
.
microseconds
+
(
td
.
seconds
+
td
.
days
*
24
*
3600
)
*
1e6
)
/
1e6
def
enable
(
filename
=
None
):
doc
=
minidom
.
Document
()
...
...
@@ -51,7 +52,7 @@ def enable(filename=None):
if
step
.
failed
:
cdata
=
doc
.
createCDATASection
(
step
.
why
.
traceback
)
failure
=
doc
.
createElement
(
"failure"
)
failure
.
setAttribute
(
"message"
,
step
.
why
.
cause
)
failure
.
setAttribute
(
"message"
,
step
.
why
.
cause
)
failure
.
appendChild
(
cdata
)
tc
.
appendChild
(
failure
)
...
...
@@ -63,4 +64,3 @@ def enable(filename=None):
root
.
setAttribute
(
"failed"
,
str
(
total
.
steps_failed
))
doc
.
appendChild
(
root
)
wrt_output
(
output_filename
,
doc
.
toxml
())
lettuce/registry.py
View file @
86dd4e89
...
...
@@ -22,76 +22,57 @@ world = threading.local()
world
.
_set
=
False
class
CleanableDict
(
dict
):
def
clear
(
self
):
for
k
in
self
.
keys
():
del
self
[
k
]
class
CallbackDict
(
CleanableDict
):
def
_function_matches
(
self
,
one
,
other
):
params
=
'co_filename'
,
'co_firstlineno'
matches
=
list
()
def
_function_matches
(
one
,
other
):
return
(
one
.
func_code
.
co_filename
==
other
.
func_code
.
co_filename
and
one
.
func_code
.
co_firstlineno
==
other
.
func_code
.
co_firstlineno
)
for
param
in
params
:
one_got
=
getattr
(
one
.
func_code
,
param
)
other_got
=
getattr
(
other
.
func_code
,
param
)
matches
.
append
(
one_got
==
other_got
)
return
all
(
matches
)
class
CallbackDict
(
dict
):
def
append_to
(
self
,
where
,
when
,
function
):
found
=
False
for
other_function
in
self
[
where
][
when
]:
if
self
.
_function_matches
(
other_function
,
function
):
found
=
True
if
not
found
:
if
not
any
(
_function_matches
(
o
,
function
)
for
o
in
self
[
where
][
when
]):
self
[
where
][
when
]
.
append
(
function
)
def
clear
(
self
):
for
name
,
action_dict
in
self
.
items
():
for
callback_list
in
action_dict
.
values
():
while
callback_list
:
callback_list
.
pop
()
callback_list
[:]
=
[]
STEP_REGISTRY
=
CleanableDict
()
STEP_REGISTRY
=
{}
CALLBACK_REGISTRY
=
CallbackDict
(
{
'all'
:
{
'before'
:
list
()
,
'after'
:
list
()
,
'before'
:
[]
,
'after'
:
[]
,
},
'step'
:
{
'before_each'
:
list
()
,
'after_each'
:
list
()
,
'before_each'
:
[]
,
'after_each'
:
[]
,
},
'scenario'
:
{
'before_each'
:
list
()
,
'after_each'
:
list
()
,
'outline'
:
list
()
,
'before_each'
:
[]
,
'after_each'
:
[]
,
'outline'
:
[]
,
},
'feature'
:
{
'before_each'
:
list
()
,
'after_each'
:
list
()
,
'before_each'
:
[]
,
'after_each'
:
[]
,
},
'app'
:
{
'before_each'
:
list
()
,
'after_each'
:
list
()
,
'before_each'
:
[]
,
'after_each'
:
[]
,
},
'harvest'
:
{
'before'
:
list
()
,
'after'
:
list
()
,
'before'
:
[]
,
'after'
:
[]
,
},
'handle_request'
:
{
'before'
:
list
()
,
'after'
:
list
()
,
'before'
:
[]
,
'after'
:
[]
,
},
'runserver'
:
{
'before'
:
list
()
,
'after'
:
list
()
,
'before'
:
[]
,
'after'
:
[]
,
},
},
)
...
...
lettuce/strings.py
View file @
86dd4e89
...
...
@@ -17,9 +17,9 @@
import
re
import
time
import
string
import
unicodedata
def
escape_if_necessary
(
what
):
what
=
unicode
(
what
)
if
len
(
what
)
is
1
:
...
...
@@ -27,6 +27,7 @@ def escape_if_necessary(what):
return
what
def
get_stripped_lines
(
string
,
ignore_lines_starting_with
=
''
):
string
=
unicode
(
string
)
lines
=
[
unicode
(
l
.
strip
())
for
l
in
string
.
splitlines
()]
...
...
@@ -39,12 +40,13 @@ def get_stripped_lines(string, ignore_lines_starting_with=''):
return
lines
def
split_wisely
(
string
,
sep
,
strip
=
False
):
string
=
unicode
(
string
)
if
strip
:
string
=
string
.
strip
()
string
=
string
.
strip
()
else
:
string
=
string
.
strip
(
"
\n
"
)
string
=
string
.
strip
(
"
\n
"
)
sep
=
unicode
(
sep
)
regex
=
re
.
compile
(
escape_if_necessary
(
sep
),
re
.
UNICODE
|
re
.
M
|
re
.
I
)
...
...
@@ -57,15 +59,18 @@ def split_wisely(string, sep, strip=False):
return
[
unicode
(
i
)
for
i
in
items
]
def
wise_startswith
(
string
,
seed
):
string
=
unicode
(
string
)
.
strip
()
seed
=
unicode
(
seed
)
regex
=
u"^
%
s"
%
re
.
escape
(
seed
)
return
bool
(
re
.
search
(
regex
,
string
,
re
.
I
))
def
remove_it
(
string
,
what
):
return
unicode
(
re
.
sub
(
unicode
(
what
),
""
,
unicode
(
string
))
.
strip
())
def
column_width
(
string
):
l
=
0
for
c
in
string
:
...
...
@@ -75,6 +80,7 @@ def column_width(string):
l
+=
1
return
l
def
rfill
(
string
,
times
,
char
=
u" "
,
append
=
u""
):
string
=
unicode
(
string
)
missing
=
times
-
column_width
(
string
)
...
...
@@ -83,13 +89,17 @@ def rfill(string, times, char=u" ", append=u""):
return
unicode
(
string
)
+
unicode
(
append
)
def
getlen
(
string
):
return
column_width
(
unicode
(
string
))
+
1
def
dicts_to_string
(
dicts
,
order
):
escape
=
"#{
%
s}"
%
str
(
time
.
time
())
def
enline
(
line
):
return
unicode
(
line
)
.
replace
(
"|"
,
escape
)
def
deline
(
line
):
return
line
.
replace
(
escape
,
'
\\
|'
)
...
...
@@ -120,12 +130,16 @@ def dicts_to_string(dicts, order):
return
deline
(
u"
\n
"
.
join
(
table
)
+
u"
\n
"
)
def
parse_hashes
(
lines
):
escape
=
"#{
%
s}"
%
str
(
time
.
time
())
def
enline
(
line
):
return
unicode
(
line
.
replace
(
"
\\
|"
,
escape
))
.
strip
()
def
deline
(
line
):
return
line
.
replace
(
escape
,
'|'
)
def
discard_comments
(
lines
):
return
[
line
for
line
in
lines
if
not
line
.
startswith
(
'#'
)]
...
...
@@ -146,6 +160,7 @@ def parse_hashes(lines):
return
keys
,
hashes
def
parse_multiline
(
lines
):
multilines
=
[]
in_multiline
=
False
...
...
@@ -163,11 +178,11 @@ def parse_multiline(lines):
def
extract_tags_from_line
(
given_line
):
"""returns tags_array if given_line contains tags, else None"""
line
=
string
.
rstrip
(
given_line
)
line
=
given_line
.
rstrip
(
)
tags
=
[]
if
re
.
match
(
"
\
s*?
\
@"
,
line
):
tags
=
[
tag
for
tag
in
re
.
split
(
"
\
s*
\
@"
,
line
)
if
len
(
tag
)
>
0
]
if
len
(
tags
)
==
0
or
[
tag
for
tag
in
tags
if
string
.
find
(
tag
,
" "
)
!=
-
1
]
:
if
len
(
tags
)
==
0
or
any
(
' '
in
tag
for
tag
in
tags
)
:
return
None
return
tags
...
...
@@ -185,6 +200,7 @@ def consume_tags_lines(lines, tags):
else
:
break
def
consume_scenario
(
lines
,
scenario_prefix
):
"""return string of scenario text
and reduce lines array by that much"""
...
...
@@ -205,6 +221,7 @@ def consume_scenario(lines, scenario_prefix):
scenario_lines
.
extend
(
get_lines_till_next_scenario
(
lines
,
scenario_prefix
))
return
unicode
(
"
\n
"
.
join
(
scenario_lines
))
def
get_lines_till_next_scenario
(
lines
,
scenario_prefix
):
"""returns array of lines up till next scenario block"""
sep
=
unicode
(
scenario_prefix
)
...
...
@@ -222,6 +239,7 @@ def get_lines_till_next_scenario(lines, scenario_prefix):
scenario_lines
.
append
(
lines
.
pop
(
0
))
return
scenario_lines
def
split_scenarios
(
lines
,
scenario_prefix
):
"""returns array of strings, one per scenario"""
scenario_strings
=
[]
...
...
lettuce/terminal.py
View file @
86dd4e89
...
...
@@ -18,6 +18,7 @@ import os
import
platform
import
struct
def
get_size
():
if
platform
.
system
()
==
"Windows"
:
size
=
get_terminal_size_win
()
...
...
@@ -29,6 +30,7 @@ def get_size():
return
size
def
get_terminal_size_win
():
#Windows specific imports
from
ctypes
import
windll
,
create_string_buffer
...
...
@@ -41,20 +43,21 @@ def get_terminal_size_win():
res
=
windll
.
kernel32
.
GetConsoleScreenBufferInfo
(
h
,
csbi
)
if
res
:
import
struct
(
bufx
,
bufy
,
curx
,
cury
,
wattr
,
left
,
top
,
right
,
bottom
,
maxx
,
maxy
)
=
struct
.
unpack
(
"hhhhHhhhhhh"
,
csbi
.
raw
)
(
bufx
,
bufy
,
curx
,
cury
,
wattr
,
left
,
top
,
right
,
bottom
,
maxx
,
maxy
)
=
struct
.
unpack
(
"hhhhHhhhhhh"
,
csbi
.
raw
)
sizex
=
right
-
left
+
1
sizey
=
bottom
-
top
+
1
else
:
sizex
,
sizey
=
80
,
25
# can't determine actual size - return default values
else
:
# can't determine actual size - return default values
sizex
,
sizey
=
80
,
25
return
sizex
,
sizey
def
get_terminal_size_unix
():
# Unix/Posix specific imports
import
fcntl
,
termios
import
fcntl
import
termios
def
ioctl_GWINSZ
(
fd
):
try
:
cr
=
struct
.
unpack
(
'hh'
,
fcntl
.
ioctl
(
fd
,
termios
.
TIOCGWINSZ
,
...
...
lettuce/terrain.py
View file @
86dd4e89
...
...
@@ -18,6 +18,7 @@ from lettuce.registry import world
from
lettuce.registry
import
CALLBACK_REGISTRY
world
.
_set
=
True
def
absorb
(
thing
,
name
=
None
):
if
not
isinstance
(
name
,
basestring
):
name
=
thing
.
__name__
...
...
@@ -27,6 +28,7 @@ def absorb(thing, name=None):
world
.
absorb
=
absorb
@world.absorb
def
spew
(
name
):
if
hasattr
(
world
,
name
):
...
...
@@ -34,54 +36,29 @@ def spew(name):
delattr
(
world
,
name
)
return
item
class
main
(
object
):
@classmethod
def
all
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'all'
,
cls
.
__name__
,
function
)
return
function
@classmethod
def
each_step
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'step'
,
"
%
s_each"
%
cls
.
__name__
,
function
)
return
function
@classmethod
def
each_scenario
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'scenario'
,
"
%
s_each"
%
cls
.
__name__
,
function
)
return
function
@classmethod
def
each_feature
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'feature'
,
"
%
s_each"
%
cls
.
__name__
,
function
)
return
function
@classmethod
def
harvest
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'harvest'
,
cls
.
__name__
,
function
)
return
function
@classmethod
def
each_app
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'app'
,
"
%
s_each"
%
cls
.
__name__
,
function
)
return
function
@classmethod
def
runserver
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'runserver'
,
cls
.
__name__
,
function
)
return
function
@classmethod
def
handle_request
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'handle_request'
,
cls
.
__name__
,
function
)
return
function
class
Main
(
object
):
def
__init__
(
self
,
callback
):
self
.
name
=
callback
@classmethod
def
outline
(
cls
,
function
):
CALLBACK_REGISTRY
.
append_to
(
'scenario'
,
"outline"
,
function
)
return
function
def
_add_method
(
cls
,
name
,
where
,
when
):
def
method
(
self
,
fn
):
CALLBACK_REGISTRY
.
append_to
(
where
,
when
.
format
(
self
.
name
),
fn
)
method
.
__name__
=
method
.
fn_name
=
name
setattr
(
cls
,
name
,
method
)
class
before
(
main
):
pass
for
name
,
where
,
when
in
(
(
'all'
,
'all'
,
'{0}'
),
(
'each_step'
,
'step'
,
'{0}_each'
),
(
'each_scenario'
,
'scenario'
,
'{0}_each'
),
(
'each_feature'
,
'feature'
,
'{0}_each'
),
(
'harvest'
,
'harvest'
,
'{0}'
),
(
'each_app'
,
'app'
,
'{0}_each'
),
(
'runserver'
,
'runserver'
,
'{0}'
),
(
'handle_request'
,
'handle_request'
,
'{0}'
),
(
'outline'
,
'scenario'
,
'outline'
)):
Main
.
_add_method
(
name
,
where
,
when
)
class
after
(
main
):
pass
before
=
Main
(
'before'
)
after
=
Main
(
'after'
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment