1# -*- coding -*- 2""" 3Provides step definitions to: 4 5 * run commands, like behave 6 * create textual files within a working directory 7 8TODO: 9 matcher that ignores empty lines and whitespace and has contains comparison 10""" 11 12from __future__ import absolute_import, print_function 13from behave import given, when, then, step, matchers 14from behave4cmd0 import command_shell, command_util, pathutil, textutil 15from behave4cmd0.pathutil import posixpath_normpath 16from behave4cmd0.command_shell_proc import \ 17 TextProcessor, BehaveWinCommandOutputProcessor 18import contextlib 19import difflib 20import os 21import shutil 22from hamcrest import assert_that, equal_to, is_not, contains_string 23 24# ----------------------------------------------------------------------------- 25# INIT: 26# ----------------------------------------------------------------------------- 27matchers.register_type(int=int) 28DEBUG = False 29file_contents_normalizer = None 30if BehaveWinCommandOutputProcessor.enabled: 31 file_contents_normalizer = TextProcessor(BehaveWinCommandOutputProcessor()) 32 33 34# ----------------------------------------------------------------------------- 35# UTILITIES: 36# ----------------------------------------------------------------------------- 37@contextlib.contextmanager 38def on_assert_failed_print_details(actual, expected): 39 """ 40 Print text details in case of assertation failed errors. 41 42 .. sourcecode:: python 43 44 with on_assert_failed_print_details(actual_text, expected_text): 45 assert actual == expected 46 """ 47 try: 48 yield 49 except AssertionError: 50 # diff = difflib.unified_diff(expected.splitlines(), actual.splitlines(), 51 # "expected", "actual") 52 diff = difflib.ndiff(expected.splitlines(), actual.splitlines()) 53 diff_text = u"\n".join(diff) 54 print(u"DIFF (+ ACTUAL, - EXPECTED):\n{0}\n".format(diff_text)) 55 if DEBUG: 56 print(u"expected:\n{0}\n".format(expected)) 57 print(u"actual:\n{0}\n".format(actual)) 58 raise 59 60@contextlib.contextmanager 61def on_error_print_details(actual, expected): 62 """ 63 Print text details in case of assertation failed errors. 64 65 .. sourcecode:: python 66 67 with on_error_print_details(actual_text, expected_text): 68 ... # Do something 69 """ 70 try: 71 yield 72 except Exception: 73 diff = difflib.ndiff(expected.splitlines(), actual.splitlines()) 74 diff_text = u"\n".join(diff) 75 print(u"DIFF (+ ACTUAL, - EXPECTED):\n{0}\n".format(diff_text)) 76 if DEBUG: 77 print(u"expected:\n{0}\n".format(expected)) 78 print(u"actual:\n{0}".format(actual)) 79 raise 80 81# ----------------------------------------------------------------------------- 82# STEPS: WORKING DIR 83# ----------------------------------------------------------------------------- 84@given(u'a new working directory') 85def step_a_new_working_directory(context): 86 """Creates a new, empty working directory.""" 87 command_util.ensure_context_attribute_exists(context, "workdir", None) 88 # MAYBE: command_util.ensure_workdir_not_exists(context) 89 command_util.ensure_workdir_exists(context) 90 # OOPS: 91 shutil.rmtree(context.workdir, ignore_errors=True) 92 command_util.ensure_workdir_exists(context) 93 94@given(u'I use the current directory as working directory') 95def step_use_curdir_as_working_directory(context): 96 """ 97 Uses the current directory as working directory 98 """ 99 context.workdir = os.path.abspath(".") 100 command_util.ensure_workdir_exists(context) 101 102# ----------------------------------------------------------------------------- 103# STEPS: Create files with contents 104# ----------------------------------------------------------------------------- 105@given(u'a file named "{filename}" and encoding="{encoding}" with') 106def step_a_file_named_filename_and_encoding_with(context, filename, encoding): 107 """Creates a textual file with the content provided as docstring.""" 108 __encoding_is_valid = True 109 assert context.text is not None, "ENSURE: multiline text is provided." 110 assert not os.path.isabs(filename) 111 assert __encoding_is_valid 112 command_util.ensure_workdir_exists(context) 113 filename2 = os.path.join(context.workdir, filename) 114 pathutil.create_textfile_with_contents(filename2, context.text, encoding) 115 116 117@given(u'a file named "{filename}" with') 118def step_a_file_named_filename_with(context, filename): 119 """Creates a textual file with the content provided as docstring.""" 120 step_a_file_named_filename_and_encoding_with(context, filename, "UTF-8") 121 122 # -- SPECIAL CASE: For usage with behave steps. 123 if filename.endswith(".feature"): 124 command_util.ensure_context_attribute_exists(context, "features", []) 125 context.features.append(filename) 126 127 128@given(u'an empty file named "{filename}"') 129def step_an_empty_file_named_filename(context, filename): 130 """ 131 Creates an empty file. 132 """ 133 assert not os.path.isabs(filename) 134 command_util.ensure_workdir_exists(context) 135 filename2 = os.path.join(context.workdir, filename) 136 pathutil.create_textfile_with_contents(filename2, "") 137 138 139# ----------------------------------------------------------------------------- 140# STEPS: Run commands 141# ----------------------------------------------------------------------------- 142@when(u'I run "{command}"') 143@when(u'I run `{command}`') 144def step_i_run_command(context, command): 145 """ 146 Run a command as subprocess, collect its output and returncode. 147 """ 148 command_util.ensure_workdir_exists(context) 149 context.command_result = command_shell.run(command, cwd=context.workdir) 150 command_util.workdir_save_coverage_files(context.workdir) 151 if False and DEBUG: 152 print(u"run_command: {0}".format(command)) 153 print(u"run_command.output {0}".format(context.command_result.output)) 154 155@when(u'I successfully run "{command}"') 156@when(u'I successfully run `{command}`') 157def step_i_successfully_run_command(context, command): 158 step_i_run_command(context, command) 159 step_it_should_pass(context) 160 161@then(u'it should fail with result "{result:int}"') 162def step_it_should_fail_with_result(context, result): 163 assert_that(context.command_result.returncode, equal_to(result)) 164 assert_that(result, is_not(equal_to(0))) 165 166@then(u'the command should fail with returncode="{result:int}"') 167def step_it_should_fail_with_returncode(context, result): 168 assert_that(context.command_result.returncode, equal_to(result)) 169 assert_that(result, is_not(equal_to(0))) 170 171@then(u'the command returncode is "{result:int}"') 172def step_the_command_returncode_is(context, result): 173 assert_that(context.command_result.returncode, equal_to(result)) 174 175@then(u'the command returncode is non-zero') 176def step_the_command_returncode_is_nonzero(context): 177 assert_that(context.command_result.returncode, is_not(equal_to(0))) 178 179@then(u'it should pass') 180def step_it_should_pass(context): 181 assert_that(context.command_result.returncode, equal_to(0), 182 context.command_result.output) 183 184@then(u'it should fail') 185def step_it_should_fail(context): 186 assert_that(context.command_result.returncode, is_not(equal_to(0)), 187 context.command_result.output) 188 189@then(u'it should pass with') 190def step_it_should_pass_with(context): 191 ''' 192 EXAMPLE: 193 ... 194 when I run "behave ..." 195 then it should pass with: 196 """ 197 TEXT 198 """ 199 ''' 200 assert context.text is not None, "ENSURE: multiline text is provided." 201 step_command_output_should_contain(context) 202 assert_that(context.command_result.returncode, equal_to(0), 203 context.command_result.output) 204 205 206@then(u'it should fail with') 207def step_it_should_fail_with(context): 208 ''' 209 EXAMPLE: 210 ... 211 when I run "behave ..." 212 then it should fail with: 213 """ 214 TEXT 215 """ 216 ''' 217 assert context.text is not None, "ENSURE: multiline text is provided." 218 step_command_output_should_contain(context) 219 assert_that(context.command_result.returncode, is_not(equal_to(0))) 220 221 222# ----------------------------------------------------------------------------- 223# STEPS FOR: Output Comparison 224# ----------------------------------------------------------------------------- 225@then(u'the command output should contain "{text}"') 226def step_command_output_should_contain_text(context, text): 227 ''' 228 EXAMPLE: 229 ... 230 Then the command output should contain "TEXT" 231 ''' 232 expected_text = text 233 if "{__WORKDIR__}" in expected_text or "{__CWD__}" in expected_text: 234 expected_text = textutil.template_substitute(text, 235 __WORKDIR__ = posixpath_normpath(context.workdir), 236 __CWD__ = posixpath_normpath(os.getcwd()) 237 ) 238 actual_output = context.command_result.output 239 with on_assert_failed_print_details(actual_output, expected_text): 240 textutil.assert_normtext_should_contain(actual_output, expected_text) 241 242 243@then(u'the command output should not contain "{text}"') 244def step_command_output_should_not_contain_text(context, text): 245 ''' 246 EXAMPLE: 247 ... 248 then the command output should not contain "TEXT" 249 ''' 250 expected_text = text 251 if "{__WORKDIR__}" in text or "{__CWD__}" in text: 252 expected_text = textutil.template_substitute(text, 253 __WORKDIR__ = posixpath_normpath(context.workdir), 254 __CWD__ = posixpath_normpath(os.getcwd()) 255 ) 256 actual_output = context.command_result.output 257 with on_assert_failed_print_details(actual_output, expected_text): 258 textutil.assert_normtext_should_not_contain(actual_output, expected_text) 259 260 261@then(u'the command output should contain "{text}" {count:d} times') 262def step_command_output_should_contain_text_multiple_times(context, text, count): 263 ''' 264 EXAMPLE: 265 ... 266 Then the command output should contain "TEXT" 3 times 267 ''' 268 assert count >= 0 269 expected_text = text 270 if "{__WORKDIR__}" in expected_text or "{__CWD__}" in expected_text: 271 expected_text = textutil.template_substitute(text, 272 __WORKDIR__ = posixpath_normpath(context.workdir), 273 __CWD__ = posixpath_normpath(os.getcwd()) 274 ) 275 actual_output = context.command_result.output 276 with on_assert_failed_print_details(actual_output, expected_text): 277 textutil.assert_normtext_should_contain_multiple_times(actual_output, 278 expected_text, 279 count) 280 281@then(u'the command output should contain exactly "{text}"') 282def step_command_output_should_contain_exactly_text(context, text): 283 """ 284 Verifies that the command output of the last command contains the 285 expected text. 286 287 .. code-block:: gherkin 288 289 When I run "echo Hello" 290 Then the command output should contain "Hello" 291 """ 292 expected_text = text 293 if "{__WORKDIR__}" in text or "{__CWD__}" in text: 294 expected_text = textutil.template_substitute(text, 295 __WORKDIR__ = posixpath_normpath(context.workdir), 296 __CWD__ = posixpath_normpath(os.getcwd()) 297 ) 298 actual_output = context.command_result.output 299 textutil.assert_text_should_contain_exactly(actual_output, expected_text) 300 301 302@then(u'the command output should not contain exactly "{text}"') 303def step_command_output_should_not_contain_exactly_text(context, text): 304 expected_text = text 305 if "{__WORKDIR__}" in text or "{__CWD__}" in text: 306 expected_text = textutil.template_substitute(text, 307 __WORKDIR__ = posixpath_normpath(context.workdir), 308 __CWD__ = posixpath_normpath(os.getcwd()) 309 ) 310 actual_output = context.command_result.output 311 textutil.assert_text_should_not_contain_exactly(actual_output, expected_text) 312 313 314@then(u'the command output should contain') 315def step_command_output_should_contain(context): 316 ''' 317 EXAMPLE: 318 ... 319 when I run "behave ..." 320 then it should pass 321 and the command output should contain: 322 """ 323 TEXT 324 """ 325 ''' 326 assert context.text is not None, "REQUIRE: multi-line text" 327 step_command_output_should_contain_text(context, context.text) 328 329 330@then(u'the command output should not contain') 331def step_command_output_should_not_contain(context): 332 ''' 333 EXAMPLE: 334 ... 335 when I run "behave ..." 336 then it should pass 337 and the command output should not contain: 338 """ 339 TEXT 340 """ 341 ''' 342 assert context.text is not None, "REQUIRE: multi-line text" 343 step_command_output_should_not_contain_text(context, context.text.strip()) 344 345@then(u'the command output should contain {count:d} times') 346def step_command_output_should_contain_multiple_times(context, count): 347 ''' 348 EXAMPLE: 349 ... 350 when I run "behave ..." 351 then it should pass 352 and the command output should contain 2 times: 353 """ 354 TEXT 355 """ 356 ''' 357 assert context.text is not None, "REQUIRE: multi-line text" 358 step_command_output_should_contain_text_multiple_times(context, 359 context.text, count) 360 361@then(u'the command output should contain exactly') 362def step_command_output_should_contain_exactly_with_multiline_text(context): 363 assert context.text is not None, "REQUIRE: multi-line text" 364 step_command_output_should_contain_exactly_text(context, context.text) 365 366 367@then(u'the command output should not contain exactly') 368def step_command_output_should_contain_not_exactly_with_multiline_text(context): 369 assert context.text is not None, "REQUIRE: multi-line text" 370 step_command_output_should_not_contain_exactly_text(context, context.text) 371 372 373# ----------------------------------------------------------------------------- 374# STEPS FOR: Directories 375# ----------------------------------------------------------------------------- 376@step(u'I remove the directory "{directory}"') 377def step_remove_directory(context, directory): 378 path_ = directory 379 if not os.path.isabs(directory): 380 path_ = os.path.join(context.workdir, os.path.normpath(directory)) 381 if os.path.isdir(path_): 382 shutil.rmtree(path_, ignore_errors=True) 383 assert_that(not os.path.isdir(path_)) 384 385@given(u'I ensure that the directory "{directory}" does not exist') 386def step_given_the_directory_should_not_exist(context, directory): 387 step_remove_directory(context, directory) 388 389@given(u'a directory named "{path}"') 390def step_directory_named_dirname(context, path): 391 assert context.workdir, "REQUIRE: context.workdir" 392 path_ = os.path.join(context.workdir, os.path.normpath(path)) 393 if not os.path.exists(path_): 394 os.makedirs(path_) 395 assert os.path.isdir(path_) 396 397@then(u'the directory "{directory}" should exist') 398def step_the_directory_should_exist(context, directory): 399 path_ = directory 400 if not os.path.isabs(directory): 401 path_ = os.path.join(context.workdir, os.path.normpath(directory)) 402 assert_that(os.path.isdir(path_)) 403 404@then(u'the directory "{directory}" should not exist') 405def step_the_directory_should_not_exist(context, directory): 406 path_ = directory 407 if not os.path.isabs(directory): 408 path_ = os.path.join(context.workdir, os.path.normpath(directory)) 409 assert_that(not os.path.isdir(path_)) 410 411@step(u'the directory "{directory}" exists') 412def step_directory_exists(context, directory): 413 """ 414 Verifies that a directory exists. 415 416 .. code-block:: gherkin 417 418 Given the directory "abc.txt" exists 419 When the directory "abc.txt" exists 420 """ 421 step_the_directory_should_exist(context, directory) 422 423@step(u'the directory "{directory}" does not exist') 424def step_directory_named_does_not_exist(context, directory): 425 """ 426 Verifies that a directory does not exist. 427 428 .. code-block:: gherkin 429 430 Given the directory "abc/" does not exist 431 When the directory "abc/" does not exist 432 """ 433 step_the_directory_should_not_exist(context, directory) 434 435# ----------------------------------------------------------------------------- 436# FILE STEPS: 437# ----------------------------------------------------------------------------- 438@step(u'a file named "{filename}" exists') 439def step_file_named_filename_exists(context, filename): 440 """ 441 Verifies that a file with this filename exists. 442 443 .. code-block:: gherkin 444 445 Given a file named "abc.txt" exists 446 When a file named "abc.txt" exists 447 """ 448 step_file_named_filename_should_exist(context, filename) 449 450@step(u'a file named "{filename}" does not exist') 451def step_file_named_filename_does_not_exist(context, filename): 452 """ 453 Verifies that a file with this filename does not exist. 454 455 .. code-block:: gherkin 456 457 Given a file named "abc.txt" does not exist 458 When a file named "abc.txt" does not exist 459 """ 460 step_file_named_filename_should_not_exist(context, filename) 461 462@then(u'a file named "{filename}" should exist') 463def step_file_named_filename_should_exist(context, filename): 464 command_util.ensure_workdir_exists(context) 465 filename_ = pathutil.realpath_with_context(filename, context) 466 assert_that(os.path.exists(filename_) and os.path.isfile(filename_)) 467 468@then(u'a file named "{filename}" should not exist') 469def step_file_named_filename_should_not_exist(context, filename): 470 command_util.ensure_workdir_exists(context) 471 filename_ = pathutil.realpath_with_context(filename, context) 472 assert_that(not os.path.exists(filename_)) 473 474# ----------------------------------------------------------------------------- 475# STEPS FOR FILE CONTENTS: 476# ----------------------------------------------------------------------------- 477@then(u'the file "{filename}" should contain "{text}"') 478def step_file_should_contain_text(context, filename, text): 479 expected_text = text 480 if "{__WORKDIR__}" in text or "{__CWD__}" in text: 481 expected_text = textutil.template_substitute(text, 482 __WORKDIR__ = posixpath_normpath(context.workdir), 483 __CWD__ = posixpath_normpath(os.getcwd()) 484 ) 485 file_contents = pathutil.read_file_contents(filename, context=context) 486 file_contents = file_contents.rstrip() 487 if file_contents_normalizer: 488 # -- HACK: Inject TextProcessor as text normalizer 489 file_contents = file_contents_normalizer(file_contents) 490 with on_assert_failed_print_details(file_contents, expected_text): 491 textutil.assert_normtext_should_contain(file_contents, expected_text) 492 493 494@then(u'the file "{filename}" should not contain "{text}"') 495def step_file_should_not_contain_text(context, filename, text): 496 file_contents = pathutil.read_file_contents(filename, context=context) 497 file_contents = file_contents.rstrip() 498 textutil.assert_normtext_should_not_contain(file_contents, text) 499 # XXX assert_that(file_contents, is_not(contains_string(text))) 500 501 502@then(u'the file "{filename}" should contain') 503def step_file_should_contain_multiline_text(context, filename): 504 assert context.text is not None, "REQUIRE: multiline text" 505 step_file_should_contain_text(context, filename, context.text) 506 507 508@then(u'the file "{filename}" should not contain') 509def step_file_should_not_contain_multiline_text(context, filename): 510 assert context.text is not None, "REQUIRE: multiline text" 511 step_file_should_not_contain_text(context, filename, context.text) 512 513 514# ----------------------------------------------------------------------------- 515# ENVIRONMENT VARIABLES 516# ----------------------------------------------------------------------------- 517@step(u'I set the environment variable "{env_name}" to "{env_value}"') 518def step_I_set_the_environment_variable_to(context, env_name, env_value): 519 if not hasattr(context, "environ"): 520 context.environ = {} 521 context.environ[env_name] = env_value 522 os.environ[env_name] = env_value 523 524@step(u'I remove the environment variable "{env_name}"') 525def step_I_remove_the_environment_variable(context, env_name): 526 if not hasattr(context, "environ"): 527 context.environ = {} 528 context.environ[env_name] = "" 529 os.environ[env_name] = "" 530 del context.environ[env_name] 531 del os.environ[env_name] 532 533