Skip to content

Commit e262568

Browse files
authored
Allure test plan support for behave (via #531)
1 parent 4fa3e3a commit e262568

File tree

7 files changed

+160
-20
lines changed

7 files changed

+160
-20
lines changed
Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import os
22
from tempfile import mkdtemp
3+
import allure_commons
34
from allure_commons_test.report import AllureReport
45
from behave.parser import Parser
56
from behave.runner import ModelRunner
67
from behave.configuration import Configuration
78
from behave.formatter._registry import make_formatters
89
from behave.formatter.base import StreamOpener
9-
import threading
10+
import tempfile
11+
from contextlib import contextmanager
1012

1113

1214
@given(u'feature definition')
1315
@given(u'feature definition {lang}')
1416
def feature_definition(context, **kwargs):
1517
parser = Parser(language=kwargs.get('lang', None))
16-
context.feature_definition = parser.parse(context.text)
18+
feature = parser.parse(context.text)
19+
if hasattr(context, "feature_definition"):
20+
context.feature_definition.append(feature)
21+
else:
22+
context.feature_definition = [feature]
1723

1824

1925
@given(u'hooks implementation')
@@ -22,26 +28,47 @@ def hooks_implementations(context):
2228
exec(context.text, context.globals)
2329

2430

31+
@given(u'test plan')
32+
def test_plan_helper(context):
33+
tmp_dir = os.environ.get("TEST_TMP")
34+
file, filename = tempfile.mkstemp(suffix=".json", dir=tmp_dir)
35+
os.environ["ALLURE_TESTPLAN_PATH"] = filename
36+
with os.fdopen(file, 'w') as tmp:
37+
tmp.write(context.text)
38+
context.test_plan = filename
39+
40+
2541
@when(u'I run behave with allure formatter')
2642
@when(u'I run behave with allure formatter with options "{args}"')
2743
def run_behave_with_allure(context, **kwargs):
28-
def run(context, **kwargs):
44+
with test_context():
2945
cmd_args = '-f allure_behave.formatter:AllureFormatter'
3046
cmd = '{options} {cmd}'.format(cmd=cmd_args, options=kwargs.get('args', ''))
3147
config = Configuration(command_args=cmd)
32-
3348
result_tmp_dir = mkdtemp(dir=os.environ.get('TEST_TMP', None))
3449
stream_opener = StreamOpener(filename=result_tmp_dir)
35-
36-
model_runner = ModelRunner(config, [context.feature_definition])
50+
model_runner = ModelRunner(config, context.feature_definition)
3751
model_runner.formatters = make_formatters(config, [stream_opener])
3852
model_runner.formatters[0].listener.fixture_context.enter()
3953
model_runner.hooks = getattr(context, 'globals', dict())
4054
model_runner.run()
41-
4255
model_runner.formatters[0].listener.__del__()
4356
context.allure_report = AllureReport(result_tmp_dir)
4457

45-
behave_tread = threading.Thread(target=run, args=(context,), kwargs=kwargs)
46-
behave_tread.start()
47-
behave_tread.join()
58+
os.environ.pop("ALLURE_TESTPLAN_PATH", None)
59+
60+
61+
@contextmanager
62+
def test_context():
63+
def _unregister_plugins():
64+
plugins = []
65+
for name, plugin in allure_commons.plugin_manager.list_name_plugin():
66+
allure_commons.plugin_manager.unregister(plugin=plugin, name=name)
67+
plugins.append(plugin)
68+
return plugins
69+
70+
plugins = _unregister_plugins()
71+
yield
72+
_unregister_plugins()
73+
for plugin in plugins:
74+
allure_commons.plugin_manager.register(plugin)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
Feature: Test plan
2+
Scenario: Select scenarios by fullname
3+
Given feature definition
4+
"""
5+
Feature: Test plan example
6+
7+
Scenario: Scenario with passed step
8+
Given passed step
9+
10+
Scenario: Ignored scenario
11+
Given passed step
12+
"""
13+
Given feature definition
14+
"""
15+
Feature: Another Test plan example
16+
17+
Scenario: Another scenario with passed step
18+
Given passed step
19+
20+
Scenario: Another ignored scenario
21+
Given passed step
22+
"""
23+
Given test plan
24+
"""
25+
{
26+
"version":"1.0",
27+
"tests": [
28+
{
29+
"selector": "<string>:Scenario with passed step"
30+
},
31+
{
32+
"selector": "<string>:Another scenario with passed step"
33+
}
34+
]
35+
}
36+
"""
37+
When I run behave with allure formatter
38+
Then allure report has a scenario with name "Scenario with passed step"
39+
Then allure report has not a scenario with name "Ignored scenario"
40+
Then allure report has a scenario with name "Another scenario with passed step"
41+
Then allure report has not a scenario with name "Another ignored scenario"
42+
43+
Scenario: Select scenarios by allureid
44+
Given feature definition
45+
"""
46+
Feature: Test plan example
47+
48+
@allure.as_id:1
49+
Scenario: Scenario with passed step
50+
Given passed step
51+
52+
@allure.as_id:2
53+
Scenario: Ignored scenario
54+
Given passed step
55+
"""
56+
Given feature definition
57+
"""
58+
Feature: Another Test plan example
59+
60+
@allure.as_id:3
61+
Scenario: Another scenario with passed step
62+
Given passed step
63+
64+
@allure.as_id:4
65+
Scenario: Another ignored scenario
66+
Given passed step
67+
"""
68+
Given test plan
69+
"""
70+
{
71+
"version":"1.0",
72+
"tests": [
73+
{
74+
"id": "1"
75+
},
76+
{
77+
"id": "3"
78+
}
79+
]
80+
}
81+
"""
82+
When I run behave with allure formatter
83+
Then allure report has a scenario with name "Scenario with passed step"
84+
Then allure report has not a scenario with name "Ignored scenario"
85+
Then allure report has a scenario with name "Another scenario with passed step"
86+
Then allure report has not a scenario with name "Another ignored scenario"

allure-behave/src/formatter.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import allure_commons
44
from allure_commons.logger import AllureFileLogger
55
from allure_behave.listener import AllureListener
6+
from allure_commons.utils import get_testplan
7+
from allure_behave.utils import is_planned_scenario
68

79

810
class AllureFormatter(Formatter):
@@ -15,12 +17,15 @@ def __init__(self, stream_opener, config):
1517
allure_commons.plugin_manager.register(self.listener)
1618
allure_commons.plugin_manager.register(file_logger)
1719

20+
self.testplan = get_testplan()
21+
1822
def _wrap_scenario(self, scenarios):
1923
for scenario in scenarios:
2024
if isinstance(scenario, ScenarioOutline):
2125
self._wrap_scenario(scenario)
2226
else:
2327
scenario.run = allure_commons.test(scenario.run, context={'scenario': scenario})
28+
is_planned_scenario(scenario, self.testplan)
2429

2530
def feature(self, feature):
2631
self._wrap_scenario(feature.scenarios)

allure-behave/src/listener.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from allure_behave.utils import scenario_links
2323
from allure_behave.utils import scenario_labels
2424
from allure_behave.utils import get_fullname
25+
from allure_behave.utils import TEST_PLAN_SKIP_REASON
2526

2627

2728
BEFORE_FIXTURES = ['before_all', 'before_tag', 'before_feature', 'before_scenario']
@@ -114,7 +115,8 @@ def stop_test(self, parent_uuid, uuid, name, context, exc_type, exc_val, exc_tb)
114115
self.stop_scenario(context['scenario'])
115116

116117
def stop_scenario(self, scenario):
117-
if scenario.status == 'skipped' and not self.behave_config.show_skipped:
118+
if scenario.status == 'skipped' \
119+
and not self.behave_config.show_skipped or scenario.skip_reason == TEST_PLAN_SKIP_REASON:
118120
self.logger.drop_test(self.current_scenario_uuid)
119121
else:
120122
status = scenario_status(scenario)

allure-behave/src/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from allure_commons.utils import format_exception, format_traceback
1212
from allure_commons.mapping import parse_tag, labels_set
1313

14+
TEST_PLAN_SKIP_REASON = "Not in allure test plan"
1415

1516
STATUS = {
1617
'passed': Status.PASSED,
@@ -114,3 +115,15 @@ def step_table(step):
114115
table = [','.join(step.table.headings)]
115116
[table.append(','.join(list(row))) for row in step.table.rows]
116117
return '\n'.join(table)
118+
119+
120+
def is_planned_scenario(scenario, test_plan):
121+
if test_plan:
122+
fullname = get_fullname(scenario)
123+
labels = scenario_labels(scenario)
124+
id_labels = list(filter(lambda label: label.name == LabelType.ID, labels))
125+
allure_id = id_labels[0].value if id_labels else None
126+
for item in test_plan:
127+
if (allure_id and allure_id == item.get("id")) or fullname == item.get("selector"):
128+
return
129+
scenario.skip(reason=TEST_PLAN_SKIP_REASON)

allure-pytest/src/plugin.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
import allure
44
import allure_commons
55
import os
6-
import json
76

87
from allure_commons.types import LabelType
98
from allure_commons.logger import AllureFileLogger
10-
9+
from allure_commons.utils import get_testplan
1110

1211
from allure_pytest.utils import allure_label, allure_labels, allure_full_name
1312
from allure_pytest.helper import AllureTestHelper
@@ -148,13 +147,7 @@ def select_by_labels(items, config):
148147

149148

150149
def select_by_testcase(items):
151-
planned_tests = []
152-
file_path = os.environ.get("ALLURE_TESTPLAN_PATH")
153-
154-
if file_path:
155-
with open(file_path, 'r') as plan_file:
156-
plan = json.load(plan_file)
157-
planned_tests = plan.get("tests", [])
150+
planned_tests = get_testplan()
158151

159152
if planned_tests:
160153

allure-python-commons/src/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import six
66
import time
77
import uuid
8+
import json
89
import socket
910
import inspect
1011
import hashlib
1112
import platform
1213
import threading
1314
import traceback
1415
import collections
16+
1517
from functools import partial
1618

1719

@@ -387,3 +389,15 @@ def format_exception(etype, value):
387389
"AssertionError: \\nExpected:...but:..."
388390
"""
389391
return '\n'.join(format_exception_only(etype, value)) if etype or value else None
392+
393+
394+
def get_testplan():
395+
planned_tests = []
396+
file_path = os.environ.get("ALLURE_TESTPLAN_PATH")
397+
398+
if file_path:
399+
with open(file_path, 'r') as plan_file:
400+
plan = json.load(plan_file)
401+
planned_tests = plan.get("tests", [])
402+
403+
return planned_tests

0 commit comments

Comments
 (0)