busser-behave 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. checksums.yaml +7 -0
  2. data/.cane +0 -0
  3. data/.gitignore +17 -0
  4. data/.tailor +4 -0
  5. data/.travis.yml +11 -0
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +15 -0
  9. data/README.md +41 -0
  10. data/Rakefile +68 -0
  11. data/busser-behave.gemspec +30 -0
  12. data/features/plugin_install_command.feature +11 -0
  13. data/features/plugin_list_command.feature +8 -0
  14. data/features/support/env.rb +13 -0
  15. data/features/test_command.feature +31 -0
  16. data/lib/busser/behave/version.rb +26 -0
  17. data/lib/busser/runner_plugin/behave.rb +37 -0
  18. data/vendor/behave/CHANGES.rst +483 -0
  19. data/vendor/behave/LICENSE +23 -0
  20. data/vendor/behave/MANIFEST.in +37 -0
  21. data/vendor/behave/PROJECT_INFO.rst +21 -0
  22. data/vendor/behave/README.rst +112 -0
  23. data/vendor/behave/VERSION.txt +1 -0
  24. data/vendor/behave/behave.ini +22 -0
  25. data/vendor/behave/behave/__init__.py +30 -0
  26. data/vendor/behave/behave/__main__.py +187 -0
  27. data/vendor/behave/behave/_stepimport.py +185 -0
  28. data/vendor/behave/behave/_types.py +134 -0
  29. data/vendor/behave/behave/api/__init__.py +7 -0
  30. data/vendor/behave/behave/api/async_step.py +283 -0
  31. data/vendor/behave/behave/capture.py +227 -0
  32. data/vendor/behave/behave/compat/__init__.py +5 -0
  33. data/vendor/behave/behave/compat/collections.py +20 -0
  34. data/vendor/behave/behave/configuration.py +788 -0
  35. data/vendor/behave/behave/contrib/__init__.py +0 -0
  36. data/vendor/behave/behave/contrib/scenario_autoretry.py +73 -0
  37. data/vendor/behave/behave/formatter/__init__.py +12 -0
  38. data/vendor/behave/behave/formatter/_builtins.py +39 -0
  39. data/vendor/behave/behave/formatter/_registry.py +135 -0
  40. data/vendor/behave/behave/formatter/ansi_escapes.py +91 -0
  41. data/vendor/behave/behave/formatter/base.py +200 -0
  42. data/vendor/behave/behave/formatter/formatters.py +57 -0
  43. data/vendor/behave/behave/formatter/json.py +253 -0
  44. data/vendor/behave/behave/formatter/null.py +12 -0
  45. data/vendor/behave/behave/formatter/plain.py +158 -0
  46. data/vendor/behave/behave/formatter/pretty.py +351 -0
  47. data/vendor/behave/behave/formatter/progress.py +287 -0
  48. data/vendor/behave/behave/formatter/rerun.py +114 -0
  49. data/vendor/behave/behave/formatter/sphinx_steps.py +372 -0
  50. data/vendor/behave/behave/formatter/sphinx_util.py +118 -0
  51. data/vendor/behave/behave/formatter/steps.py +497 -0
  52. data/vendor/behave/behave/formatter/tags.py +178 -0
  53. data/vendor/behave/behave/i18n.py +614 -0
  54. data/vendor/behave/behave/importer.py +102 -0
  55. data/vendor/behave/behave/json_parser.py +264 -0
  56. data/vendor/behave/behave/log_capture.py +233 -0
  57. data/vendor/behave/behave/matchers.py +402 -0
  58. data/vendor/behave/behave/model.py +1737 -0
  59. data/vendor/behave/behave/model_core.py +416 -0
  60. data/vendor/behave/behave/model_describe.py +105 -0
  61. data/vendor/behave/behave/parser.py +615 -0
  62. data/vendor/behave/behave/reporter/__init__.py +0 -0
  63. data/vendor/behave/behave/reporter/base.py +45 -0
  64. data/vendor/behave/behave/reporter/junit.py +473 -0
  65. data/vendor/behave/behave/reporter/summary.py +94 -0
  66. data/vendor/behave/behave/runner.py +753 -0
  67. data/vendor/behave/behave/runner_util.py +417 -0
  68. data/vendor/behave/behave/step_registry.py +112 -0
  69. data/vendor/behave/behave/tag_expression.py +111 -0
  70. data/vendor/behave/behave/tag_matcher.py +465 -0
  71. data/vendor/behave/behave/textutil.py +137 -0
  72. data/vendor/behave/behave/userdata.py +130 -0
  73. data/vendor/behave/behave4cmd0/__all_steps__.py +12 -0
  74. data/vendor/behave/behave4cmd0/__init__.py +5 -0
  75. data/vendor/behave/behave4cmd0/__setup.py +11 -0
  76. data/vendor/behave/behave4cmd0/command_shell.py +216 -0
  77. data/vendor/behave/behave4cmd0/command_shell_proc.py +256 -0
  78. data/vendor/behave/behave4cmd0/command_steps.py +532 -0
  79. data/vendor/behave/behave4cmd0/command_util.py +147 -0
  80. data/vendor/behave/behave4cmd0/failing_steps.py +49 -0
  81. data/vendor/behave/behave4cmd0/log/__init__.py +1 -0
  82. data/vendor/behave/behave4cmd0/log/steps.py +395 -0
  83. data/vendor/behave/behave4cmd0/note_steps.py +29 -0
  84. data/vendor/behave/behave4cmd0/passing_steps.py +36 -0
  85. data/vendor/behave/behave4cmd0/pathutil.py +146 -0
  86. data/vendor/behave/behave4cmd0/setup_command_shell.py +24 -0
  87. data/vendor/behave/behave4cmd0/textutil.py +304 -0
  88. data/vendor/behave/bin/behave +44 -0
  89. data/vendor/behave/bin/behave.cmd +10 -0
  90. data/vendor/behave/bin/behave.junit_filter.py +85 -0
  91. data/vendor/behave/bin/behave.step_durations.py +163 -0
  92. data/vendor/behave/bin/behave2cucumber_json.py +63 -0
  93. data/vendor/behave/bin/behave_cmd.py +44 -0
  94. data/vendor/behave/bin/convert_i18n_yaml.py +77 -0
  95. data/vendor/behave/bin/explore_platform_encoding.py +24 -0
  96. data/vendor/behave/bin/i18n.yml +621 -0
  97. data/vendor/behave/bin/invoke +8 -0
  98. data/vendor/behave/bin/invoke.cmd +9 -0
  99. data/vendor/behave/bin/json.format.py +167 -0
  100. data/vendor/behave/bin/jsonschema_validate.py +122 -0
  101. data/vendor/behave/bin/make_localpi.py +279 -0
  102. data/vendor/behave/bin/project_bootstrap.sh +30 -0
  103. data/vendor/behave/bin/toxcmd.py +270 -0
  104. data/vendor/behave/bin/toxcmd3.py +270 -0
  105. data/vendor/behave/conftest.py +27 -0
  106. data/vendor/behave/docs/Makefile +154 -0
  107. data/vendor/behave/docs/_static/agogo.css +501 -0
  108. data/vendor/behave/docs/_static/behave_logo.png +0 -0
  109. data/vendor/behave/docs/_static/behave_logo1.png +0 -0
  110. data/vendor/behave/docs/_static/behave_logo2.png +0 -0
  111. data/vendor/behave/docs/_static/behave_logo3.png +0 -0
  112. data/vendor/behave/docs/_themes/LICENSE +45 -0
  113. data/vendor/behave/docs/_themes/kr/layout.html +17 -0
  114. data/vendor/behave/docs/_themes/kr/relations.html +19 -0
  115. data/vendor/behave/docs/_themes/kr/static/flasky.css_t +480 -0
  116. data/vendor/behave/docs/_themes/kr/static/small_flask.css +90 -0
  117. data/vendor/behave/docs/_themes/kr/theme.conf +7 -0
  118. data/vendor/behave/docs/_themes/kr_small/layout.html +22 -0
  119. data/vendor/behave/docs/_themes/kr_small/static/flasky.css_t +287 -0
  120. data/vendor/behave/docs/_themes/kr_small/theme.conf +10 -0
  121. data/vendor/behave/docs/api.rst +408 -0
  122. data/vendor/behave/docs/appendix.rst +19 -0
  123. data/vendor/behave/docs/behave.rst +640 -0
  124. data/vendor/behave/docs/behave.rst-template +86 -0
  125. data/vendor/behave/docs/behave_ecosystem.rst +81 -0
  126. data/vendor/behave/docs/comparison.rst +85 -0
  127. data/vendor/behave/docs/conf.py +293 -0
  128. data/vendor/behave/docs/context_attributes.rst +66 -0
  129. data/vendor/behave/docs/django.rst +192 -0
  130. data/vendor/behave/docs/formatters.rst +61 -0
  131. data/vendor/behave/docs/gherkin.rst +673 -0
  132. data/vendor/behave/docs/index.rst +57 -0
  133. data/vendor/behave/docs/install.rst +60 -0
  134. data/vendor/behave/docs/more_info.rst +184 -0
  135. data/vendor/behave/docs/new_and_noteworthy.rst +18 -0
  136. data/vendor/behave/docs/new_and_noteworthy_v1.2.4.rst +11 -0
  137. data/vendor/behave/docs/new_and_noteworthy_v1.2.5.rst +814 -0
  138. data/vendor/behave/docs/new_and_noteworthy_v1.2.6.rst +255 -0
  139. data/vendor/behave/docs/parse_builtin_types.rst +59 -0
  140. data/vendor/behave/docs/philosophy.rst +235 -0
  141. data/vendor/behave/docs/regular_expressions.rst +71 -0
  142. data/vendor/behave/docs/related.rst +14 -0
  143. data/vendor/behave/docs/test_domains.rst +62 -0
  144. data/vendor/behave/docs/tutorial.rst +636 -0
  145. data/vendor/behave/docs/update_behave_rst.py +100 -0
  146. data/vendor/behave/etc/json/behave.json-schema +172 -0
  147. data/vendor/behave/etc/junit.xml/behave_junit.xsd +103 -0
  148. data/vendor/behave/etc/junit.xml/junit-4.xsd +92 -0
  149. data/vendor/behave/examples/async_step/README.txt +8 -0
  150. data/vendor/behave/examples/async_step/behave.ini +14 -0
  151. data/vendor/behave/examples/async_step/features/async_dispatch.feature +8 -0
  152. data/vendor/behave/examples/async_step/features/async_run.feature +6 -0
  153. data/vendor/behave/examples/async_step/features/environment.py +28 -0
  154. data/vendor/behave/examples/async_step/features/steps/async_dispatch_steps.py +26 -0
  155. data/vendor/behave/examples/async_step/features/steps/async_steps34.py +10 -0
  156. data/vendor/behave/examples/async_step/features/steps/async_steps35.py +10 -0
  157. data/vendor/behave/examples/async_step/testrun_example.async_dispatch.txt +11 -0
  158. data/vendor/behave/examples/async_step/testrun_example.async_run.txt +9 -0
  159. data/vendor/behave/examples/env_vars/README.rst +26 -0
  160. data/vendor/behave/examples/env_vars/behave.ini +15 -0
  161. data/vendor/behave/examples/env_vars/behave_run.output_example.txt +12 -0
  162. data/vendor/behave/examples/env_vars/features/env_var.feature +6 -0
  163. data/vendor/behave/examples/env_vars/features/steps/env_var_steps.py +38 -0
  164. data/vendor/behave/features/README.txt +12 -0
  165. data/vendor/behave/features/background.feature +392 -0
  166. data/vendor/behave/features/capture_stderr.feature +172 -0
  167. data/vendor/behave/features/capture_stdout.feature +125 -0
  168. data/vendor/behave/features/cmdline.lang_list.feature +33 -0
  169. data/vendor/behave/features/configuration.default_paths.feature +116 -0
  170. data/vendor/behave/features/context.global_params.feature +35 -0
  171. data/vendor/behave/features/context.local_params.feature +17 -0
  172. data/vendor/behave/features/directory_layout.advanced.feature +147 -0
  173. data/vendor/behave/features/directory_layout.basic.feature +75 -0
  174. data/vendor/behave/features/directory_layout.basic2.feature +87 -0
  175. data/vendor/behave/features/environment.py +53 -0
  176. data/vendor/behave/features/exploratory_testing.with_table.feature +141 -0
  177. data/vendor/behave/features/feature.description.feature +0 -0
  178. data/vendor/behave/features/feature.exclude_from_run.feature +96 -0
  179. data/vendor/behave/features/formatter.help.feature +30 -0
  180. data/vendor/behave/features/formatter.json.feature +420 -0
  181. data/vendor/behave/features/formatter.progress3.feature +235 -0
  182. data/vendor/behave/features/formatter.rerun.feature +296 -0
  183. data/vendor/behave/features/formatter.steps.feature +181 -0
  184. data/vendor/behave/features/formatter.steps_catalog.feature +100 -0
  185. data/vendor/behave/features/formatter.steps_doc.feature +140 -0
  186. data/vendor/behave/features/formatter.steps_usage.feature +404 -0
  187. data/vendor/behave/features/formatter.tags.feature +134 -0
  188. data/vendor/behave/features/formatter.tags_location.feature +183 -0
  189. data/vendor/behave/features/formatter.user_defined.feature +196 -0
  190. data/vendor/behave/features/i18n.unicode_problems.feature +445 -0
  191. data/vendor/behave/features/logcapture.clear_handlers.feature +114 -0
  192. data/vendor/behave/features/logcapture.feature +188 -0
  193. data/vendor/behave/features/logcapture.filter.feature +130 -0
  194. data/vendor/behave/features/logging.no_capture.feature +99 -0
  195. data/vendor/behave/features/logging.setup_format.feature +157 -0
  196. data/vendor/behave/features/logging.setup_level.feature +168 -0
  197. data/vendor/behave/features/logging.setup_with_configfile.feature +137 -0
  198. data/vendor/behave/features/parser.background.sad_cases.feature +129 -0
  199. data/vendor/behave/features/parser.feature.sad_cases.feature +144 -0
  200. data/vendor/behave/features/runner.abort_by_user.feature +305 -0
  201. data/vendor/behave/features/runner.continue_after_failed_step.feature +136 -0
  202. data/vendor/behave/features/runner.default_format.feature +175 -0
  203. data/vendor/behave/features/runner.dry_run.feature +184 -0
  204. data/vendor/behave/features/runner.feature_listfile.feature +223 -0
  205. data/vendor/behave/features/runner.hook_errors.feature +382 -0
  206. data/vendor/behave/features/runner.multiple_formatters.feature +285 -0
  207. data/vendor/behave/features/runner.scenario_autoretry.feature +131 -0
  208. data/vendor/behave/features/runner.select_files_by_regexp.example.feature +71 -0
  209. data/vendor/behave/features/runner.select_files_by_regexp.feature +84 -0
  210. data/vendor/behave/features/runner.select_scenarios_by_file_location.feature +403 -0
  211. data/vendor/behave/features/runner.select_scenarios_by_name.feature +289 -0
  212. data/vendor/behave/features/runner.select_scenarios_by_tag.feature +225 -0
  213. data/vendor/behave/features/runner.stop_after_failure.feature +122 -0
  214. data/vendor/behave/features/runner.tag_logic.feature +67 -0
  215. data/vendor/behave/features/runner.unknown_formatter.feature +23 -0
  216. data/vendor/behave/features/runner.use_stage_implementations.feature +126 -0
  217. data/vendor/behave/features/scenario.description.feature +171 -0
  218. data/vendor/behave/features/scenario.exclude_from_run.feature +217 -0
  219. data/vendor/behave/features/scenario_outline.basics.feature +100 -0
  220. data/vendor/behave/features/scenario_outline.improved.feature +177 -0
  221. data/vendor/behave/features/scenario_outline.name_annotation.feature +157 -0
  222. data/vendor/behave/features/scenario_outline.parametrized.feature +401 -0
  223. data/vendor/behave/features/scenario_outline.tagged_examples.feature +118 -0
  224. data/vendor/behave/features/step.async_steps.feature +225 -0
  225. data/vendor/behave/features/step.duplicated_step.feature +106 -0
  226. data/vendor/behave/features/step.execute_steps.feature +59 -0
  227. data/vendor/behave/features/step.execute_steps.with_table.feature +65 -0
  228. data/vendor/behave/features/step.import_other_step_module.feature +103 -0
  229. data/vendor/behave/features/step.pending_steps.feature +128 -0
  230. data/vendor/behave/features/step.undefined_steps.feature +307 -0
  231. data/vendor/behave/features/step.use_step_library.feature +44 -0
  232. data/vendor/behave/features/step_dialect.generic_steps.feature +189 -0
  233. data/vendor/behave/features/step_dialect.given_when_then.feature +89 -0
  234. data/vendor/behave/features/step_param.builtin_types.with_float.feature +239 -0
  235. data/vendor/behave/features/step_param.builtin_types.with_integer.feature +305 -0
  236. data/vendor/behave/features/step_param.custom_types.feature +134 -0
  237. data/vendor/behave/features/steps/behave_active_tags_steps.py +86 -0
  238. data/vendor/behave/features/steps/behave_context_steps.py +67 -0
  239. data/vendor/behave/features/steps/behave_model_tag_logic_steps.py +105 -0
  240. data/vendor/behave/features/steps/behave_model_util.py +105 -0
  241. data/vendor/behave/features/steps/behave_select_files_steps.py +83 -0
  242. data/vendor/behave/features/steps/behave_tag_expression_steps.py +166 -0
  243. data/vendor/behave/features/steps/behave_undefined_steps.py +101 -0
  244. data/vendor/behave/features/steps/use_steplib_behave4cmd.py +12 -0
  245. data/vendor/behave/features/summary.undefined_steps.feature +114 -0
  246. data/vendor/behave/features/tags.active_tags.feature +385 -0
  247. data/vendor/behave/features/tags.default_tags.feature +104 -0
  248. data/vendor/behave/features/tags.tag_expression.feature +105 -0
  249. data/vendor/behave/features/userdata.feature +331 -0
  250. data/vendor/behave/invoke.yaml +21 -0
  251. data/vendor/behave/issue.features/README.txt +17 -0
  252. data/vendor/behave/issue.features/environment.py +97 -0
  253. data/vendor/behave/issue.features/issue0030.feature +21 -0
  254. data/vendor/behave/issue.features/issue0031.feature +16 -0
  255. data/vendor/behave/issue.features/issue0032.feature +28 -0
  256. data/vendor/behave/issue.features/issue0035.feature +74 -0
  257. data/vendor/behave/issue.features/issue0040.feature +154 -0
  258. data/vendor/behave/issue.features/issue0041.feature +135 -0
  259. data/vendor/behave/issue.features/issue0042.feature +230 -0
  260. data/vendor/behave/issue.features/issue0044.feature +51 -0
  261. data/vendor/behave/issue.features/issue0046.feature +77 -0
  262. data/vendor/behave/issue.features/issue0052.feature +66 -0
  263. data/vendor/behave/issue.features/issue0059.feature +29 -0
  264. data/vendor/behave/issue.features/issue0063.feature +102 -0
  265. data/vendor/behave/issue.features/issue0064.feature +97 -0
  266. data/vendor/behave/issue.features/issue0065.feature +18 -0
  267. data/vendor/behave/issue.features/issue0066.feature +80 -0
  268. data/vendor/behave/issue.features/issue0067.feature +90 -0
  269. data/vendor/behave/issue.features/issue0069.feature +64 -0
  270. data/vendor/behave/issue.features/issue0072.feature +32 -0
  271. data/vendor/behave/issue.features/issue0073.feature +228 -0
  272. data/vendor/behave/issue.features/issue0075.feature +18 -0
  273. data/vendor/behave/issue.features/issue0077.feature +89 -0
  274. data/vendor/behave/issue.features/issue0080.feature +49 -0
  275. data/vendor/behave/issue.features/issue0081.feature +138 -0
  276. data/vendor/behave/issue.features/issue0083.feature +69 -0
  277. data/vendor/behave/issue.features/issue0084.feature +69 -0
  278. data/vendor/behave/issue.features/issue0085.feature +119 -0
  279. data/vendor/behave/issue.features/issue0092.feature +66 -0
  280. data/vendor/behave/issue.features/issue0096.feature +173 -0
  281. data/vendor/behave/issue.features/issue0099.feature +130 -0
  282. data/vendor/behave/issue.features/issue0109.feature +60 -0
  283. data/vendor/behave/issue.features/issue0111.feature +53 -0
  284. data/vendor/behave/issue.features/issue0112.feature +64 -0
  285. data/vendor/behave/issue.features/issue0114.feature +118 -0
  286. data/vendor/behave/issue.features/issue0116.feature +71 -0
  287. data/vendor/behave/issue.features/issue0125.feature +49 -0
  288. data/vendor/behave/issue.features/issue0127.feature +64 -0
  289. data/vendor/behave/issue.features/issue0139.feature +67 -0
  290. data/vendor/behave/issue.features/issue0142.feature +37 -0
  291. data/vendor/behave/issue.features/issue0143.feature +54 -0
  292. data/vendor/behave/issue.features/issue0145.feature +63 -0
  293. data/vendor/behave/issue.features/issue0148.feature +105 -0
  294. data/vendor/behave/issue.features/issue0152.feature +52 -0
  295. data/vendor/behave/issue.features/issue0159.feature +74 -0
  296. data/vendor/behave/issue.features/issue0162.feature +86 -0
  297. data/vendor/behave/issue.features/issue0171.feature +16 -0
  298. data/vendor/behave/issue.features/issue0172.feature +51 -0
  299. data/vendor/behave/issue.features/issue0175.feature +91 -0
  300. data/vendor/behave/issue.features/issue0177.feature +40 -0
  301. data/vendor/behave/issue.features/issue0181.feature +36 -0
  302. data/vendor/behave/issue.features/issue0184.feature +144 -0
  303. data/vendor/behave/issue.features/issue0186.feature +12 -0
  304. data/vendor/behave/issue.features/issue0188.feature +60 -0
  305. data/vendor/behave/issue.features/issue0191.feature +178 -0
  306. data/vendor/behave/issue.features/issue0194.feature +215 -0
  307. data/vendor/behave/issue.features/issue0197.feature +11 -0
  308. data/vendor/behave/issue.features/issue0216.feature +129 -0
  309. data/vendor/behave/issue.features/issue0226.feature +51 -0
  310. data/vendor/behave/issue.features/issue0228.feature +41 -0
  311. data/vendor/behave/issue.features/issue0230.feature +46 -0
  312. data/vendor/behave/issue.features/issue0231.feature +77 -0
  313. data/vendor/behave/issue.features/issue0238.feature +52 -0
  314. data/vendor/behave/issue.features/issue0251.feature +15 -0
  315. data/vendor/behave/issue.features/issue0280.feature +118 -0
  316. data/vendor/behave/issue.features/issue0288.feature +95 -0
  317. data/vendor/behave/issue.features/issue0300.feature +49 -0
  318. data/vendor/behave/issue.features/issue0302.feature +91 -0
  319. data/vendor/behave/issue.features/issue0309.feature +52 -0
  320. data/vendor/behave/issue.features/issue0330.feature +124 -0
  321. data/vendor/behave/issue.features/issue0349.feature +9 -0
  322. data/vendor/behave/issue.features/issue0361.feature +79 -0
  323. data/vendor/behave/issue.features/issue0383.feature +76 -0
  324. data/vendor/behave/issue.features/issue0384.feature +103 -0
  325. data/vendor/behave/issue.features/issue0385.feature +109 -0
  326. data/vendor/behave/issue.features/issue0424.feature +66 -0
  327. data/vendor/behave/issue.features/issue0446.feature +116 -0
  328. data/vendor/behave/issue.features/issue0449.feature +42 -0
  329. data/vendor/behave/issue.features/issue0453.feature +42 -0
  330. data/vendor/behave/issue.features/issue0457.feature +65 -0
  331. data/vendor/behave/issue.features/issue0462.feature +38 -0
  332. data/vendor/behave/issue.features/issue0476.feature +39 -0
  333. data/vendor/behave/issue.features/issue0487.feature +92 -0
  334. data/vendor/behave/issue.features/issue0506.feature +77 -0
  335. data/vendor/behave/issue.features/issue0510.feature +51 -0
  336. data/vendor/behave/issue.features/requirements.txt +12 -0
  337. data/vendor/behave/issue.features/steps/ansi_steps.py +20 -0
  338. data/vendor/behave/issue.features/steps/behave_hooks_steps.py +10 -0
  339. data/vendor/behave/issue.features/steps/use_steplib_behave4cmd.py +13 -0
  340. data/vendor/behave/more.features/formatter.json.validate_output.feature +37 -0
  341. data/vendor/behave/more.features/steps/tutorial_steps.py +16 -0
  342. data/vendor/behave/more.features/steps/use_steplib_behave4cmd.py +7 -0
  343. data/vendor/behave/more.features/tutorial.feature +6 -0
  344. data/vendor/behave/py.requirements/README.txt +5 -0
  345. data/vendor/behave/py.requirements/all.txt +16 -0
  346. data/vendor/behave/py.requirements/basic.txt +21 -0
  347. data/vendor/behave/py.requirements/develop.txt +28 -0
  348. data/vendor/behave/py.requirements/docs.txt +6 -0
  349. data/vendor/behave/py.requirements/json.txt +7 -0
  350. data/vendor/behave/py.requirements/more_py26.txt +8 -0
  351. data/vendor/behave/py.requirements/testing.txt +10 -0
  352. data/vendor/behave/pytest.ini +24 -0
  353. data/vendor/behave/setup.cfg +29 -0
  354. data/vendor/behave/setup.py +118 -0
  355. data/vendor/behave/setuptools_behave.py +130 -0
  356. data/vendor/behave/tasks/__behave.py +45 -0
  357. data/vendor/behave/tasks/__init__.py +55 -0
  358. data/vendor/behave/tasks/__main__.py +70 -0
  359. data/vendor/behave/tasks/_setup.py +135 -0
  360. data/vendor/behave/tasks/_vendor/README.rst +35 -0
  361. data/vendor/behave/tasks/_vendor/invoke.zip +0 -0
  362. data/vendor/behave/tasks/_vendor/path.py +1725 -0
  363. data/vendor/behave/tasks/_vendor/pathlib.py +1280 -0
  364. data/vendor/behave/tasks/_vendor/six.py +868 -0
  365. data/vendor/behave/tasks/clean.py +246 -0
  366. data/vendor/behave/tasks/docs.py +97 -0
  367. data/vendor/behave/tasks/requirements.txt +17 -0
  368. data/vendor/behave/tasks/test.py +192 -0
  369. data/vendor/behave/test/__init__.py +0 -0
  370. data/vendor/behave/test/_importer_candidate.py +3 -0
  371. data/vendor/behave/test/reporters/__init__.py +0 -0
  372. data/vendor/behave/test/reporters/test_summary.py +240 -0
  373. data/vendor/behave/test/test_ansi_escapes.py +73 -0
  374. data/vendor/behave/test/test_configuration.py +172 -0
  375. data/vendor/behave/test/test_formatter.py +265 -0
  376. data/vendor/behave/test/test_formatter_progress.py +39 -0
  377. data/vendor/behave/test/test_formatter_rerun.py +97 -0
  378. data/vendor/behave/test/test_formatter_tags.py +57 -0
  379. data/vendor/behave/test/test_importer.py +151 -0
  380. data/vendor/behave/test/test_log_capture.py +29 -0
  381. data/vendor/behave/test/test_matchers.py +236 -0
  382. data/vendor/behave/test/test_model.py +871 -0
  383. data/vendor/behave/test/test_parser.py +1590 -0
  384. data/vendor/behave/test/test_runner.py +1074 -0
  385. data/vendor/behave/test/test_step_registry.py +96 -0
  386. data/vendor/behave/test/test_tag_expression.py +506 -0
  387. data/vendor/behave/test/test_tag_expression2.py +462 -0
  388. data/vendor/behave/test/test_tag_matcher.py +729 -0
  389. data/vendor/behave/test/test_userdata.py +184 -0
  390. data/vendor/behave/tests/README.txt +12 -0
  391. data/vendor/behave/tests/__init__.py +0 -0
  392. data/vendor/behave/tests/api/__ONLY_PY34_or_newer.txt +0 -0
  393. data/vendor/behave/tests/api/__init__.py +0 -0
  394. data/vendor/behave/tests/api/_test_async_step34.py +130 -0
  395. data/vendor/behave/tests/api/_test_async_step35.py +75 -0
  396. data/vendor/behave/tests/api/test_async_step.py +18 -0
  397. data/vendor/behave/tests/api/testing_support.py +94 -0
  398. data/vendor/behave/tests/api/testing_support_async.py +21 -0
  399. data/vendor/behave/tests/issues/test_issue0336.py +66 -0
  400. data/vendor/behave/tests/issues/test_issue0449.py +55 -0
  401. data/vendor/behave/tests/issues/test_issue0453.py +62 -0
  402. data/vendor/behave/tests/issues/test_issue0458.py +54 -0
  403. data/vendor/behave/tests/issues/test_issue0495.py +65 -0
  404. data/vendor/behave/tests/unit/__init__.py +0 -0
  405. data/vendor/behave/tests/unit/test_behave4cmd_command_shell_proc.py +135 -0
  406. data/vendor/behave/tests/unit/test_capture.py +280 -0
  407. data/vendor/behave/tests/unit/test_model_core.py +56 -0
  408. data/vendor/behave/tests/unit/test_textutil.py +267 -0
  409. data/vendor/behave/tools/test-features/background.feature +9 -0
  410. data/vendor/behave/tools/test-features/environment.py +8 -0
  411. data/vendor/behave/tools/test-features/french.feature +11 -0
  412. data/vendor/behave/tools/test-features/outline.feature +39 -0
  413. data/vendor/behave/tools/test-features/parse.feature +10 -0
  414. data/vendor/behave/tools/test-features/step-data.feature +60 -0
  415. data/vendor/behave/tools/test-features/steps/steps.py +120 -0
  416. data/vendor/behave/tools/test-features/tags.feature +18 -0
  417. data/vendor/behave/tox.ini +159 -0
  418. metadata +562 -0
@@ -0,0 +1,402 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ This module provides the step matchers functionality that matches a
4
+ step definition (as text) with step-functions that implement this step.
5
+ """
6
+
7
+ from __future__ import absolute_import, print_function, with_statement
8
+ import copy
9
+ import re
10
+ import parse
11
+ import six
12
+ from parse_type import cfparse
13
+ from behave._types import ChainedExceptionUtil, ExceptionUtil
14
+ from behave.model_core import Argument, FileLocation, Replayable
15
+
16
+
17
+ # -----------------------------------------------------------------------------
18
+ # SECTION: Exceptions
19
+ # -----------------------------------------------------------------------------
20
+ class StepParseError(ValueError):
21
+ """Exception class, used when step matching fails before a step is run.
22
+ This is normally the case when an error occurs during the type conversion
23
+ of step parameters.
24
+ """
25
+
26
+ def __init__(self, text=None, exc_cause=None):
27
+ if not text and exc_cause:
28
+ text = six.text_type(exc_cause)
29
+ if exc_cause and six.PY2:
30
+ # -- NOTE: Python2 does not show chained-exception causes.
31
+ # Therefore, provide some hint (see also: PEP-3134).
32
+ cause_text = ExceptionUtil.describe(exc_cause,
33
+ use_traceback=True,
34
+ prefix="CAUSED-BY: ")
35
+ text += u"\n" + cause_text
36
+
37
+ ValueError.__init__(self, text)
38
+ if exc_cause:
39
+ # -- CHAINED EXCEPTION (see: PEP 3134)
40
+ ChainedExceptionUtil.set_cause(self, exc_cause)
41
+
42
+
43
+
44
+ # -----------------------------------------------------------------------------
45
+ # SECTION: Model Elements
46
+ # -----------------------------------------------------------------------------
47
+ class Match(Replayable):
48
+ """An parameter-matched *feature file* step name extracted using
49
+ step decorator `parameters`_.
50
+
51
+ .. attribute:: func
52
+
53
+ The step function that this match will be applied to.
54
+
55
+ .. attribute:: arguments
56
+
57
+ A list of :class:`~behave.model_core.Argument` instances containing the
58
+ matched parameters from the step name.
59
+ """
60
+ type = "match"
61
+
62
+ def __init__(self, func, arguments=None):
63
+ super(Match, self).__init__()
64
+ self.func = func
65
+ self.arguments = arguments
66
+ self.location = None
67
+ if func:
68
+ self.location = self.make_location(func)
69
+
70
+ def __repr__(self):
71
+ if self.func:
72
+ func_name = self.func.__name__
73
+ else:
74
+ func_name = '<no function>'
75
+ return '<Match %s, %s>' % (func_name, self.location)
76
+
77
+ def __eq__(self, other):
78
+ if not isinstance(other, Match):
79
+ return False
80
+ return (self.func, self.location) == (other.func, other.location)
81
+
82
+ def with_arguments(self, arguments):
83
+ match = copy.copy(self)
84
+ match.arguments = arguments
85
+ return match
86
+
87
+ def run(self, context):
88
+ args = []
89
+ kwargs = {}
90
+ for arg in self.arguments:
91
+ if arg.name is not None:
92
+ kwargs[arg.name] = arg.value
93
+ else:
94
+ args.append(arg.value)
95
+
96
+ with context.use_with_user_mode():
97
+ self.func(context, *args, **kwargs)
98
+
99
+ @staticmethod
100
+ def make_location(step_function):
101
+ """Extracts the location information from the step function and
102
+ builds a FileLocation object with (filename, line_number) info.
103
+
104
+ :param step_function: Function whose location should be determined.
105
+ :return: FileLocation object for step function.
106
+ """
107
+ return FileLocation.for_function(step_function)
108
+
109
+
110
+ class NoMatch(Match):
111
+ """Used for an "undefined step" when it can not be matched with a
112
+ step definition.
113
+ """
114
+
115
+ def __init__(self):
116
+ Match.__init__(self, func=None)
117
+ self.func = None
118
+ self.arguments = []
119
+ self.location = None
120
+
121
+
122
+ class MatchWithError(Match):
123
+ """Match class when error occur during step-matching
124
+
125
+ REASON:
126
+ * Type conversion error occured.
127
+ * ...
128
+ """
129
+ def __init__(self, func, error):
130
+ if not ExceptionUtil.has_traceback(error):
131
+ ExceptionUtil.set_traceback(error)
132
+ Match.__init__(self, func=func)
133
+ self.stored_error = error
134
+
135
+ def run(self, context):
136
+ """Raises stored error from step matching phase (type conversion)."""
137
+ raise StepParseError(exc_cause=self.stored_error)
138
+
139
+
140
+
141
+
142
+ # -----------------------------------------------------------------------------
143
+ # SECTION: Matchers
144
+ # -----------------------------------------------------------------------------
145
+ class Matcher(object):
146
+ """Pull parameters out of step names.
147
+
148
+ .. attribute:: string
149
+
150
+ The match pattern attached to the step function.
151
+
152
+ .. attribute:: func
153
+
154
+ The step function the pattern is being attached to.
155
+ """
156
+ schema = u"@%s('%s')" # Schema used to describe step definition (matcher)
157
+
158
+ def __init__(self, func, string, step_type=None):
159
+ self.func = func
160
+ self.string = string
161
+ self.step_type = step_type
162
+ self._location = None
163
+
164
+ @property
165
+ def location(self):
166
+ if self._location is None:
167
+ self._location = Match.make_location(self.func)
168
+ return self._location
169
+
170
+ def describe(self, schema=None):
171
+ """Provide a textual description of the step function/matcher object.
172
+
173
+ :param schema: Text schema to use.
174
+ :return: Textual description of this step definition (matcher).
175
+ """
176
+ step_type = self.step_type or "step"
177
+ if not schema:
178
+ schema = self.schema
179
+ return schema % (step_type, self.string)
180
+
181
+
182
+ def check_match(self, step):
183
+ """Match me against the "step" name supplied.
184
+
185
+ Return None, if I don't match otherwise return a list of matches as
186
+ :class:`~behave.model_core.Argument` instances.
187
+
188
+ The return value from this function will be converted into a
189
+ :class:`~behave.matchers.Match` instance by *behave*.
190
+ """
191
+ raise NotImplementedError
192
+
193
+ def match(self, step):
194
+ # -- PROTECT AGAINST: Type conversion errors (with ParseMatcher).
195
+ try:
196
+ result = self.check_match(step)
197
+ except Exception as e: # pylint: disable=broad-except
198
+ return MatchWithError(self.func, e)
199
+
200
+ if result is None:
201
+ return None # -- NO-MATCH
202
+ return Match(self.func, result)
203
+
204
+ def __repr__(self):
205
+ return u"<%s: %r>" % (self.__class__.__name__, self.string)
206
+
207
+
208
+ class ParseMatcher(Matcher):
209
+ custom_types = {}
210
+
211
+ def __init__(self, func, string, step_type=None):
212
+ super(ParseMatcher, self).__init__(func, string, step_type)
213
+ self.parser = parse.compile(self.string, self.custom_types)
214
+
215
+ def check_match(self, step):
216
+ # -- FAILURE-POINT: Type conversion of parameters may fail here.
217
+ # NOTE: Type converter should raise ValueError in case of PARSE ERRORS.
218
+ result = self.parser.parse(step)
219
+ if not result:
220
+ return None
221
+
222
+ args = []
223
+ for index, value in enumerate(result.fixed):
224
+ start, end = result.spans[index]
225
+ args.append(Argument(start, end, step[start:end], value))
226
+ for name, value in result.named.items():
227
+ start, end = result.spans[name]
228
+ args.append(Argument(start, end, step[start:end], value, name))
229
+ args.sort(key=lambda x: x.start)
230
+ return args
231
+
232
+ class CFParseMatcher(ParseMatcher):
233
+ """
234
+ Uses :class:`~parse_type.cfparse.Parser` instead of "parse.Parser".
235
+ Provides support for automatic generation of type variants
236
+ for fields with CardinalityField part.
237
+ """
238
+ def __init__(self, func, string, step_type=None):
239
+ super(CFParseMatcher, self).__init__(func, string, step_type)
240
+ self.parser = cfparse.Parser(self.string, self.custom_types)
241
+
242
+
243
+ def register_type(**kw):
244
+ # pylint: disable=anomalous-backslash-in-string
245
+ # REQUIRED-BY: code example
246
+ """Registers a custom type that will be available to "parse"
247
+ for type conversion during step matching.
248
+
249
+ Converters should be supplied as ``name=callable`` arguments (or as dict).
250
+
251
+ A type converter should follow :pypi:`parse` module rules.
252
+ In general, a type converter is a function that converts text (as string)
253
+ into a value-type (type converted value).
254
+
255
+ EXAMPLE:
256
+
257
+ .. code-block:: python
258
+
259
+ from behave import register_type, given
260
+ import parse
261
+
262
+ # -- TYPE CONVERTER: For a simple, positive integer number.
263
+ @parse.with_pattern(r"\d+")
264
+ def parse_number(text):
265
+ return int(text)
266
+
267
+ # -- REGISTER TYPE-CONVERTER: With behave
268
+ register_type(Number=parse_number)
269
+
270
+ # -- STEP DEFINITIONS: Use type converter.
271
+ @given('{amount:Number} vehicles')
272
+ def step_impl(context, amount):
273
+ assert isinstance(amount, int)
274
+ """
275
+ ParseMatcher.custom_types.update(kw)
276
+
277
+
278
+ class RegexMatcher(Matcher):
279
+ def __init__(self, func, string, step_type=None):
280
+ super(RegexMatcher, self).__init__(func, string, step_type)
281
+ self.regex = re.compile(self.string)
282
+
283
+ def check_match(self, step):
284
+ m = self.regex.match(step)
285
+ if not m:
286
+ return None
287
+
288
+ groupindex = dict((y, x) for x, y in self.regex.groupindex.items())
289
+ args = []
290
+ for index, group in enumerate(m.groups()):
291
+ index += 1
292
+ name = groupindex.get(index, None)
293
+ args.append(Argument(m.start(index), m.end(index), group,
294
+ group, name))
295
+
296
+ return args
297
+
298
+ class SimplifiedRegexMatcher(RegexMatcher):
299
+ """Simplified regular expression step-matcher that automatically adds
300
+ start-of-line/end-of-line matcher symbols to string:
301
+
302
+ .. code-block:: python
303
+
304
+ @when(u'a step passes') # re.pattern = "^a step passes$"
305
+ def step_impl(context): pass
306
+ """
307
+
308
+ def __init__(self, func, string, step_type=None):
309
+ assert not (string.startswith("^") or string.endswith("$")), \
310
+ "Regular expression should not use begin/end-markers: "+ string
311
+ expression = "^%s$" % string
312
+ super(SimplifiedRegexMatcher, self).__init__(func, expression, step_type)
313
+ self.string = string
314
+
315
+
316
+ class CucumberRegexMatcher(RegexMatcher):
317
+ """Compatible to (old) Cucumber style regular expressions.
318
+ Text must contain start-of-line/end-of-line matcher symbols to string:
319
+
320
+ .. code-block:: python
321
+
322
+ @when(u'^a step passes$') # re.pattern = "^a step passes$"
323
+ def step_impl(context): pass
324
+ """
325
+
326
+ matcher_mapping = {
327
+ "parse": ParseMatcher,
328
+ "cfparse": CFParseMatcher,
329
+ "re": SimplifiedRegexMatcher,
330
+
331
+ # -- BACKWARD-COMPATIBLE REGEX MATCHER: Old Cucumber compatible style.
332
+ # To make it the default step-matcher use the following snippet:
333
+ # # -- FILE: features/environment.py
334
+ # from behave import use_step_matcher
335
+ # def before_all(context):
336
+ # use_step_matcher("re0")
337
+ "re0": CucumberRegexMatcher,
338
+ }
339
+ current_matcher = ParseMatcher # pylint: disable=invalid-name
340
+
341
+
342
+ def use_step_matcher(name):
343
+ """Change the parameter matcher used in parsing step text.
344
+
345
+ The change is immediate and may be performed between step definitions in
346
+ your step implementation modules - allowing adjacent steps to use different
347
+ matchers if necessary.
348
+
349
+ There are several parsers available in *behave* (by default):
350
+
351
+ **parse** (the default, based on: :pypi:`parse`)
352
+ Provides a simple parser that replaces regular expressions for
353
+ step parameters with a readable syntax like ``{param:Type}``.
354
+ The syntax is inspired by the Python builtin ``string.format()``
355
+ function.
356
+ Step parameters must use the named fields syntax of :pypi:`parse`
357
+ in step definitions. The named fields are extracted,
358
+ optionally type converted and then used as step function arguments.
359
+
360
+ Supports type conversions by using type converters
361
+ (see :func:`~behave.register_type()`).
362
+
363
+ **cfparse** (extends: :pypi:`parse`, requires: :pypi:`parse_type`)
364
+ Provides an extended parser with "Cardinality Field" (CF) support.
365
+ Automatically creates missing type converters for related cardinality
366
+ as long as a type converter for cardinality=1 is provided.
367
+ Supports parse expressions like:
368
+
369
+ * ``{values:Type+}`` (cardinality=1..N, many)
370
+ * ``{values:Type*}`` (cardinality=0..N, many0)
371
+ * ``{value:Type?}`` (cardinality=0..1, optional)
372
+
373
+ Supports type conversions (as above).
374
+
375
+ **re**
376
+ This uses full regular expressions to parse the clause text. You will
377
+ need to use named groups "(?P<name>...)" to define the variables pulled
378
+ from the text and passed to your ``step()`` function.
379
+
380
+ Type conversion is **not supported**.
381
+ A step function writer may implement type conversion
382
+ inside the step function (implementation).
383
+
384
+ You may `define your own matcher`_.
385
+
386
+ .. _`define your own matcher`: api.html#step-parameters
387
+ """
388
+ global current_matcher # pylint: disable=global-statement
389
+ current_matcher = matcher_mapping[name]
390
+
391
+ def step_matcher(name):
392
+ """
393
+ DEPRECATED, use :func:`use_step_matcher()` instead.
394
+ """
395
+ # -- BACKWARD-COMPATIBLE NAME: Mark as deprecated.
396
+ import warnings
397
+ warnings.warn("Use 'use_step_matcher()' instead",
398
+ PendingDeprecationWarning, stacklevel=2)
399
+ use_step_matcher(name)
400
+
401
+ def get_matcher(func, string):
402
+ return current_matcher(func, string)
@@ -0,0 +1,1737 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint: disable=too-many-lines
3
+ """
4
+ This module provides the model element class that represent a behave model:
5
+
6
+ * :class:`Feature`
7
+ * :class:`Scenario`
8
+ * :class:`ScenarioOutline`
9
+ * :class:`Step`
10
+ * ...
11
+ """
12
+
13
+ from __future__ import absolute_import, with_statement
14
+ import copy
15
+ import difflib
16
+ import logging
17
+ import itertools
18
+ import time
19
+ import six
20
+ from six.moves import zip # pylint: disable=redefined-builtin
21
+ from behave.model_core import \
22
+ Status, BasicStatement, TagAndStatusStatement, TagStatement, Replayable
23
+ from behave.matchers import NoMatch
24
+ from behave.textutil import text as _text
25
+ if six.PY2:
26
+ # -- USE PYTHON3 BACKPORT: With unicode traceback support.
27
+ import traceback2 as traceback
28
+ else:
29
+ import traceback
30
+
31
+
32
+ class Feature(TagAndStatusStatement, Replayable):
33
+ """A `feature`_ parsed from a *feature file*.
34
+
35
+ The attributes are:
36
+
37
+ .. attribute:: keyword
38
+
39
+ This is the keyword as seen in the *feature file*. In English this will
40
+ be "Feature".
41
+
42
+ .. attribute:: name
43
+
44
+ The name of the feature (the text after "Feature".)
45
+
46
+ .. attribute:: description
47
+
48
+ The description of the feature as seen in the *feature file*. This is
49
+ stored as a list of text lines.
50
+
51
+ .. attribute:: background
52
+
53
+ The :class:`~behave.model.Background` for this feature, if any.
54
+
55
+ .. attribute:: scenarios
56
+
57
+ A list of :class:`~behave.model.Scenario` making up this feature.
58
+
59
+ .. attribute:: tags
60
+
61
+ A list of @tags (as :class:`~behave.model.Tag` which are basically
62
+ glorified strings) attached to the feature.
63
+ See :ref:`controlling things with tags`.
64
+
65
+ .. attribute:: status
66
+
67
+ Read-Only. A summary status of the feature's run. If read before the
68
+ feature is fully tested it will return "untested" otherwise it will
69
+ return one of:
70
+
71
+ Status.untested
72
+ The feature was has not been completely tested yet.
73
+ Status.skipped
74
+ One or more steps of this feature was passed over during testing.
75
+ Status.passed
76
+ The feature was tested successfully.
77
+ Status.failed
78
+ One or more steps of this feature failed.
79
+
80
+ .. versionchanged:: 1.2.6
81
+ Use Status enum class (was: string).
82
+
83
+ .. attribute:: hook_failed
84
+
85
+ Indicates if a hook failure occured while running this feature.
86
+
87
+ .. versionadded:: 1.2.6
88
+
89
+ .. attribute:: duration
90
+
91
+ The time, in seconds, that it took to test this feature. If read before
92
+ the feature is tested it will return 0.0.
93
+
94
+ .. attribute:: filename
95
+
96
+ The file name (or "<string>") of the *feature file* where the feature
97
+ was found.
98
+
99
+ .. attribute:: line
100
+
101
+ The line number of the *feature file* where the feature was found.
102
+
103
+ .. attribute:: language
104
+
105
+ Indicates which spoken language (English, French, German, ..) was used
106
+ for parsing the feature file and its keywords. The I18N language code
107
+ indicates which language is used. This corresponds to the language tag
108
+ at the beginning of the feature file.
109
+
110
+ .. versionadded:: 1.2.6
111
+
112
+ .. _`feature`: gherkin.html#features
113
+ """
114
+
115
+ type = "feature"
116
+
117
+ def __init__(self, filename, line, keyword, name, tags=None,
118
+ description=None, scenarios=None, background=None,
119
+ language=None):
120
+ tags = tags or []
121
+ super(Feature, self).__init__(filename, line, keyword, name, tags)
122
+ self.description = description or []
123
+ self.scenarios = []
124
+ self.background = background
125
+ self.language = language
126
+ self.parser = None
127
+ self.hook_failed = False
128
+ if scenarios:
129
+ for scenario in scenarios:
130
+ self.add_scenario(scenario)
131
+
132
+ def reset(self):
133
+ """Reset to clean state before a test run."""
134
+ super(Feature, self).reset()
135
+ self.hook_failed = False
136
+ for scenario in self.scenarios:
137
+ scenario.reset()
138
+
139
+ def __repr__(self):
140
+ return '<Feature "%s": %d scenario(s)>' % \
141
+ (self.name, len(self.scenarios))
142
+
143
+ def __iter__(self):
144
+ return iter(self.scenarios)
145
+
146
+ def add_scenario(self, scenario):
147
+ scenario.feature = self
148
+ scenario.background = self.background
149
+ self.scenarios.append(scenario)
150
+
151
+ def compute_status(self):
152
+ """Compute the status of this feature based on its:
153
+ * scenarios
154
+ * scenario outlines
155
+ * hook failures
156
+
157
+ :return: Computed status (as string-enum).
158
+ """
159
+ if self.hook_failed:
160
+ return Status.failed
161
+
162
+ skipped = True
163
+ passed_count = 0
164
+ for scenario in self.scenarios:
165
+ scenario_status = scenario.status
166
+ if scenario_status == Status.failed:
167
+ return Status.failed
168
+ elif scenario_status == Status.untested:
169
+ if passed_count > 0:
170
+ return Status.failed # ABORTED: Some passed, now untested.
171
+ return Status.untested
172
+ if scenario_status != Status.skipped:
173
+ skipped = False
174
+ if scenario_status == Status.passed:
175
+ passed_count += 1
176
+
177
+ if skipped:
178
+ return Status.skipped
179
+ else:
180
+ return Status.passed
181
+
182
+
183
+ @property
184
+ def duration(self):
185
+ # -- NEW: Background is executed N times, now part of scenarios.
186
+ feature_duration = 0.0
187
+ for scenario in self.scenarios:
188
+ feature_duration += scenario.duration
189
+ return feature_duration
190
+
191
+ def walk_scenarios(self, with_outlines=False):
192
+ """
193
+ Provides a flat list of all scenarios of this feature.
194
+ A ScenarioOutline element adds its scenarios to this list.
195
+ But the ScenarioOutline element itself is only added when specified.
196
+
197
+ A flat scenario list is useful when all scenarios of a features
198
+ should be processed.
199
+
200
+ :param with_outlines: If ScenarioOutline items should be added, too.
201
+ :return: List of all scenarios of this feature.
202
+ """
203
+ all_scenarios = []
204
+ for scenario in self.scenarios:
205
+ if isinstance(scenario, ScenarioOutline):
206
+ scenario_outline = scenario
207
+ if with_outlines:
208
+ all_scenarios.append(scenario_outline)
209
+ all_scenarios.extend(scenario_outline.scenarios)
210
+ else:
211
+ all_scenarios.append(scenario)
212
+ return all_scenarios
213
+
214
+ def should_run(self, config=None):
215
+ """
216
+ Determines if this Feature (and its scenarios) should run.
217
+ Implements the run decision logic for a feature.
218
+ The decision depends on:
219
+
220
+ * if the Feature is marked as skipped
221
+ * if the config.tags (tag expression) enable/disable this feature
222
+
223
+ :param config: Runner configuration to use (optional).
224
+ :return: True, if scenario should run. False, otherwise.
225
+ """
226
+ answer = not self.should_skip
227
+ if answer and config:
228
+ answer = self.should_run_with_tags(config.tags)
229
+ return answer
230
+
231
+ def should_run_with_tags(self, tag_expression):
232
+ """Determines if this feature should run when the tag expression is used.
233
+ A feature should run if:
234
+ * it should run according to its tags
235
+ * any of its scenarios should run according to its tags
236
+
237
+ :param tag_expression: Runner/config environment tags to use.
238
+ :return: True, if feature should run. False, otherwise (skip it).
239
+ """
240
+ run_feature = tag_expression.check(self.tags)
241
+ if not run_feature:
242
+ for scenario in self:
243
+ if scenario.should_run_with_tags(tag_expression):
244
+ run_feature = True
245
+ break
246
+ return run_feature
247
+
248
+ def mark_skipped(self):
249
+ """Marks this feature (and all its scenarios and steps) as skipped.
250
+ Note this function may be called before the feature is executed.
251
+ """
252
+ self.skip(require_not_executed=True)
253
+ assert self.status == Status.skipped or self.hook_failed
254
+
255
+ def skip(self, reason=None, require_not_executed=False):
256
+ """Skip executing this feature or the remaining parts of it.
257
+ Note that this feature may be already partly executed
258
+ when this function is called.
259
+
260
+ :param reason: Optional reason why feature should be skipped (as string).
261
+ :param require_not_executed: Optional, requires that feature is not
262
+ executed yet (default: false).
263
+ """
264
+ if reason:
265
+ logger = logging.getLogger("behave")
266
+ logger.warning(u"SKIP FEATURE %s: %s", self.name, reason)
267
+
268
+ self.clear_status()
269
+ self.should_skip = True
270
+ self.skip_reason = reason
271
+ for scenario in self.scenarios:
272
+ scenario.skip(reason, require_not_executed)
273
+ if not self.scenarios:
274
+ # -- SPECIAL CASE: Feature without scenarios
275
+ self.set_status(Status.skipped)
276
+ assert self.status in self.final_status #< skipped, failed or passed.
277
+
278
+ def run(self, runner):
279
+ # pylint: disable=too-many-branches
280
+ # MAYBE: self.reset()
281
+ self.clear_status()
282
+ self.hook_failed = False
283
+
284
+ runner.context._push() # pylint: disable=protected-access
285
+ runner.context.feature = self
286
+ runner.context.tags = set(self.tags)
287
+
288
+ skip_feature_untested = runner.aborted
289
+ run_feature = self.should_run(runner.config)
290
+ failed_count = 0
291
+ hooks_called = False
292
+ if not runner.config.dry_run and run_feature:
293
+ hooks_called = True
294
+ for tag in self.tags:
295
+ runner.run_hook("before_tag", runner.context, tag)
296
+ runner.run_hook("before_feature", runner.context, self)
297
+ if self.hook_failed:
298
+ failed_count += 1
299
+
300
+ # -- RE-EVALUATE SHOULD-RUN STATE:
301
+ # Hook may call feature.mark_skipped() to exclude it.
302
+ skip_feature_untested = self.hook_failed or runner.aborted
303
+ run_feature = self.should_run()
304
+
305
+ # run this feature if the tags say so or any one of its scenarios
306
+ if run_feature or runner.config.show_skipped:
307
+ for formatter in runner.formatters:
308
+ formatter.feature(self)
309
+ if self.background:
310
+ for formatter in runner.formatters:
311
+ formatter.background(self.background)
312
+
313
+ if not skip_feature_untested:
314
+ for scenario in self.scenarios:
315
+ # -- OPTIONAL: Select scenario by name (regular expressions).
316
+ if (runner.config.name and
317
+ not scenario.should_run_with_name_select(runner.config)):
318
+ scenario.mark_skipped()
319
+ continue
320
+
321
+ failed = scenario.run(runner)
322
+ if failed:
323
+ failed_count += 1
324
+ if runner.config.stop or runner.aborted:
325
+ # -- FAIL-EARLY: Stop after first failure.
326
+ break
327
+
328
+ self.clear_status() # -- ENFORCE: compute_status() after run.
329
+ if not self.scenarios and not run_feature:
330
+ # -- SPECIAL CASE: Feature without scenarios
331
+ self.set_status(Status.skipped)
332
+
333
+ if hooks_called:
334
+ runner.run_hook("after_feature", runner.context, self)
335
+ for tag in self.tags:
336
+ runner.run_hook("after_tag", runner.context, tag)
337
+ if self.hook_failed:
338
+ failed_count += 1
339
+ self.set_status(Status.failed)
340
+
341
+ runner.context._pop() # pylint: disable=protected-access
342
+
343
+ if run_feature or runner.config.show_skipped:
344
+ for formatter in runner.formatters:
345
+ formatter.eof()
346
+
347
+ failed = (failed_count > 0)
348
+ return failed
349
+
350
+
351
+ class Background(BasicStatement, Replayable):
352
+ """A `background`_ parsed from a *feature file*.
353
+
354
+ The attributes are:
355
+
356
+ .. attribute:: keyword
357
+
358
+ This is the keyword as seen in the *feature file*. In English this will
359
+ typically be "Background".
360
+
361
+ .. attribute:: name
362
+
363
+ The name of the background (the text after "Background:".)
364
+
365
+ .. attribute:: steps
366
+
367
+ A list of :class:`~behave.model.Step` making up this background.
368
+
369
+ .. attribute:: duration
370
+
371
+ The time, in seconds, that it took to run this background. If read
372
+ before the background is run it will return 0.0.
373
+
374
+ .. attribute:: filename
375
+
376
+ The file name (or "<string>") of the *feature file* where the background
377
+ was found.
378
+
379
+ .. attribute:: line
380
+
381
+ The line number of the *feature file* where the background was found.
382
+
383
+ .. _`background`: gherkin.html#backgrounds
384
+ """
385
+ type = "background"
386
+
387
+ def __init__(self, filename, line, keyword, name, steps=None):
388
+ super(Background, self).__init__(filename, line, keyword, name)
389
+ self.steps = steps or []
390
+
391
+ def __repr__(self):
392
+ return '<Background "%s">' % self.name
393
+
394
+ def __iter__(self):
395
+ return iter(self.steps)
396
+
397
+ @property
398
+ def duration(self):
399
+ duration = 0
400
+ for step in self.steps:
401
+ duration += step.duration
402
+ return duration
403
+
404
+
405
+ class Scenario(TagAndStatusStatement, Replayable):
406
+ """A `scenario`_ parsed from a *feature file*.
407
+
408
+ The attributes are:
409
+
410
+ .. attribute:: keyword
411
+
412
+ This is the keyword as seen in the *feature file*. In English this will
413
+ typically be "Scenario".
414
+
415
+ .. attribute:: name
416
+
417
+ The name of the scenario (the text after "Scenario:".)
418
+
419
+ .. attribute:: description
420
+
421
+ The description of the scenario as seen in the *feature file*.
422
+ This is stored as a list of text lines.
423
+
424
+ .. attribute:: feature
425
+
426
+ The :class:`~behave.model.Feature` this scenario belongs to.
427
+
428
+ .. attribute:: steps
429
+
430
+ A list of :class:`~behave.model.Step` making up this scenario.
431
+
432
+ .. attribute:: tags
433
+
434
+ A list of @tags (as :class:`~behave.model.Tag` which are basically
435
+ glorified strings) attached to the scenario.
436
+ See :ref:`controlling things with tags`.
437
+
438
+ .. attribute:: status
439
+
440
+ Read-Only. A summary status of the scenario's run. If read before the
441
+ scenario is fully tested it will return "untested" otherwise it will
442
+ return one of:
443
+
444
+
445
+ Status.untested
446
+ The scenario was has not been completely tested yet.
447
+ Status.skipped
448
+ One or more steps of this scenario was passed over during testing.
449
+ Status.passed
450
+ The scenario was tested successfully.
451
+ Status.failed
452
+ One or more steps of this scenario failed.
453
+
454
+ .. versionchanged:: 1.2.6
455
+ Use Status enum class (was: string)
456
+
457
+ .. attribute:: hook_failed
458
+
459
+ Indicates if a hook failure occured while running this scenario.
460
+
461
+ .. versionadded:: 1.2.6
462
+
463
+ .. attribute:: duration
464
+
465
+ The time, in seconds, that it took to test this scenario. If read before
466
+ the scenario is tested it will return 0.0.
467
+
468
+ .. attribute:: filename
469
+
470
+ The file name (or "<string>") of the *feature file* where the scenario
471
+ was found.
472
+
473
+ .. attribute:: line
474
+
475
+ The line number of the *feature file* where the scenario was found.
476
+
477
+
478
+ .. _`scenario`: gherkin.html#scenarios
479
+ """
480
+ # pylint: disable=too-many-instance-attributes
481
+ type = "scenario"
482
+ continue_after_failed_step = False
483
+
484
+ def __init__(self, filename, line, keyword, name, tags=None, steps=None,
485
+ description=None):
486
+ tags = tags or []
487
+ super(Scenario, self).__init__(filename, line, keyword, name, tags)
488
+ self.description = description or []
489
+ self.steps = steps or []
490
+ self.background = None
491
+ self.feature = None # REFER-TO: owner=Feature
492
+ self.hook_failed = False
493
+ self._background_steps = None
494
+ self._row = None
495
+ self.was_dry_run = False
496
+
497
+ def reset(self):
498
+ """Reset the internal data to reintroduce new-born state just after the
499
+ ctor was called.
500
+ """
501
+ super(Scenario, self).reset()
502
+ self.hook_failed = False
503
+ self._row = None
504
+ self.was_dry_run = False
505
+ for step in self.all_steps:
506
+ step.reset()
507
+
508
+ @property
509
+ def background_steps(self):
510
+ """Provide background steps if feature has a background.
511
+ Lazy init that copies the background steps.
512
+
513
+ Note that a copy of the background steps is needed to ensure
514
+ that the background step status is specific to the scenario.
515
+
516
+ :return: List of background steps or empty list
517
+ """
518
+ if self._background_steps is None:
519
+ # -- LAZY-INIT (need copy of background.steps):
520
+ # Each scenario needs own background.steps.
521
+ # Otherwise, background step status of the last-run scenario is used.
522
+ steps = []
523
+ if self.background:
524
+ steps = [copy.copy(step) for step in self.background.steps]
525
+ self._background_steps = steps
526
+ return self._background_steps
527
+
528
+ @property
529
+ def all_steps(self):
530
+ """Returns iterator to all steps, including background steps if any."""
531
+ if self.background is not None:
532
+ return itertools.chain(self.background_steps, self.steps)
533
+ else:
534
+ return iter(self.steps)
535
+
536
+ def __repr__(self):
537
+ return '<Scenario "%s">' % self.name
538
+
539
+ def __iter__(self):
540
+ return self.all_steps
541
+
542
+ def compute_status(self):
543
+ """Compute the status of the scenario from its steps
544
+ (and hook failures).
545
+
546
+ :return: Computed status (as enum value).
547
+ """
548
+ if self.hook_failed:
549
+ return Status.failed
550
+
551
+ for step in self.all_steps:
552
+ if step.status == Status.undefined:
553
+ if self.was_dry_run:
554
+ # -- SPECIAL CASE: In dry-run with undefined-step discovery
555
+ # Undefined steps should not cause failed scenario.
556
+ return Status.untested
557
+ else:
558
+ # -- NORMALLY: Undefined steps cause failed scenario.
559
+ return Status.failed
560
+ elif step.status != Status.passed:
561
+ # pylint: disable=line-too-long
562
+ assert step.status in (Status.failed, Status.skipped, Status.untested)
563
+ return step.status
564
+ return Status.passed
565
+
566
+ @property
567
+ def duration(self):
568
+ # -- ORIG: for step in self.steps: Background steps were excluded.
569
+ scenario_duration = 0
570
+ for step in self.all_steps:
571
+ scenario_duration += step.duration
572
+ return scenario_duration
573
+
574
+ @property
575
+ def effective_tags(self):
576
+ """
577
+ Effective tags for this scenario:
578
+ * own tags
579
+ * tags inherited from its feature
580
+ """
581
+ tags = self.tags
582
+ if self.feature:
583
+ tags = self.feature.tags + self.tags
584
+ return tags
585
+
586
+ def should_run(self, config=None):
587
+ """
588
+ Determines if this Scenario (or ScenarioOutline) should run.
589
+ Implements the run decision logic for a scenario.
590
+ The decision depends on:
591
+
592
+ * if the Scenario is marked as skipped
593
+ * if the config.tags (tag expression) enable/disable this scenario
594
+ * if the scenario is selected by name
595
+
596
+ :param config: Runner configuration to use (optional).
597
+ :return: True, if scenario should run. False, otherwise.
598
+ """
599
+ answer = not self.should_skip
600
+ if answer and config:
601
+ answer = (self.should_run_with_tags(config.tags) and
602
+ self.should_run_with_name_select(config))
603
+ return answer
604
+
605
+ def should_run_with_tags(self, tag_expression):
606
+ """
607
+ Determines if this scenario should run when the tag expression is used.
608
+
609
+ :param tag_expression: Runner/config environment tags to use.
610
+ :return: True, if scenario should run. False, otherwise (skip it).
611
+ """
612
+ return tag_expression.check(self.effective_tags)
613
+
614
+ def should_run_with_name_select(self, config):
615
+ """Determines if this scenario should run when it is selected by name.
616
+
617
+ :param config: Runner/config environment name regexp (if any).
618
+ :return: True, if scenario should run. False, otherwise (skip it).
619
+ """
620
+ # -- SELECT-ANY: If select by name is not specified (not config.name).
621
+ return not config.name or config.name_re.search(self.name)
622
+
623
+ def mark_skipped(self):
624
+ """Marks this scenario (and all its steps) as skipped.
625
+ Note that this method can be called before the scenario is executed.
626
+ """
627
+ self.skip(require_not_executed=True)
628
+ assert self.status == Status.skipped or self.hook_failed, \
629
+ "OOPS: scenario.status=%s" % self.status.name
630
+
631
+ def skip(self, reason=None, require_not_executed=False):
632
+ """Skip from executing this scenario or the remaining parts of it.
633
+ Note that the scenario may be already partly executed
634
+ when this method is called.
635
+
636
+ :param reason: Optional reason why it should be skipped (as string).
637
+ """
638
+ if reason:
639
+ scenario_type = self.__class__.__name__
640
+ logger = logging.getLogger("behave")
641
+ logger.warning(u"SKIP %s %s: %s", scenario_type, self.name, reason)
642
+
643
+ self.clear_status()
644
+ self.should_skip = True
645
+ self.skip_reason = reason
646
+ for step in self.all_steps:
647
+ not_executed = step.status in (Status.untested, Status.skipped)
648
+ if not_executed:
649
+ step.status = Status.skipped
650
+ else:
651
+ assert not require_not_executed, \
652
+ "REQUIRE NOT-EXECUTED, but step is %s" % step.status
653
+
654
+ scenario_without_steps = not self.steps and not self.background_steps
655
+ if scenario_without_steps:
656
+ self.set_status(Status.skipped)
657
+ assert self.status in self.final_status #< skipped, failed or passed
658
+
659
+ def run(self, runner):
660
+ # pylint: disable=too-many-branches, too-many-statements
661
+ self.clear_status()
662
+ self.captured.reset()
663
+ self.hook_failed = False
664
+ failed = False
665
+ skip_scenario_untested = runner.aborted
666
+ run_scenario = self.should_run(runner.config)
667
+ run_steps = run_scenario and not runner.config.dry_run
668
+ dry_run_scenario = run_scenario and runner.config.dry_run
669
+ self.was_dry_run = dry_run_scenario
670
+
671
+ runner.context._push() # pylint: disable=protected-access
672
+ runner.context.scenario = self
673
+ runner.context.tags = set(self.effective_tags)
674
+
675
+ hooks_called = False
676
+ if not runner.config.dry_run and run_scenario:
677
+ hooks_called = True
678
+ for tag in self.tags:
679
+ runner.run_hook("before_tag", runner.context, tag)
680
+ runner.run_hook("before_scenario", runner.context, self)
681
+ if self.hook_failed:
682
+ # -- SKIP: Scenario steps and behave like dry_run_scenario
683
+ failed = True
684
+
685
+ # -- RE-EVALUATE SHOULD-RUN STATE:
686
+ # Hook may call scenario.mark_skipped() to exclude it.
687
+ skip_scenario_untested = self.hook_failed or runner.aborted
688
+ run_scenario = self.should_run()
689
+ run_steps = run_scenario and not runner.config.dry_run
690
+
691
+ if run_scenario or runner.config.show_skipped:
692
+ for formatter in runner.formatters:
693
+ formatter.scenario(self)
694
+
695
+ # TODO: Reevaluate location => Move in front of hook-calls
696
+ runner.setup_capture()
697
+
698
+ if run_scenario or runner.config.show_skipped:
699
+ for step in self:
700
+ for formatter in runner.formatters:
701
+ formatter.step(step)
702
+
703
+ if not skip_scenario_untested:
704
+ for step in self.all_steps:
705
+ if run_steps:
706
+ if not step.run(runner):
707
+ # -- CASE: Failed or undefined step
708
+ # Optionally continue_after_failed_step if enabled.
709
+ # But disable run_steps after undefined-step.
710
+ run_steps = (self.continue_after_failed_step and
711
+ step.status == Status.failed)
712
+ failed = True
713
+ # pylint: disable=protected-access
714
+ runner.context._set_root_attribute("failed", True)
715
+ self.set_status(Status.failed)
716
+ elif self.should_skip:
717
+ # -- CASE: Step skipped remaining scenario.
718
+ # assert self.status == Status.skipped
719
+ run_steps = False
720
+ elif failed or dry_run_scenario:
721
+ # -- SKIP STEPS: After failure/undefined-step occurred.
722
+ # BUT: Detect all remaining undefined steps.
723
+ step.status = Status.skipped
724
+ if dry_run_scenario:
725
+ # pylint: disable=redefined-variable-type
726
+ step.status = Status.untested
727
+ found_step_match = runner.step_registry.find_match(step)
728
+ if not found_step_match:
729
+ step.status = Status.undefined
730
+ runner.undefined_steps.append(step)
731
+ elif dry_run_scenario:
732
+ # -- BETTER DIAGNOSTICS: Provide step file location
733
+ # (when --format=pretty is used).
734
+ assert step.status == Status.untested
735
+ for formatter in runner.formatters:
736
+ # -- EMULATE: Step.run() protocol w/o step execution.
737
+ formatter.match(found_step_match)
738
+ formatter.result(step)
739
+ else:
740
+ # -- SKIP STEPS: For disabled scenario.
741
+ # CASES:
742
+ # * Undefined steps are not detected (by intention).
743
+ # * Step skipped remaining scenario.
744
+ step.status = Status.skipped
745
+
746
+ self.clear_status() # -- ENFORCE: compute_status() after run.
747
+ if not run_scenario and not self.steps:
748
+ # -- SPECIAL CASE: Scenario without steps.
749
+ self.set_status(Status.skipped)
750
+
751
+
752
+ if hooks_called:
753
+ runner.run_hook("after_scenario", runner.context, self)
754
+ for tag in self.tags:
755
+ runner.run_hook("after_tag", runner.context, tag)
756
+ if self.hook_failed:
757
+ failed = True
758
+ self.set_status(Status.failed)
759
+
760
+ # -- CAPTURED-OUTPUT:
761
+ store_captured = (runner.config.junit or self.status == Status.failed)
762
+ if store_captured:
763
+ self.captured = runner.capture_controller.captured
764
+
765
+ runner.teardown_capture()
766
+ runner.context._pop() # pylint: disable=protected-access
767
+ return failed
768
+
769
+
770
+ class ScenarioOutlineBuilder(object):
771
+ """Helper class to use a ScenarioOutline as a template and
772
+ build its scenarios (as template instances).
773
+ """
774
+
775
+ def __init__(self, annotation_schema):
776
+ self.annotation_schema = annotation_schema
777
+
778
+ @staticmethod
779
+ def render_template(text, row=None, params=None):
780
+ """Render a text template with placeholders, ala "Hello <name>".
781
+
782
+ :param row: As placeholder provider (dict-like).
783
+ :param params: As additional placeholder provider (as dict).
784
+ :return: Rendered text, known placeholders are substituted w/ values.
785
+ """
786
+ if not ("<" in text and ">" in text):
787
+ return text
788
+
789
+ safe_values = False
790
+ for placeholders in (row, params):
791
+ if not placeholders:
792
+ continue
793
+ for name, value in placeholders.items():
794
+ if safe_values and ("<" in value and ">" in value):
795
+ continue # -- OOPS, value looks like placeholder.
796
+ text = text.replace("<%s>" % name, value)
797
+ return text
798
+
799
+ def make_scenario_name(self, outline_name, example, row, params=None):
800
+ """Build a scenario name for an example row of this scenario outline.
801
+ Placeholders for row data are replaced by values.
802
+
803
+ SCHEMA: "{outline_name} -*- {examples.name}@{row.id}"
804
+
805
+ :param outline_name: ScenarioOutline's name (as template).
806
+ :param example: Examples object.
807
+ :param row: Row of this example.
808
+ :param params: Additional placeholders for example/row.
809
+ :return: Computed name for the scenario representing example/row.
810
+ """
811
+ if params is None:
812
+ params = {}
813
+ params["examples.name"] = example.name or ""
814
+ params.setdefault("examples.index", example.index)
815
+ params.setdefault("row.index", row.index)
816
+ params.setdefault("row.id", row.id)
817
+
818
+ # -- STEP: Replace placeholders in scenario/example name (if any).
819
+ examples_name = self.render_template(example.name, row, params)
820
+ params["examples.name"] = examples_name
821
+ scenario_name = self.render_template(outline_name, row, params)
822
+
823
+ class Data(object):
824
+ def __init__(self, name, index):
825
+ self.name = name
826
+ self.index = index
827
+ self.id = name # pylint: disable=invalid-name
828
+
829
+ example_data = Data(examples_name, example.index)
830
+ row_data = Data(row.id, row.index)
831
+ return self.annotation_schema.format(name=scenario_name,
832
+ examples=example_data, row=row_data)
833
+
834
+ @classmethod
835
+ def make_row_tags(cls, outline_tags, row, params=None):
836
+ if not outline_tags:
837
+ return []
838
+
839
+ tags = []
840
+ for tag in outline_tags:
841
+ if "<" in tag and ">" in tag:
842
+ tag = cls.render_template(tag, row, params)
843
+ if "<" in tag or ">" in tag:
844
+ # -- OOPS: Unknown placeholder, drop tag.
845
+ continue
846
+ new_tag = Tag.make_name(tag, unescape=True)
847
+ tags.append(new_tag)
848
+ return tags
849
+
850
+ @classmethod
851
+ def make_step_for_row(cls, outline_step, row, params=None):
852
+ # -- BASED-ON: new_step = outline_step.set_values(row)
853
+ new_step = copy.deepcopy(outline_step)
854
+ new_step.name = cls.render_template(new_step.name, row, params)
855
+ if new_step.text:
856
+ new_step.text = cls.render_template(new_step.text, row)
857
+ if new_step.table:
858
+ for name, value in row.items():
859
+ for row in new_step.table:
860
+ for i, cell in enumerate(row.cells):
861
+ row.cells[i] = cell.replace("<%s>" % name, value)
862
+ return new_step
863
+
864
+ def build_scenarios(self, scenario_outline):
865
+ """Build scenarios for a ScenarioOutline from its examples."""
866
+ # -- BUILD SCENARIOS (once): For this ScenarioOutline from examples.
867
+ params = {
868
+ "examples.name": None,
869
+ "examples.index": None,
870
+ "row.index": None,
871
+ "row.id": None,
872
+ }
873
+ scenarios = []
874
+ for example_index, example in enumerate(scenario_outline.examples):
875
+ example.index = example_index+1
876
+ params["examples.name"] = example.name
877
+ params["examples.index"] = _text(example.index)
878
+ for row_index, row in enumerate(example.table):
879
+ row.index = row_index+1
880
+ row.id = "%d.%d" % (example.index, row.index)
881
+ params["row.id"] = row.id
882
+ params["row.index"] = _text(row.index)
883
+ scenario_name = self.make_scenario_name(scenario_outline.name,
884
+ example, row, params)
885
+ row_tags = self.make_row_tags(scenario_outline.tags, row, params)
886
+ row_tags.extend(example.tags)
887
+ new_steps = []
888
+ for outline_step in scenario_outline.steps:
889
+ new_step = self.make_step_for_row(outline_step, row, params)
890
+ new_steps.append(new_step)
891
+
892
+ # -- STEP: Make Scenario name for this row.
893
+ # scenario_line = example.line + 2 + row_index
894
+ scenario_line = row.line
895
+ scenario = Scenario(scenario_outline.filename, scenario_line,
896
+ scenario_outline.keyword,
897
+ scenario_name, row_tags, new_steps)
898
+ scenario.feature = scenario_outline.feature
899
+ scenario.background = scenario_outline.background
900
+ scenario._row = row # pylint: disable=protected-access
901
+ scenarios.append(scenario)
902
+ return scenarios
903
+
904
+
905
+ class ScenarioOutline(Scenario):
906
+ """A `scenario outline`_ parsed from a *feature file*.
907
+
908
+ A scenario outline extends the existing :class:`~behave.model.Scenario`
909
+ class with the addition of the :class:`~behave.model.Examples` tables of
910
+ data from the *feature file*.
911
+
912
+ The attributes are:
913
+
914
+ .. attribute:: keyword
915
+
916
+ This is the keyword as seen in the *feature file*. In English this will
917
+ typically be "Scenario Outline".
918
+
919
+ .. attribute:: name
920
+
921
+ The name of the scenario (the text after "Scenario Outline:".)
922
+
923
+ .. attribute:: description
924
+
925
+ The description of the `scenario outline`_ as seen in the *feature file*.
926
+ This is stored as a list of text lines.
927
+
928
+ .. attribute:: feature
929
+
930
+ The :class:`~behave.model.Feature` this scenario outline belongs to.
931
+
932
+ .. attribute:: steps
933
+
934
+ A list of :class:`~behave.model.Step` making up this scenario outline.
935
+
936
+ .. attribute:: examples
937
+
938
+ A list of :class:`~behave.model.Examples` used by this scenario outline.
939
+
940
+ .. attribute:: tags
941
+
942
+ A list of @tags (as :class:`~behave.model.Tag` which are basically
943
+ glorified strings) attached to the scenario.
944
+ See :ref:`controlling things with tags`.
945
+
946
+ .. attribute:: status
947
+
948
+ Read-Only. A summary status of the scenario outlines's run. If read
949
+ before the scenario is fully tested it will return "untested" otherwise
950
+ it will return one of:
951
+
952
+ Status.untested
953
+ The scenario was has not been completely tested yet.
954
+ Status.skipped
955
+ One or more scenarios of this outline was passed over during testing.
956
+ Status.passed
957
+ The scenario was tested successfully.
958
+ Status.failed
959
+ One or more scenarios of this outline failed.
960
+
961
+ .. versionchanged:: 1.2.6
962
+ Use Status enum class (was: string)
963
+
964
+ .. attribute:: duration
965
+
966
+ The time, in seconds, that it took to test the scenarios of this
967
+ outline. If read before the scenarios are tested it will return 0.0.
968
+
969
+ .. attribute:: filename
970
+
971
+ The file name (or "<string>") of the *feature file* where the scenario
972
+ was found.
973
+
974
+ .. attribute:: line
975
+
976
+ The line number of the *feature file* where the scenario was found.
977
+
978
+ .. _`scenario outline`: gherkin.html#scenario-outlines
979
+ """
980
+ type = "scenario_outline"
981
+ annotation_schema = u"{name} -- @{row.id} {examples.name}"
982
+
983
+ def __init__(self, filename, line, keyword, name, tags=None,
984
+ steps=None, examples=None, description=None):
985
+ super(ScenarioOutline, self).__init__(filename, line, keyword, name,
986
+ tags, steps, description)
987
+ self.examples = examples or []
988
+ self._scenarios = []
989
+
990
+ def reset(self):
991
+ """Reset runtime temporary data like before a test run."""
992
+ super(ScenarioOutline, self).reset()
993
+ for scenario in self._scenarios: # -- AVOID: BUILD-SCENARIOS
994
+ scenario.reset()
995
+
996
+ @property
997
+ def scenarios(self):
998
+ """Return the scenarios with the steps altered to take the values from
999
+ the examples.
1000
+ """
1001
+ if self._scenarios:
1002
+ return self._scenarios
1003
+
1004
+ # -- BUILD SCENARIOS (once): For this ScenarioOutline from examples.
1005
+ builder = ScenarioOutlineBuilder(self.annotation_schema)
1006
+ self._scenarios = builder.build_scenarios(self)
1007
+ return self._scenarios
1008
+
1009
+ def __repr__(self):
1010
+ return '<ScenarioOutline "%s">' % self.name
1011
+
1012
+ def __iter__(self):
1013
+ return iter(self.scenarios) # -- REQUIRE: BUILD-SCENARIOS
1014
+
1015
+ def compute_status(self):
1016
+ skipped_count = 0
1017
+ for scenario in self._scenarios: # -- AVOID: BUILD-SCENARIOS
1018
+ scenario_status = scenario.status
1019
+ if scenario_status in (Status.failed, Status.untested):
1020
+ return scenario_status
1021
+ elif scenario_status == Status.skipped:
1022
+ skipped_count += 1
1023
+ if skipped_count > 0 and skipped_count == len(self._scenarios):
1024
+ # -- ALL SKIPPED:
1025
+ return Status.skipped
1026
+ # -- OTHERWISE: ALL PASSED (some scenarios may have been excluded)
1027
+ return Status.passed
1028
+
1029
+ @property
1030
+ def duration(self):
1031
+ outline_duration = 0
1032
+ for scenario in self._scenarios: # -- AVOID: BUILD-SCENARIOS
1033
+ outline_duration += scenario.duration
1034
+ return outline_duration
1035
+
1036
+ def should_run_with_tags(self, tag_expression):
1037
+ """Determines if this scenario outline (or one of its scenarios)
1038
+ should run when the tag expression is used.
1039
+
1040
+ :param tag_expression: Runner/config environment tags to use.
1041
+ :return: True, if scenario should run. False, otherwise (skip it).
1042
+ """
1043
+ if tag_expression.check(self.effective_tags):
1044
+ return True
1045
+
1046
+ for scenario in self.scenarios: # -- REQUIRE: BUILD-SCENARIOS
1047
+ if scenario.should_run_with_tags(tag_expression):
1048
+ return True
1049
+ # -- NOTHING SELECTED:
1050
+ return False
1051
+
1052
+ def should_run_with_name_select(self, config):
1053
+ """Determines if this scenario should run when it is selected by name.
1054
+
1055
+ :param config: Runner/config environment name regexp (if any).
1056
+ :return: True, if scenario should run. False, otherwise (skip it).
1057
+ """
1058
+ if not config.name:
1059
+ return True # -- SELECT-ALL: Select by name is not specified.
1060
+
1061
+ for scenario in self.scenarios: # -- REQUIRE: BUILD-SCENARIOS
1062
+ if scenario.should_run_with_name_select(config):
1063
+ return True
1064
+ # -- NOTHING SELECTED:
1065
+ return False
1066
+
1067
+
1068
+ def mark_skipped(self):
1069
+ """Marks this scenario outline (and all its scenarios/steps) as skipped.
1070
+ Note that this method may be called before the scenario outline
1071
+ is executed.
1072
+ """
1073
+ self.skip(require_not_executed=True)
1074
+ assert self.status == Status.skipped
1075
+
1076
+ def skip(self, reason=None, require_not_executed=False):
1077
+ """Skip from executing this scenario outline or its remaining parts.
1078
+ Note that the scenario outline may be already partly executed
1079
+ when this method is called.
1080
+
1081
+ :param reason: Optional reason why it should be skipped (as string).
1082
+ """
1083
+ if reason:
1084
+ logger = logging.getLogger("behave")
1085
+ logger.warning(u"SKIP ScenarioOutline %s: %s", self.name, reason)
1086
+
1087
+ self.clear_status()
1088
+ self.should_skip = True
1089
+ for scenario in self.scenarios:
1090
+ scenario.skip(reason, require_not_executed)
1091
+ if not self.scenarios:
1092
+ # -- SPECIAL CASE: ScenarioOutline without scenarios/examples
1093
+ self.set_status(Status.skipped)
1094
+ assert self.status in self.final_status #< skipped, failed or passed
1095
+
1096
+ def run(self, runner):
1097
+ # pylint: disable=protected-access
1098
+ # REASON: context._set_root_attribute(), scenario._row
1099
+ self.clear_status()
1100
+ failed_count = 0
1101
+ for scenario in self.scenarios: # -- REQUIRE: BUILD-SCENARIOS
1102
+ runner.context._set_root_attribute("active_outline", scenario._row)
1103
+ failed = scenario.run(runner)
1104
+ if failed:
1105
+ failed_count += 1
1106
+ if runner.config.stop or runner.aborted:
1107
+ # -- FAIL-EARLY: Stop after first failure.
1108
+ break
1109
+ runner.context._set_root_attribute("active_outline", None)
1110
+ return failed_count > 0
1111
+
1112
+ class Examples(TagStatement, Replayable):
1113
+ """A table parsed from a `scenario outline`_ in a *feature file*.
1114
+
1115
+ The attributes are:
1116
+
1117
+ .. attribute:: keyword
1118
+
1119
+ This is the keyword as seen in the *feature file*. In English this will
1120
+ typically be "Example".
1121
+
1122
+ .. attribute:: name
1123
+
1124
+ The name of the example (the text after "Example:".)
1125
+
1126
+ .. attribute:: table
1127
+
1128
+ An instance of :class:`~behave.model.Table` that came with the example
1129
+ in the *feature file*.
1130
+
1131
+ .. attribute:: filename
1132
+
1133
+ The file name (or "<string>") of the *feature file* where the example
1134
+ was found.
1135
+
1136
+ .. attribute:: line
1137
+
1138
+ The line number of the *feature file* where the example was found.
1139
+
1140
+ .. _`examples`: gherkin.html#examples
1141
+ """
1142
+ type = "examples"
1143
+
1144
+ def __init__(self, filename, line, keyword, name, tags=None, table=None):
1145
+ super(Examples, self).__init__(filename, line, keyword, name, tags)
1146
+ self.table = table
1147
+ self.index = None
1148
+
1149
+
1150
+ class Step(BasicStatement, Replayable):
1151
+ """A single `step`_ parsed from a *feature file*.
1152
+
1153
+ The attributes are:
1154
+
1155
+ .. attribute:: keyword
1156
+
1157
+ This is the keyword as seen in the *feature file*. In English this will
1158
+ typically be "Given", "When", "Then" or a number of other words.
1159
+
1160
+ .. attribute:: name
1161
+
1162
+ The name of the step (the text after "Given" etc.)
1163
+
1164
+ .. attribute:: step_type
1165
+
1166
+ The type of step as determined by the keyword. If the keyword is "and"
1167
+ then the previous keyword in the *feature file* will determine this
1168
+ step's step_type.
1169
+
1170
+ .. attribute:: text
1171
+
1172
+ An instance of :class:`~behave.model.Text` that came with the step
1173
+ in the *feature file*.
1174
+
1175
+ .. attribute:: table
1176
+
1177
+ An instance of :class:`~behave.model.Table` that came with the step
1178
+ in the *feature file*.
1179
+
1180
+ .. attribute:: status
1181
+
1182
+ Read-Only. A summary status of the step's run. If read before the
1183
+ step is tested it will return "untested" otherwise it will
1184
+ return one of:
1185
+
1186
+ Status.untested
1187
+ This step was not run (yet).
1188
+ Status.skipped
1189
+ This step was skipped during testing.
1190
+ Status.passed
1191
+ The step was tested successfully.
1192
+ Status.failed
1193
+ The step failed.
1194
+ Status.undefined
1195
+ The step has no matching step implementation.
1196
+
1197
+ .. versionchanged::
1198
+ Use Status enum class (was: string).
1199
+
1200
+ .. attribute:: hook_failed
1201
+
1202
+ Indicates if a hook failure occured while running this step.
1203
+
1204
+ .. versionadded:: 1.2.6
1205
+
1206
+ .. attribute:: duration
1207
+
1208
+ The time, in seconds, that it took to test this step. If read before the
1209
+ step is tested it will return 0.0.
1210
+
1211
+ .. attribute:: error_message
1212
+
1213
+ If the step failed then this will hold any error information, as a
1214
+ single string. It will otherwise be None.
1215
+
1216
+ .. versionchanged:: 1.2.6 (moved to base class)
1217
+
1218
+ .. attribute:: filename
1219
+
1220
+ The file name (or "<string>") of the *feature file* where the step was
1221
+ found.
1222
+
1223
+ .. attribute:: line
1224
+
1225
+ The line number of the *feature file* where the step was found.
1226
+
1227
+ .. _`step`: gherkin.html#steps
1228
+ """
1229
+ type = "step"
1230
+
1231
+ def __init__(self, filename, line, keyword, step_type, name, text=None,
1232
+ table=None):
1233
+ super(Step, self).__init__(filename, line, keyword, name)
1234
+ self.step_type = step_type
1235
+ self.text = text
1236
+ self.table = table
1237
+
1238
+ self.status = Status.untested
1239
+ self.hook_failed = False
1240
+ self.duration = 0
1241
+
1242
+ def reset(self):
1243
+ """Reset temporary runtime data to reach clean state again."""
1244
+ super(Step, self).reset()
1245
+ self.status = Status.untested
1246
+ self.hook_failed = False
1247
+ self.duration = 0
1248
+ # -- POSTCONDITION: assert self.status == Status.untested
1249
+
1250
+ def __repr__(self):
1251
+ return '<%s "%s">' % (self.step_type, self.name)
1252
+
1253
+ def __eq__(self, other):
1254
+ return (self.step_type, self.name) == (other.step_type, other.name)
1255
+
1256
+ def __hash__(self):
1257
+ return hash(self.step_type) + hash(self.name)
1258
+
1259
+ def set_values(self, table_row):
1260
+ """Clone a new step from this one, used for ScenarioOutline.
1261
+ Replace ScenarioOutline placeholders w/ values.
1262
+
1263
+ :param table_row: Placeholder data for example row.
1264
+ :return: Cloned, adapted step object.
1265
+
1266
+ .. note:: Deprecating
1267
+ Use 'ScenarioOutlineBuilder.make_step_for_row()' instead.
1268
+ """
1269
+ import warnings
1270
+ warnings.warn("Use 'ScenarioOutline.make_step_for_row()' instead",
1271
+ PendingDeprecationWarning, stacklevel=2)
1272
+ outline_step = self
1273
+ return ScenarioOutlineBuilder.make_step_for_row(outline_step, table_row)
1274
+
1275
+ def run(self, runner, quiet=False, capture=True):
1276
+ # pylint: disable=too-many-branches, too-many-statements
1277
+ # -- RESET: Run-time information.
1278
+ # self.status = Status.untested
1279
+ # self.hook_failed = False
1280
+ self.reset()
1281
+
1282
+ match = runner.step_registry.find_match(self)
1283
+ if match is None:
1284
+ runner.undefined_steps.append(self)
1285
+ if not quiet:
1286
+ for formatter in runner.formatters:
1287
+ formatter.match(NoMatch())
1288
+
1289
+ self.status = Status.undefined
1290
+ if not quiet:
1291
+ for formatter in runner.formatters:
1292
+ formatter.result(self)
1293
+ return False
1294
+
1295
+ keep_going = True
1296
+ error = u""
1297
+
1298
+ if not quiet:
1299
+ for formatter in runner.formatters:
1300
+ formatter.match(match)
1301
+
1302
+ if capture:
1303
+ runner.start_capture()
1304
+
1305
+ skip_step_untested = False
1306
+ runner.run_hook("before_step", runner.context, self)
1307
+ if self.hook_failed:
1308
+ skip_step_untested = True
1309
+
1310
+ start = time.time()
1311
+ if not skip_step_untested:
1312
+ try:
1313
+ # -- ENSURE:
1314
+ # * runner.context.text/.table attributes are reset (#66).
1315
+ # * Even EMPTY multiline text is available in context.
1316
+ runner.context.text = self.text
1317
+ runner.context.table = self.table
1318
+ match.run(runner.context)
1319
+ if self.status == Status.untested:
1320
+ # -- NOTE: Executed step may have skipped scenario and itself.
1321
+ # pylint: disable=redefined-variable-type
1322
+ self.status = Status.passed
1323
+ except KeyboardInterrupt as e:
1324
+ runner.aborted = True
1325
+ error = u"ABORTED: By user (KeyboardInterrupt)."
1326
+ self.status = Status.failed
1327
+ self.store_exception_context(e)
1328
+ except AssertionError as e:
1329
+ self.status = Status.failed
1330
+ self.store_exception_context(e)
1331
+ if e.args:
1332
+ message = _text(e)
1333
+ error = u"Assertion Failed: "+ message
1334
+ else:
1335
+ # no assertion text; format the exception
1336
+ error = _text(traceback.format_exc())
1337
+ except Exception as e: # pylint: disable=broad-except
1338
+ self.status = Status.failed
1339
+ error = _text(traceback.format_exc())
1340
+ self.store_exception_context(e)
1341
+
1342
+ self.duration = time.time() - start
1343
+ runner.run_hook("after_step", runner.context, self)
1344
+ if self.hook_failed:
1345
+ self.status = Status.failed
1346
+
1347
+ if capture:
1348
+ runner.stop_capture()
1349
+
1350
+ # flesh out the failure with details
1351
+ store_captured_always = False # PREPARED
1352
+ store_captured = self.status == Status.failed or store_captured_always
1353
+ if self.status == Status.failed:
1354
+ assert isinstance(error, six.text_type)
1355
+ if capture:
1356
+ # -- CAPTURE-ONLY: Non-nested step failures.
1357
+ self.captured = runner.capture_controller.captured
1358
+ error2 = self.captured.make_report()
1359
+ if error2:
1360
+ error += "\n" + error2
1361
+ self.error_message = error
1362
+ keep_going = False
1363
+ elif store_captured and capture:
1364
+ self.captured = runner.capture_controller.captured
1365
+
1366
+ if not quiet:
1367
+ for formatter in runner.formatters:
1368
+ formatter.result(self)
1369
+
1370
+ return keep_going
1371
+
1372
+
1373
+ class Table(Replayable):
1374
+ """A `table`_ extracted from a *feature file*.
1375
+
1376
+ Table instance data is accessible using a number of methods:
1377
+
1378
+ **iteration**
1379
+ Iterating over the Table will yield the :class:`~behave.model.Row`
1380
+ instances from the .rows attribute.
1381
+
1382
+ **indexed access**
1383
+ Individual rows may be accessed directly by index on the Table instance;
1384
+ table[0] gives the first non-heading row and table[-1] gives the last
1385
+ row.
1386
+
1387
+ The attributes are:
1388
+
1389
+ .. attribute:: headings
1390
+
1391
+ The headings of the table as a list of strings.
1392
+
1393
+ .. attribute:: rows
1394
+
1395
+ An list of instances of :class:`~behave.model.Row` that make up the body
1396
+ of the table in the *feature file*.
1397
+
1398
+ Tables are also comparable, for what that's worth. Headings and row data
1399
+ are compared.
1400
+
1401
+ .. _`table`: gherkin.html#table
1402
+ """
1403
+ type = "table"
1404
+
1405
+ def __init__(self, headings, line=None, rows=None):
1406
+ Replayable.__init__(self)
1407
+ self.headings = headings
1408
+ self.line = line
1409
+ self.rows = []
1410
+ if rows:
1411
+ for row in rows:
1412
+ self.add_row(row, line)
1413
+
1414
+ def add_row(self, row, line=None):
1415
+ self.rows.append(Row(self.headings, row, line))
1416
+
1417
+ def add_column(self, column_name, values=None, default_value=u""):
1418
+ """Adds a new column to this table.
1419
+ Uses :param:`default_value` for new cells (if :param:`values` are
1420
+ not provided). param:`values` are extended with :param:`default_value`
1421
+ if values list is smaller than the number of table rows.
1422
+
1423
+ :param column_name: Name of new column (as string).
1424
+ :param values: Optional list of cell values in new column.
1425
+ :param default_value: Default value for cell (if values not provided).
1426
+ :returns: Index of new column (as number).
1427
+ """
1428
+ # assert isinstance(column_name, unicode)
1429
+ assert not self.has_column(column_name)
1430
+ if values is None:
1431
+ values = [default_value] * len(self.rows)
1432
+ elif not isinstance(values, list):
1433
+ values = list(values)
1434
+ if len(values) < len(self.rows):
1435
+ more_size = len(self.rows) - len(values)
1436
+ more_values = [default_value] * more_size
1437
+ values.extend(more_values)
1438
+
1439
+ new_column_index = len(self.headings)
1440
+ self.headings.append(column_name)
1441
+ for row, value in zip(self.rows, values):
1442
+ assert len(row.cells) == new_column_index
1443
+ row.cells.append(value)
1444
+ return new_column_index
1445
+
1446
+ def remove_column(self, column_name):
1447
+ if not isinstance(column_name, int):
1448
+ try:
1449
+ column_index = self.get_column_index(column_name)
1450
+ except ValueError:
1451
+ raise KeyError("column=%s is unknown" % column_name)
1452
+
1453
+ assert isinstance(column_index, int)
1454
+ assert column_index < len(self.headings)
1455
+ del self.headings[column_index]
1456
+ for row in self.rows:
1457
+ assert column_index < len(row.cells)
1458
+ del row.cells[column_index]
1459
+
1460
+ def remove_columns(self, column_names):
1461
+ for column_name in column_names:
1462
+ self.remove_column(column_name)
1463
+
1464
+ def has_column(self, column_name):
1465
+ return column_name in self.headings
1466
+
1467
+ def get_column_index(self, column_name):
1468
+ return self.headings.index(column_name)
1469
+
1470
+ def require_column(self, column_name):
1471
+ """Require that a column exists in the table.
1472
+ Raise an AssertionError if the column does not exist.
1473
+
1474
+ :param column_name: Name of new column (as string).
1475
+ :return: Index of column (as number) if it exists.
1476
+ """
1477
+ if not self.has_column(column_name):
1478
+ columns = ", ".join(self.headings)
1479
+ msg = "REQUIRE COLUMN: %s (columns: %s)" % (column_name, columns)
1480
+ raise AssertionError(msg)
1481
+ return self.get_column_index(column_name)
1482
+
1483
+ def require_columns(self, column_names):
1484
+ for column_name in column_names:
1485
+ self.require_column(column_name)
1486
+
1487
+ def ensure_column_exists(self, column_name):
1488
+ """Ensures that a column with the given name exists.
1489
+ If the column does not exist, the column is added.
1490
+
1491
+ :param column_name: Name of column (as string).
1492
+ :return: Index of column (as number).
1493
+ """
1494
+ if self.has_column(column_name):
1495
+ return self.get_column_index(column_name)
1496
+ else:
1497
+ return self.add_column(column_name)
1498
+
1499
+ def __repr__(self):
1500
+ return "<Table: %dx%d>" % (len(self.headings), len(self.rows))
1501
+
1502
+ def __eq__(self, other):
1503
+ if isinstance(other, Table):
1504
+ if self.headings != other.headings:
1505
+ return False
1506
+ for my_row, their_row in zip(self.rows, other.rows):
1507
+ if my_row != their_row:
1508
+ return False
1509
+ else:
1510
+ # -- ASSUME: table <=> raw data comparison
1511
+ other_rows = other
1512
+ for my_row, their_row in zip(self.rows, other_rows):
1513
+ if my_row != their_row:
1514
+ return False
1515
+ return True
1516
+
1517
+ def __ne__(self, other):
1518
+ return not self.__eq__(other)
1519
+
1520
+ def __iter__(self):
1521
+ return iter(self.rows)
1522
+
1523
+ def __getitem__(self, index):
1524
+ return self.rows[index]
1525
+
1526
+ def assert_equals(self, data):
1527
+ """Assert that this table's cells are the same as the supplied "data".
1528
+
1529
+ The data passed in must be a list of lists giving:
1530
+
1531
+ [
1532
+ [row 1],
1533
+ [row 2],
1534
+ [row 3],
1535
+ ]
1536
+
1537
+ If the cells do not match then a useful AssertionError will be raised.
1538
+ """
1539
+ assert self == data
1540
+ raise NotImplementedError
1541
+
1542
+
1543
+ class Row(object):
1544
+ """One row of a `table`_ parsed from a *feature file*.
1545
+
1546
+ Row data is accessible using a number of methods:
1547
+
1548
+ **iteration**
1549
+ Iterating over the Row will yield the individual cells as strings.
1550
+
1551
+ **named access**
1552
+ Individual cells may be accessed by heading name; row["name"] would give
1553
+ the cell value for the column with heading "name".
1554
+
1555
+ **indexed access**
1556
+ Individual cells may be accessed directly by index on the Row instance;
1557
+ row[0] gives the first cell and row[-1] gives the last cell.
1558
+
1559
+ The attributes are:
1560
+
1561
+ .. attribute:: cells
1562
+
1563
+ The list of strings that form the cells of this row.
1564
+
1565
+ .. attribute:: headings
1566
+
1567
+ The headings of the table as a list of strings.
1568
+
1569
+ Rows are also comparable, for what that's worth. Only the cells are
1570
+ compared.
1571
+
1572
+ .. _`table`: gherkin.html#table
1573
+ """
1574
+ def __init__(self, headings, cells, line=None, comments=None):
1575
+ self.headings = headings
1576
+ self.comments = comments
1577
+ for c in cells:
1578
+ assert isinstance(c, six.text_type)
1579
+ self.cells = cells
1580
+ self.line = line
1581
+
1582
+ def __getitem__(self, name):
1583
+ try:
1584
+ index = self.headings.index(name)
1585
+ except ValueError:
1586
+ if isinstance(name, int):
1587
+ index = name
1588
+ else:
1589
+ raise KeyError('"%s" is not a row heading' % name)
1590
+ return self.cells[index]
1591
+
1592
+ def __repr__(self):
1593
+ return "<Row %r>" % (self.cells,)
1594
+
1595
+ def __eq__(self, other):
1596
+ return self.cells == other.cells
1597
+
1598
+ def __ne__(self, other):
1599
+ return not self.__eq__(other)
1600
+
1601
+ def __len__(self):
1602
+ return len(self.cells)
1603
+
1604
+ def __iter__(self):
1605
+ return iter(self.cells)
1606
+
1607
+ def items(self):
1608
+ return zip(self.headings, self.cells)
1609
+
1610
+ def get(self, key, default=None):
1611
+ try:
1612
+ return self[key]
1613
+ except KeyError:
1614
+ return default
1615
+
1616
+ def as_dict(self):
1617
+ """Converts the row and its cell data into a dictionary.
1618
+ :return: Row data as dictionary (without comments, line info).
1619
+ """
1620
+ from behave.compat.collections import OrderedDict
1621
+ return OrderedDict(self.items())
1622
+
1623
+
1624
+ class Tag(six.text_type):
1625
+ """Tags appear may be associated with Features or Scenarios.
1626
+
1627
+ They're a subclass of regular strings (unicode pre-Python 3) with an
1628
+ additional ``line`` number attribute (where the tag was seen in the source
1629
+ feature file.
1630
+
1631
+ See :ref:`controlling things with tags`.
1632
+ """
1633
+ allowed_chars = u"._-=:" # In addition to aplha-numerical chars.
1634
+ quoting_chars = ("'", '"', "<", ">")
1635
+
1636
+ def __new__(cls, name, line):
1637
+ o = six.text_type.__new__(cls, name)
1638
+ o.line = line
1639
+ return o
1640
+
1641
+ @classmethod
1642
+ def make_name(cls, text, unescape=False, allowed_chars=None):
1643
+ """Translate text into a "valid tag" without whitespace, etc.
1644
+ Translation rules are:
1645
+ * alnum chars => same, kept
1646
+ * space chars => "_"
1647
+ * other chars => deleted
1648
+
1649
+ Preserve following characters (in addition to alnums, like: A-z, 0-9):
1650
+ * dots => "." (support: dotted-names, active-tag name schema)
1651
+ * minus => "-" (support: dashed-names)
1652
+ * underscore => "_"
1653
+ * equal => "=" (support: active-tag name schema)
1654
+ * colon => ":" (support: active-tag name schema or similar)
1655
+
1656
+ :param text: Unicode text as input for name.
1657
+ :param unescape: Optional flag to unescape some chars (default: false)
1658
+ :param allowed_chars: Optional string with additional preserved chars.
1659
+ :return: Unicode name that can be used as tag.
1660
+ """
1661
+ assert isinstance(text, six.text_type)
1662
+ if allowed_chars is None:
1663
+ allowed_chars = cls.allowed_chars
1664
+
1665
+ if unescape:
1666
+ # -- UNESCAPE: Some escaped sequences
1667
+ text = text.replace("\\t", "\t").replace("\\n", "\n")
1668
+ chars = []
1669
+ for char in text:
1670
+ if char.isalnum() or (allowed_chars and char in allowed_chars):
1671
+ chars.append(char)
1672
+ elif char.isspace():
1673
+ chars.append(u"_")
1674
+ elif char in cls.quoting_chars:
1675
+ pass # -- NORMALIZE: Remove any quoting chars.
1676
+ # -- MAYBE:
1677
+ # else:
1678
+ # # -- OTHERWISE: Accept gracefully any other character.
1679
+ # chars.append(char)
1680
+ return u"".join(chars)
1681
+
1682
+
1683
+ class Text(six.text_type):
1684
+ """Store multiline text from a Step definition.
1685
+
1686
+ The attributes are:
1687
+
1688
+ .. attribute:: value
1689
+
1690
+ The actual text parsed from the *feature file*.
1691
+
1692
+ .. attribute:: content_type
1693
+
1694
+ Currently only "text/plain".
1695
+ """
1696
+ def __new__(cls, value, content_type=u"text/plain", line=0):
1697
+ assert isinstance(value, six.text_type)
1698
+ assert isinstance(content_type, six.text_type)
1699
+ o = six.text_type.__new__(cls, value)
1700
+ o.content_type = content_type
1701
+ o.line = line
1702
+ return o
1703
+
1704
+ def line_range(self):
1705
+ line_count = len(self.splitlines())
1706
+ return (self.line, self.line + line_count + 1)
1707
+
1708
+ def replace(self, old, new, count=-1):
1709
+ return Text(super(Text, self).replace(old, new, count), self.content_type,
1710
+ self.line)
1711
+
1712
+ def assert_equals(self, expected):
1713
+ """Assert that my text is identical to the "expected" text.
1714
+
1715
+ A nice context diff will be displayed if they do not match.
1716
+ """
1717
+ if self == expected:
1718
+ return True
1719
+ diff = []
1720
+ for line in difflib.unified_diff(self.splitlines(),
1721
+ expected.splitlines()):
1722
+ diff.append(line)
1723
+ # strip unnecessary diff prefix
1724
+ diff = ["Text does not match:"] + diff[3:]
1725
+ raise AssertionError("\n".join(diff))
1726
+
1727
+
1728
+ # -----------------------------------------------------------------------------
1729
+ # UTILITY FUNCTIONS:
1730
+ # -----------------------------------------------------------------------------
1731
+ def reset_model(model_elements):
1732
+ """Reset the test run information stored in model elements.
1733
+
1734
+ :param model_elements: List of model elements (Feature, Scenario, ...)
1735
+ """
1736
+ for model_element in model_elements:
1737
+ model_element.reset()