Skip to content

Commit

Permalink
Fix compatibility of step-functions matching on multiple lines
Browse files Browse the repository at this point in the history
In pytest-bdd v8.0.0 it is no longer possible to match based on multiple
lines, which breaks essentially all steps that support docstrings. Solve
this by adding a wrapper-function for each of these instances, that
matches the docstring step, and calls the original function.

So, what used to be:

    @then(parse("the output should match\n{regex}"))
    @then(parse('the output should match "{regex}"'))
    def output_should_match(regex, cli_run):
        ...

Is now:

    @then(parse("the output should match"))
    def output_should_match_docstring(cli_run, docstring):
        output_should_match(docstring, cli_run)

    @then(parse('the output should match "{regex}"'))
    def output_should_match(regex, cli_run):
        ...

There is possibly a way around this that is much better than what I've
done here, but this is a start at least.
  • Loading branch information
carlsmedstad committed Nov 16, 2024
1 parent a3e55c5 commit 4d99448
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 34 deletions.
7 changes: 6 additions & 1 deletion tests/lib/given_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from pytest_bdd import given
from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re

from jrnl import __version__
from jrnl.time import __get_pdt_calendar
Expand All @@ -21,7 +22,11 @@
from tests.lib.fixtures import TestKeyring


@given(parse("we {editor_method} to the editor if opened\n{editor_input}"))
@given(re(r"we (?P<editor_method>\w+) to the editor if opened"))
def we_enter_editor_docstring(editor_method, editor_state, docstring):
we_enter_editor(editor_method, docstring, editor_state)


@given(parse("we {editor_method} nothing to the editor if opened"))
def we_enter_editor(editor_method, editor_input, editor_state):
file_method = editor_state["intent"]["method"]
Expand Down
94 changes: 62 additions & 32 deletions tests/lib/then_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from pytest_bdd import then
from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re as pytest_bdd_parsers_re
from ruamel.yaml import YAML

from jrnl.config import scope_config
Expand All @@ -30,22 +31,30 @@ def should_get_an_error(cli_run):
assert cli_run["status"] != 0, cli_run["status"]


@then(parse("the output should match\n{regex}"))
@then(parse("the output should match"))
def output_should_match_docstring(cli_run, docstring):
output_should_match(docstring, cli_run)


@then(parse('the output should match "{regex}"'))
def output_should_match(regex, cli_run):
out = cli_run["stdout"]
matches = re.findall(regex, out)
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}"


@then(parse("the output {it_should:Should} contain\n{expected}", SHOULD_DICT))
@then(parse('the output {it_should:Should} contain "{expected}"', SHOULD_DICT))
@then(parse("the output {it_should:Should} contain", SHOULD_DICT))
@then(
parse(
"the {which_output_stream} output {it_should:Should} contain\n{expected}",
"the {which_output_stream} output {it_should:Should} contain",
SHOULD_DICT,
)
)
def output_should_contain_docstring(which_output_stream, cli_run, it_should, docstring):
output_should_contain(docstring, which_output_stream, cli_run, it_should)


@then(parse('the output {it_should:Should} contain "{expected}"', SHOULD_DICT))
@then(
parse(
'the {which_output_stream} output {it_should:Should} contain "{expected}"',
Expand Down Expand Up @@ -75,13 +84,21 @@ def output_should_contain(expected, which_output_stream, cli_run, it_should):
assert (expected in cli_run[which_output_stream]) == it_should, output_str


@then(parse("the output should not contain\n{expected_output}"))
@then(parse("the output should not contain"))
def output_should_not_contain_docstring(cli_run, docstring):
output_should_not_contain(docstring, cli_run)


@then(parse('the output should not contain "{expected_output}"'))
def output_should_not_contain(expected_output, cli_run):
assert expected_output not in cli_run["stdout"]


@then(parse("the output should be\n{expected_output}"))
@then(parse("the output should be"))
def output_should_be_docstring(cli_run, docstring):
output_should_be(docstring, cli_run)


@then(parse('the output should be "{expected_output}"'))
def output_should_be(expected_output, cli_run):
actual = cli_run["stdout"].strip()
Expand Down Expand Up @@ -139,20 +156,23 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir

@then(
parse(
'the config for journal "{journal_name}" '
'{it_should:Should} contain "{some_yaml}"',
'the config for journal "{journal_name}" {it_should:Should} contain',
SHOULD_DICT,
)
)
@then(parse("the config {it_should:Should} contain", SHOULD_DICT))
def config_var_on_disk_docstring(config_on_disk, journal_name, it_should, docstring):
config_var_on_disk(config_on_disk, journal_name, it_should, docstring)


@then(
parse(
'the config for journal "{journal_name}" '
"{it_should:Should} contain\n{some_yaml}",
'{it_should:Should} contain "{some_yaml}"',
SHOULD_DICT,
)
)
@then(parse('the config {it_should:Should} contain "{some_yaml}"', SHOULD_DICT))
@then(parse("the config {it_should:Should} contain\n{some_yaml}", SHOULD_DICT))
def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
actual = config_on_disk
if journal_name:
Expand All @@ -171,23 +191,27 @@ def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
@then(
parse(
'the config in memory for journal "{journal_name}" '
'{it_should:Should} contain "{some_yaml}"',
"{it_should:Should} contain",
SHOULD_DICT,
)
)
@then(parse("the config in memory {it_should:Should} contain", SHOULD_DICT))
def config_var_in_memory_docstring(
config_in_memory, journal_name, it_should, docstring
):
config_var_in_memory(config_in_memory, journal_name, it_should, docstring)


@then(
parse(
'the config in memory for journal "{journal_name}" '
"{it_should:Should} contain\n{some_yaml}",
'{it_should:Should} contain "{some_yaml}"',
SHOULD_DICT,
)
)
@then(
parse('the config in memory {it_should:Should} contain "{some_yaml}"', SHOULD_DICT)
)
@then(
parse("the config in memory {it_should:Should} contain\n{some_yaml}", SHOULD_DICT)
)
def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
actual = config_in_memory["overrides"]
if journal_name:
Expand All @@ -213,21 +237,23 @@ def password_was_not_called(cli_run):
assert not cli_run["mocks"]["user_input"].return_value.input.called


@then(parse("the cache directory should contain the files\n{file_list}"))
def assert_dir_contains_files(file_list, cache_dir):
assert does_directory_contain_files(file_list, cache_dir["path"])
@then(parse("the cache directory should contain the files"))
def assert_dir_contains_files(cache_dir, docstring):
assert does_directory_contain_files(docstring, cache_dir["path"])


@then(parse("the cache directory should contain {number} files"))
@then(
pytest_bdd_parsers_re(r"the cache directory should contain (?P<number>\d+) files")
)
def assert_dir_contains_n_files(cache_dir, number):
assert does_directory_contain_n_files(cache_dir["path"], number)


@then(parse("the journal directory should contain\n{file_list}"))
def journal_directory_should_contain(config_on_disk, file_list):
@then(parse("the journal directory should contain"))
def journal_directory_should_contain(config_on_disk, docstring):
scoped_config = scope_config(config_on_disk, "default")

assert does_directory_contain_files(file_list, scoped_config["journal"])
assert does_directory_contain_files(docstring, scoped_config["journal"])


@then(parse('journal "{journal_name}" should not exist'))
Expand Down Expand Up @@ -262,10 +288,10 @@ def directory_should_not_exist(config_on_disk, it_should, journal_name):
assert dir_exists == it_should


@then(parse('the content of file "{file_path}" in the cache should be\n{file_content}'))
def content_of_file_should_be(file_path, file_content, cache_dir):
@then(parse('the content of file "{file_path}" in the cache should be'))
def content_of_file_should_be(file_path, cache_dir, docstring):
assert cache_dir["exists"]
expected_content = file_content.strip().splitlines()
expected_content = docstring.strip().splitlines()

with open(os.path.join(cache_dir["path"], file_path), "r") as f:
actual_content = f.read().strip().splitlines()
Expand All @@ -282,12 +308,12 @@ def content_of_file_should_be(file_path, file_content, cache_dir):
]


@then(parse("the cache should contain the files\n{file_list}"))
def cache_dir_contains_files(file_list, cache_dir):
@then(parse("the cache should contain the files"))
def cache_dir_contains_files(cache_dir, docstring):
assert cache_dir["exists"]

actual_files = os.listdir(cache_dir["path"])
expected_files = file_list.split("\n")
expected_files = docstring.split("\n")

# sort to deal with inconsistent default file ordering on different OS's
actual_files.sort()
Expand Down Expand Up @@ -336,11 +362,11 @@ def assert_parsed_output_item_count(node_name, number, parsed_output):
assert False, f"Language name {lang} not recognized"


@then(parse('"{field_name}" in the parsed output should {comparison}\n{expected_keys}'))
def assert_output_field_content(field_name, comparison, expected_keys, parsed_output):
@then(parse('"{field_name}" in the parsed output should {comparison}'))
def assert_output_field_content(field_name, comparison, parsed_output, docstring):
lang = parsed_output["lang"]
obj = parsed_output["obj"]
expected_keys = expected_keys.split("\n")
expected_keys = docstring.split("\n")
if len(expected_keys) == 1:
expected_keys = expected_keys[0]

Expand Down Expand Up @@ -420,9 +446,13 @@ def editor_filename_suffix(suffix, editor_state):
assert editor_state["tmpfile"]["name"].endswith(suffix), (editor_filename, suffix)


@then(parse("the editor file content should {comparison}"))
def contains_editor_file_docstring(comparison, editor_state, docstring):
contains_editor_file(comparison, docstring, editor_state)


@then(parse('the editor file content should {comparison} "{str_value}"'))
@then(parse("the editor file content should {comparison} empty"))
@then(parse("the editor file content should {comparison}\n{str_value}"))
def contains_editor_file(comparison, str_value, editor_state):
content = editor_state["tmpfile"]["content"]
# content = f'\n"""\n{content}\n"""\n'
Expand Down
6 changes: 5 additions & 1 deletion tests/lib/when_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ def when_we_change_directory(directory_name):
# an empty line of input internally for testing purposes.


@when(parse('we run "jrnl {command}" and {input_method}\n{all_input}'))
@when(re(f'we run "jrnl {command}" and {input_method}'))
def we_run_jrnl_docstring(capsys, keyring, request, command, input_method, docstring):
we_run_jrnl(capsys, keyring, request, command, input_method, docstring)


@when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}'))
@when(re(f'we run "jrnl {command}"(?! and)'))
@when('we run "jrnl"')
Expand Down

0 comments on commit 4d99448

Please sign in to comment.