summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2020-05-17 09:42:46 +0000
committerDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2020-05-17 09:42:46 +0000
commit6d77fdedd7fd0f832a543b7eb530ae15984aa377 (patch)
treef354dde229b1cd41e75e3aaa76c9487766bce7c6
parent5db9eef3e02f7e3b344becd08c74a99fb274f2ed (diff)
parentf276c028dd4869a97129140cea28f02f3889ac47 (diff)
downloadsubplot-6d77fdedd7fd0f832a543b7eb530ae15984aa377.tar.gz
Merge branch 'finally2' into 'master'
Add cleanup support to scenario steps Closes #51 See merge request larswirzenius/subplot!42
-rw-r--r--src/bindings.rs95
-rw-r--r--src/matches.rs4
-rw-r--r--subplot.md185
-rw-r--r--subplot.py30
-rw-r--r--subplot.yaml16
-rw-r--r--templates/bash/template.sh68
-rw-r--r--templates/python/template.py143
7 files changed, 460 insertions, 81 deletions
diff --git a/src/bindings.rs b/src/bindings.rs
index 946e8fb..67c45d6 100644
--- a/src/bindings.rs
+++ b/src/bindings.rs
@@ -17,22 +17,29 @@ use regex::{escape, Regex};
/// Contains the pattern used to match against scenario steps,
/// combined with the step kind. The pattern is a regular expression
/// as understood by the regex crate.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Binding {
kind: StepKind,
pattern: String,
regex: Regex,
function: String,
+ cleanup: Option<String>,
}
impl Binding {
/// Create a new Binding, from a step kind and a pattern.
- pub fn new(kind: StepKind, pattern: &str, function: &str) -> Result<Binding> {
+ pub fn new(
+ kind: StepKind,
+ pattern: &str,
+ function: &str,
+ cleanup: Option<&str>,
+ ) -> Result<Binding> {
Ok(Binding {
kind,
pattern: pattern.to_owned(),
regex: Regex::new(&format!("^{}$", pattern))?,
function: function.to_string(),
+ cleanup: cleanup.map(String::from),
})
}
@@ -51,6 +58,14 @@ impl Binding {
&self.function
}
+ /// Return name of function that implements cleanup.
+ pub fn cleanup(&self) -> Option<&str> {
+ match self.cleanup {
+ None => None,
+ Some(ref s) => Some(&s),
+ }
+ }
+
/// Return the compiled regular expression for the pattern of the
/// binding.
///
@@ -69,7 +84,7 @@ impl Binding {
let caps = self.regex.captures(step_text)?;
// If there is only one capture, it's the whole string.
- let mut m = MatchedStep::new(self.kind(), &self.function);
+ let mut m = MatchedStep::new(self.kind(), &self.function, self.cleanup());
if caps.len() == 1 {
m.append_part(PartialStep::uncaptured(step_text));
return Some(m);
@@ -125,12 +140,6 @@ impl PartialEq for Binding {
impl Eq for Binding {}
-impl Clone for Binding {
- fn clone(&self) -> Binding {
- Binding::new(self.kind, self.pattern.as_str(), &self.function).unwrap()
- }
-}
-
#[cfg(test)]
mod test_binding {
use super::Binding;
@@ -139,47 +148,65 @@ mod test_binding {
use crate::StepKind;
#[test]
- fn creates_new() {
- let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
+ fn creates_new_without_cleanup() {
+ let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name", None).unwrap();
+ assert_eq!(b.kind(), StepKind::Given);
+ assert!(b.regex().is_match("I am Tomjon"));
+ assert!(!b.regex().is_match("I am Tomjon of Lancre"));
+ assert!(!b.regex().is_match("Hello, I am Tomjon"));
+ assert_eq!(b.function(), "set_name");
+ assert_eq!(b.cleanup(), None);
+ }
+
+ #[test]
+ fn creates_new_with_cleanup() {
+ let b = Binding::new(
+ StepKind::Given,
+ "I am Tomjon",
+ "set_name",
+ Some("unset_name"),
+ )
+ .unwrap();
assert_eq!(b.kind(), StepKind::Given);
assert!(b.regex().is_match("I am Tomjon"));
assert!(!b.regex().is_match("I am Tomjon of Lancre"));
assert!(!b.regex().is_match("Hello, I am Tomjon"));
assert_eq!(b.function(), "set_name");
+ assert_eq!(b.cleanup(), Some("unset_name"));
}
#[test]
fn equal() {
- let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
- let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
+ let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name", Some("unset")).unwrap();
+ let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name", Some("unset")).unwrap();
assert_eq!(a, b);
}
#[test]
fn not_equal() {
- let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
- let b = Binding::new(StepKind::Given, "I am Tomjon of Lancre", "set_name").unwrap();
+ let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name", None).unwrap();
+ let b = Binding::new(StepKind::Given, "I am Tomjon of Lancre", "set_name", None).unwrap();
assert_ne!(a, b);
}
#[test]
fn does_not_match_with_wrong_kind() {
let step = ScenarioStep::new(StepKind::Given, "given", "yo");
- let b = Binding::new(StepKind::When, "yo", "do_yo").unwrap();
+ let b = Binding::new(StepKind::When, "yo", "do_yo", None).unwrap();
assert!(b.match_with_step(&step).is_none());
}
#[test]
fn does_not_match_with_wrong_text() {
let step = ScenarioStep::new(StepKind::Given, "given", "foo");
- let b = Binding::new(StepKind::Given, "bar", "yo").unwrap();
+ let b = Binding::new(StepKind::Given, "bar", "yo", None).unwrap();
assert!(b.match_with_step(&step).is_none());
}
#[test]
fn match_with_fixed_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "foo");
- let b = Binding::new(StepKind::Given, "foo", "do_foo").unwrap();
+ let b = Binding::new(StepKind::Given, "foo", "do_foo", None).unwrap();
let m = b.match_with_step(&step).unwrap();
assert_eq!(m.kind(), StepKind::Given);
let mut parts = m.parts();
@@ -191,7 +218,13 @@ mod test_binding {
#[test]
fn match_with_regex() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon, I am");
- let b = Binding::new(StepKind::Given, r"I am (?P<who>\S+), I am", "set_name").unwrap();
+ let b = Binding::new(
+ StepKind::Given,
+ r"I am (?P<who>\S+), I am",
+ "set_name",
+ None,
+ )
+ .unwrap();
let m = b.match_with_step(&step).unwrap();
assert_eq!(m.kind(), StepKind::Given);
let mut parts = m.parts();
@@ -220,6 +253,7 @@ struct ParsedBinding {
when: Option<String>,
then: Option<String>,
function: String,
+ cleanup: Option<String>,
regex: Option<bool>,
}
@@ -331,7 +365,15 @@ fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding> {
regex_from_simple_pattern(pattern)?
};
- Ok(Binding::new(kind, &pattern, &parsed.function)?)
+ Ok(Binding::new(
+ kind,
+ &pattern,
+ &parsed.function,
+ match parsed.cleanup {
+ None => None,
+ Some(ref s) => Some(s),
+ },
+ )?)
}
#[cfg(test)]
@@ -351,7 +393,8 @@ mod test_bindings {
#[test]
fn adds_binding() {
- let binding = Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name").unwrap();
+ let binding =
+ Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
assert_eq!(bindings.bindings(), &[binding]);
@@ -393,7 +436,7 @@ mod test_bindings {
#[test]
fn does_not_find_match_for_unmatching_kind() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::When, r"I am Tomjon", "set_foo").unwrap();
+ let binding = Binding::new(StepKind::When, r"I am Tomjon", "set_foo", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
assert!(bindings.find(&step).is_none());
@@ -402,7 +445,8 @@ mod test_bindings {
#[test]
fn does_not_find_match_for_unmatching_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::Given, r"I am Tomjon of Lancre", "set_foo").unwrap();
+ let binding =
+ Binding::new(StepKind::Given, r"I am Tomjon of Lancre", "set_foo", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
assert!(bindings.find(&step).is_none());
@@ -411,7 +455,7 @@ mod test_bindings {
#[test]
fn finds_match_for_fixed_string_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::Given, r"I am Tomjon", "set_name").unwrap();
+ let binding = Binding::new(StepKind::Given, r"I am Tomjon", "set_name", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
let m = bindings.find(&step).unwrap();
@@ -428,7 +472,8 @@ mod test_bindings {
#[test]
fn finds_match_for_regexp_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name").unwrap();
+ let binding =
+ Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
let m = bindings.find(&step).unwrap();
diff --git a/src/matches.rs b/src/matches.rs
index 89cf791..27a71e1 100644
--- a/src/matches.rs
+++ b/src/matches.rs
@@ -46,16 +46,18 @@ pub struct MatchedStep {
text: String,
parts: Vec<PartialStep>,
function: String,
+ cleanup: Option<String>,
}
impl MatchedStep {
/// Return a new empty match. Empty means it has no step parts.
- pub fn new(kind: StepKind, function: &str) -> MatchedStep {
+ pub fn new(kind: StepKind, function: &str, cleanup: Option<&str>) -> MatchedStep {
MatchedStep {
kind,
text: "".to_string(),
parts: vec![],
function: function.to_string(),
+ cleanup: cleanup.map(String::from),
}
}
diff --git a/subplot.md b/subplot.md
index 05b9341..f0dfa9b 100644
--- a/subplot.md
+++ b/subplot.md
@@ -691,6 +691,191 @@ then bar was done
~~~~
+## Automatic cleanup in scenarios
+
+A binding can define a cleanup function, which gets called at the end
+of the scenario in reverse order for the successful steps. If a step
+fails, all the cleanups for the successful steps are still called. We
+test this for every language templat we support.
+
+~~~{#cleanup.yaml .file .yaml .numberLines}
+- given: foo
+ function: foo
+ cleanup: foo_cleanup
+- given: bar
+ function: bar
+ cleanup: bar_cleanup
+- given: failure
+ function: failure
+ cleanup: failure_cleanup
+~~~
+
+~~~{#cleanup.py .file .python .numberLines}
+def foo(ctx):
+ pass
+def foo_cleanup(ctx):
+ pass
+def bar(ctx):
+ pass
+def bar_cleanup(ctx):
+ pass
+def failure(ctx):
+ assert 0
+def failure_cleanup(ctx):
+ pass
+~~~
+
+~~~{#cleanup.sh .file .bash .numberLines}
+foo() {
+ true
+}
+foo_cleanup() {
+ true
+}
+bar() {
+ true
+}
+bar_cleanup() {
+ true
+}
+failure() {
+ return 1
+}
+failure_cleanup() {
+ true
+}
+~~~
+
+
+### Cleanup functions gets called on success (Python)
+
+~~~scenario
+given file cleanup-success-python.md
+and file cleanup.yaml
+and file cleanup.py
+when I run sp-codegen --run cleanup-success-python.md -o test.py
+then scenario "Cleanup" was run
+and step "given foo" was run, and then step "given bar"
+and cleanup for "given bar" was run, and then for "given foo"
+and program finished successfully
+~~~
+
+
+~~~~~{#cleanup-success-python.md .file .markdown .numberLines}
+---
+title: Cleanup
+bindings: cleanup.yaml
+functions: cleanup.py
+template: python
+...
+
+# Cleanup
+
+~~~scenario
+given foo
+given bar
+~~~
+~~~~~
+
+
+### Cleanup functions get called on failure (Python)
+
+~~~scenario
+given file cleanup-fail-python.md
+and file cleanup.yaml
+and file cleanup.py
+when I try to run sp-codegen --run cleanup-fail-python.md -o test.py
+then scenario "Cleanup" was run
+and step "given foo" was run, and then step "given bar"
+and cleanup for "given bar" was run, and then for "given foo"
+and cleanup for "given failure" was not run
+and exit code is non-zero
+~~~
+
+~~~~~{#cleanup-fail-python.md .file .markdown .numberLines}
+---
+title: Cleanup
+bindings: cleanup.yaml
+functions: cleanup.py
+template: python
+...
+
+# Cleanup
+
+~~~scenario
+given foo
+given bar
+given failure
+~~~
+~~~~~
+
+
+### Cleanup functions gets called on success (Bash)
+
+~~~scenario
+given file cleanup-success-bash.md
+and file cleanup.yaml
+and file cleanup.sh
+when I run sp-codegen --run cleanup-success-bash.md -o test.sh
+then scenario "Cleanup" was run
+and step "given foo" was run, and then step "given bar"
+and cleanup for "given bar" was run, and then for "given foo"
+and program finished successfully
+~~~
+
+~~~~~{#cleanup-success-bash.md .file .markdown .numberLines}
+---
+title: Cleanup
+bindings: cleanup.yaml
+functions: cleanup.sh
+template: bash
+...
+
+# Cleanup
+
+~~~scenario
+given foo
+given bar
+~~~
+~~~~~
+
+
+### Cleanup functions get called on failure (Bash)
+
+If a step fails, all the cleanups for the preceding steps are still
+called, in reverse order.
+
+~~~scenario
+given file cleanup-fail-bash.md
+and file cleanup.yaml
+and file cleanup.sh
+when I try to run sp-codegen --run cleanup-fail-bash.md -o test.sh
+then scenario "Cleanup" was run
+and step "given foo" was run, and then step "given bar"
+and cleanup for "given bar" was run, and then for "given foo"
+and cleanup for "given failure" was not run
+and exit code is non-zero
+~~~
+
+~~~~~{#cleanup-fail-bash.md .file .markdown .numberLines}
+---
+title: Cleanup
+bindings: cleanup.yaml
+functions: cleanup.sh
+template: bash
+...
+
+# Cleanup
+
+~~~scenario
+given foo
+given bar
+given failure
+~~~
+~~~~~
+
+
+
## Capturing parts of steps for functions
A scenario step binding can capture parts of a scenario step, to be
diff --git a/subplot.py b/subplot.py
index 1495553..e586b53 100644
--- a/subplot.py
+++ b/subplot.py
@@ -35,14 +35,14 @@ def run_docgen_with_date(ctx, md=None, output=None, date=None):
exit_code_zero(ctx)
-def try_codegen_and_program(ctx, filename=None):
+def try_codegen_and_program(ctx, filename=None, testprog=None):
codegen = binary("sp-codegen")
tmpldir = os.path.join(srcdir, "templates")
- runcmd(ctx, [codegen, filename, "-o", "test.py", "--run", "--templates", tmpldir])
+ runcmd(ctx, [codegen, filename, "-o", testprog, "--run", "--templates", tmpldir])
-def run_codegen_and_program(ctx, filename=None):
- try_codegen_and_program(ctx, filename=filename)
+def run_codegen_and_program(ctx, filename=None, testprog=None):
+ try_codegen_and_program(ctx, filename=filename, testprog=testprog)
exit_code_zero(ctx)
@@ -111,6 +111,28 @@ def step_was_run(ctx, keyword=None, name=None):
stdout_matches(ctx, pattern="\n step: {} {}\n".format(keyword, name))
+def step_was_run_and_then(ctx, keyword1=None, name1=None, keyword2=None, name2=None):
+ stdout_matches(
+ ctx,
+ pattern="\n step: {} {}\n step: {} {}".format(
+ keyword1, name1, keyword2, name2
+ ),
+ )
+
+
+def cleanup_was_run(ctx, keyword1=None, name1=None, keyword2=None, name2=None):
+ stdout_matches(
+ ctx,
+ pattern="\n cleanup: {} {}\n cleanup: {} {}\n".format(
+ keyword1, name1, keyword2, name2
+ ),
+ )
+
+
+def cleanup_was_not_run(ctx, keyword=None, name=None):
+ stdout_does_not_match(ctx, pattern="\n cleanup: {} {}\n".format(keyword, name))
+
+
def exit_code_zero(ctx):
if ctx.get("exit") != 0:
print("context:", ctx.as_dict())
diff --git a/subplot.yaml b/subplot.yaml
index 607e361..8d4187f 100644
--- a/subplot.yaml
+++ b/subplot.yaml
@@ -17,13 +17,13 @@
- when: I try to run sp-docgen {md} -o {output}
function: try_docgen
-- when: I run sp-codegen --run {filename} -o test.py
+- when: I run sp-codegen --run {filename} -o {testprog}
function: run_codegen_and_program
- when: I run sp-codegen {filename} -o {testprog}
function: run_codegen
-- when: I try to run sp-codegen --run {filename} -o test.py
+- when: I try to run sp-codegen --run {filename} -o {testprog}
function: try_codegen_and_program
- when: I run python3 {testprog} {pattern}
@@ -84,6 +84,18 @@
function: step_was_run
regex: true
+- then: step "(?P<keyword1>given|when|then) (?P<name1>.+)" was run, and then step "(?P<keyword2>given|when|then) (?P<name2>.+)"
+ function: step_was_run_and_then
+ regex: true
+
+- then: cleanup for "(?P<keyword1>given|when|then) (?P<name1>.+)" was run, and then for "(?P<keyword2>given|when|then) (?P<name2>.+)"
+ function: cleanup_was_run
+ regex: true
+
+- then: cleanup for "(?P<keyword>given|when|then) (?P<name>.+)" was not run
+ function: cleanup_was_not_run
+ regex: true
+
- then: program finished successfully
function: exit_code_zero
diff --git a/templates/bash/template.sh b/templates/bash/template.sh
index c0a6e7e..bc6a51e 100644
--- a/templates/bash/template.sh
+++ b/templates/bash/template.sh
@@ -167,28 +167,62 @@ files_set "$filename" "$contents"
######################################
# Scenario: {{ scenario.title }}
scenario_{{ loop.index }}() {
- local _title _scendir _step _name _text
+ local title scendir step name text ret cleanups steps
+ declare -a cleanups
+ declare -a steps
- _title="$(decode_base64 '{{ scenario.title | base64 }}')"
- echo "scenario: $_title"
+ title="$(decode_base64 '{{ scenario.title | base64 }}')"
+ echo "scenario: $title"
- _scendir="$(mktemp -d -p "$_datadir")"
- cd "$_scendir"
+ scendir="$(mktemp -d -p "$_datadir")"
+ cd "$scendir"
ctx_new
+ cleanups[0]=''
+ steps[0]=''
+ ret=0
+
{% for step in scenario.steps %}
- # Step: {{ step.text }}
- _step="$(decode_base64 '{{ step.text | base64 }}')"
- echo " step: {{ step.kind | lower }} $_step"
-
- cap_new
- {% for part in step.parts %}{% if part.CapturedText is defined -%}
- _name="$(decode_base64 '{{ part.CapturedText.name | base64 }}')"
- _text="$(decode_base64 '{{ part.CapturedText.text | base64 }}')"
- cap_set "$_name" "$_text"
- {% endif -%}
- {% endfor -%}
- {{ step.function }}
+ if [ "$ret" = 0 ]
+ then
+ # Step: {{ step.text }}
+ step="{{ step.kind | lower }} $(decode_base64 '{{ step.text | base64 }}')"
+ echo " step: $step"
+
+ cap_new
+ {% for part in step.parts %}{% if part.CapturedText is defined -%}
+ name="$(decode_base64 '{{ part.CapturedText.name | base64 }}')"
+ text="$(decode_base64 '{{ part.CapturedText.text | base64 }}')"
+ cap_set "$name" "$text"
+ {% endif -%}
+ {% endfor -%}
+ if {{ step.function }}
+ then
+ cleanup='{{ step.cleanup }}'
+ if [ "$cleanup" != "" ]
+ then
+ {% raw %}
+ i=${#cleanups}
+ cleanups[$i]="$cleanup"
+ steps[$i]="$step"
+ {% endraw %}
+ fi
+ else
+ ret=$?
+ fi
+ fi
{% endfor %}
+
+ {% raw %}
+ echo "${!cleanups[*]}" | tr ' ' '\n' | tac | while read i
+ do
+ step="${steps[$i]}"
+ func="${cleanups[$i]}"
+ echo " cleanup: $step"
+ $func
+ done
+ {% endraw %}
+
+ return $ret
}
{% endfor %}
diff --git a/templates/python/template.py b/templates/python/template.py
index 62b1fc9..76d8239 100644
--- a/templates/python/template.py
+++ b/templates/python/template.py
@@ -82,36 +82,115 @@ os.chdir(_datadir)
#############################################################################
# Code to implement the scenarios.
+
+class Step:
+
+ def __init__(self):
+ self._kind = None
+ self._text = None
+ self._args = {}
+ self._function = None
+ self._cleanup = None
+
+ def set_kind(self, kind):
+ self._kind = kind
+
+ def set_text(self, text):
+ self._text = text
+
+ def set_arg(self, name, value):
+ self._args[name] = value
+
+ def set_function(self, function):
+ self._function = function
+
+ def set_cleanup(self, cleanup):
+ self._cleanup = cleanup
+
+ def do(self, ctx):
+ print(' step: {} {}'.format(self._kind, self._text))
+ logging.info(' step: {} {}'.format(self._kind, self._text))
+ self._function(ctx, **self._args)
+
+ def cleanup(self, ctx):
+ if self._cleanup:
+ print(' cleanup: {} {}'.format(self._kind, self._text))
+ logging.info(' cleanup: {} {}'.format(self._kind, self._text))
+ self._cleanup(ctx)
+ else:
+ logging.info(' no cleanup defined: {} {}'.format(self._kind, self._text))
+
+
+class Scenario:
+
+ def __init__(self):
+ self._title = None
+ self._steps = []
+
+ def get_title(self):
+ return self._title
+
+ def set_title(self, title):
+ self._title = title
+
+ def append_step(self, step):
+ self._steps.append(step)
+
+ def run(self):
+ print('scenario: {}'.format(self._title))
+ logging.info("Scenario: {}".format(self._title))
+
+ scendir = tempfile.mkdtemp(dir=_datadir)
+ os.chdir(scendir)
+
+ ctx = Context()
+ done = []
+ try:
+ for step in self._steps:
+ step.do(ctx)
+ done.append(step)
+ except Exception as e:
+ logging.error(str(e), exc_info=True)
+ for step in reversed(done):
+ step.cleanup(ctx)
+ raise
+ for step in reversed(done):
+ step.cleanup(ctx)
+
+
{% for scenario in scenarios %}
######################################
# Scenario: {{ scenario.title }}
-def scenario_{{ loop.index }}():
- title = decode_str('{{ scenario.title | base64 }}')
- print('scenario: {}'.format(title))
- logging.info("Scenario: {}".format(title))
- _scendir = tempfile.mkdtemp(dir=_datadir)
- os.chdir(_scendir)
- ctx = Context()
- {% for step in scenario.steps %}
- # Step: {{ step.text }}
- step = decode_str('{{ step.text | base64 }}')
- print(' step: {{ step.kind | lower }} {}'.format(step))
- logging.info(' step: {{ step.kind | lower }} {}'.format(step))
- args = {}
- {% for part in step.parts %}{% if part.CapturedText is defined -%}
- name = decode_str('{{ part.CapturedText.name | base64 }}')
- text = decode_str('{{ part.CapturedText.text | base64 }}')
- args[name] = text
- {% endif -%}
- {% endfor -%}
- {{ step.function }}(ctx, **args)
+class Scenario_{{ loop.index }}():
+ def __init__(self):
+ self._scenario = Scenario()
+ self._scenario.set_title(decode_str('{{ scenario.title | base64 }}'))
+ {% for step in scenario.steps %}
+ # Step: {{ step.text }}
+ step = Step()
+ step.set_kind('{{ step.kind | lower }}')
+ step.set_text(decode_str('{{ step.text | base64 }}'))
+ step.set_function({{ step.function }})
+ if '{{ step.cleanup }}':
+ step.set_cleanup({{ step.cleanup }})
+ self._scenario.append_step(step)
+ {% for part in step.parts %}{% if part.CapturedText is defined -%}
+ name = decode_str('{{ part.CapturedText.name | base64 }}')
+ text = decode_str('{{ part.CapturedText.text | base64 }}')
+ step.set_arg(name, text)
+ {% endif -%}
+ {% endfor -%}
{% endfor %}
-{% endfor %}
-_scenarios = {
-{% for scenario in scenarios %}
- '{{ scenario.title }}': scenario_{{ loop.index }},
+ def get_title(self):
+ return self._scenario.get_title()
+
+ def run(self):
+ self._scenario.run()
{% endfor %}
+
+_scenarios = { {% for scenario in scenarios %}
+ Scenario_{{ loop.index }}(),{% endfor %}
}
@@ -156,20 +235,20 @@ def main():
logging.info("patterns: {}".format(args.patterns))
if len(args.patterns) == 0:
logging.info("Executing all scenarios")
- funcs = list(_scenarios.values())
- random.shuffle(funcs)
+ todo = list(_scenarios)
+ random.shuffle(todo)
else:
logging.info("Executing requested scenarios only: {}".format(args.patterns))
patterns = [arg.lower() for arg in args.patterns]
- funcs = [
- func
- for title, func in _scenarios.items()
- if any(pattern in title.lower() for pattern in patterns)
+ todo = [
+ scen
+ for scen in _scenarios
+ if any(pattern in scen.get_title().lower() for pattern in patterns)
]
try:
- for func in funcs:
- func()
+ for scen in todo:
+ scen.run()
except Exception as e:
logging.error(str(e), exc_info=True)
if args.save_on_failure: