busser-behave 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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()