Fix compatibility of step-functions matching on multiple lines

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.
This commit is contained in:
Carl Smedstad 2024-11-16 18:28:35 +01:00
parent a3e55c5e0d
commit 4d99448cdd
No known key found for this signature in database
GPG key ID: 49C93367BA86290E
3 changed files with 82 additions and 43 deletions

View file

@ -13,6 +13,7 @@ from xml.etree import ElementTree as ET
from pytest_bdd import given from pytest_bdd import given
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re
from jrnl import __version__ from jrnl import __version__
from jrnl.time import __get_pdt_calendar from jrnl.time import __get_pdt_calendar
@ -21,7 +22,11 @@ from tests.lib.fixtures import NoKeyring
from tests.lib.fixtures import TestKeyring 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")) @given(parse("we {editor_method} nothing to the editor if opened"))
def we_enter_editor(editor_method, editor_input, editor_state): def we_enter_editor(editor_method, editor_input, editor_state):
file_method = editor_state["intent"]["method"] file_method = editor_state["intent"]["method"]

View file

@ -8,6 +8,7 @@ from xml.etree import ElementTree as ET
from pytest_bdd import then from pytest_bdd import then
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re as pytest_bdd_parsers_re
from ruamel.yaml import YAML from ruamel.yaml import YAML
from jrnl.config import scope_config from jrnl.config import scope_config
@ -30,7 +31,11 @@ def should_get_an_error(cli_run):
assert cli_run["status"] != 0, cli_run["status"] 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}"')) @then(parse('the output should match "{regex}"'))
def output_should_match(regex, cli_run): def output_should_match(regex, cli_run):
out = cli_run["stdout"] out = cli_run["stdout"]
@ -38,14 +43,18 @@ def output_should_match(regex, cli_run):
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}" 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", SHOULD_DICT))
@then(parse('the output {it_should:Should} contain "{expected}"', SHOULD_DICT))
@then( @then(
parse( parse(
"the {which_output_stream} output {it_should:Should} contain\n{expected}", "the {which_output_stream} output {it_should:Should} contain",
SHOULD_DICT, 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( @then(
parse( parse(
'the {which_output_stream} output {it_should:Should} contain "{expected}"', 'the {which_output_stream} output {it_should:Should} contain "{expected}"',
@ -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 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}"')) @then(parse('the output should not contain "{expected_output}"'))
def output_should_not_contain(expected_output, cli_run): def output_should_not_contain(expected_output, cli_run):
assert expected_output not in cli_run["stdout"] 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}"')) @then(parse('the output should be "{expected_output}"'))
def output_should_be(expected_output, cli_run): def output_should_be(expected_output, cli_run):
actual = cli_run["stdout"].strip() actual = cli_run["stdout"].strip()
@ -137,6 +154,17 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir
assert os.path.samefile(default_journal_path, expected_journal_path) assert os.path.samefile(default_journal_path, expected_journal_path)
@then(
parse(
'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( @then(
parse( parse(
'the config for journal "{journal_name}" ' 'the config for journal "{journal_name}" '
@ -144,15 +172,7 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir
SHOULD_DICT, SHOULD_DICT,
) )
) )
@then(
parse(
'the config for journal "{journal_name}" '
"{it_should:Should} contain\n{some_yaml}",
SHOULD_DICT,
)
)
@then(parse('the config {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): def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
actual = config_on_disk actual = config_on_disk
if journal_name: if journal_name:
@ -168,6 +188,20 @@ def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
assert (expected == actual_slice) == it_should assert (expected == actual_slice) == it_should
@then(
parse(
'the config in memory for journal "{journal_name}" '
"{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( @then(
parse( parse(
'the config in memory for journal "{journal_name}" ' 'the config in memory for journal "{journal_name}" '
@ -175,19 +209,9 @@ def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
SHOULD_DICT, SHOULD_DICT,
) )
) )
@then(
parse(
'the config in memory for journal "{journal_name}" '
"{it_should:Should} contain\n{some_yaml}",
SHOULD_DICT,
)
)
@then( @then(
parse('the config in memory {it_should:Should} contain "{some_yaml}"', SHOULD_DICT) 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): def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
actual = config_in_memory["overrides"] actual = config_in_memory["overrides"]
if journal_name: if journal_name:
@ -213,21 +237,23 @@ def password_was_not_called(cli_run):
assert not cli_run["mocks"]["user_input"].return_value.input.called assert not cli_run["mocks"]["user_input"].return_value.input.called
@then(parse("the cache directory should contain the files\n{file_list}")) @then(parse("the cache directory should contain the files"))
def assert_dir_contains_files(file_list, cache_dir): def assert_dir_contains_files(cache_dir, docstring):
assert does_directory_contain_files(file_list, cache_dir["path"]) 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): def assert_dir_contains_n_files(cache_dir, number):
assert does_directory_contain_n_files(cache_dir["path"], number) assert does_directory_contain_n_files(cache_dir["path"], number)
@then(parse("the journal directory should contain\n{file_list}")) @then(parse("the journal directory should contain"))
def journal_directory_should_contain(config_on_disk, file_list): def journal_directory_should_contain(config_on_disk, docstring):
scoped_config = scope_config(config_on_disk, "default") 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')) @then(parse('journal "{journal_name}" should not exist'))
@ -262,10 +288,10 @@ def directory_should_not_exist(config_on_disk, it_should, journal_name):
assert dir_exists == it_should assert dir_exists == it_should
@then(parse('the content of file "{file_path}" in the cache should be\n{file_content}')) @then(parse('the content of file "{file_path}" in the cache should be'))
def content_of_file_should_be(file_path, file_content, cache_dir): def content_of_file_should_be(file_path, cache_dir, docstring):
assert cache_dir["exists"] 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: with open(os.path.join(cache_dir["path"], file_path), "r") as f:
actual_content = f.read().strip().splitlines() actual_content = f.read().strip().splitlines()
@ -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}")) @then(parse("the cache should contain the files"))
def cache_dir_contains_files(file_list, cache_dir): def cache_dir_contains_files(cache_dir, docstring):
assert cache_dir["exists"] assert cache_dir["exists"]
actual_files = os.listdir(cache_dir["path"]) 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 # sort to deal with inconsistent default file ordering on different OS's
actual_files.sort() actual_files.sort()
@ -336,11 +362,11 @@ def assert_parsed_output_item_count(node_name, number, parsed_output):
assert False, f"Language name {lang} not recognized" assert False, f"Language name {lang} not recognized"
@then(parse('"{field_name}" in the parsed output should {comparison}\n{expected_keys}')) @then(parse('"{field_name}" in the parsed output should {comparison}'))
def assert_output_field_content(field_name, comparison, expected_keys, parsed_output): def assert_output_field_content(field_name, comparison, parsed_output, docstring):
lang = parsed_output["lang"] lang = parsed_output["lang"]
obj = parsed_output["obj"] obj = parsed_output["obj"]
expected_keys = expected_keys.split("\n") expected_keys = docstring.split("\n")
if len(expected_keys) == 1: if len(expected_keys) == 1:
expected_keys = expected_keys[0] expected_keys = expected_keys[0]
@ -420,9 +446,13 @@ def editor_filename_suffix(suffix, editor_state):
assert editor_state["tmpfile"]["name"].endswith(suffix), (editor_filename, suffix) 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} "{str_value}"'))
@then(parse("the editor file content should {comparison} empty")) @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): def contains_editor_file(comparison, str_value, editor_state):
content = editor_state["tmpfile"]["content"] content = editor_state["tmpfile"]["content"]
# content = f'\n"""\n{content}\n"""\n' # content = f'\n"""\n{content}\n"""\n'

View file

@ -34,7 +34,11 @@ all_input = '("(?P<all_input>[^"]*)")'
# an empty line of input internally for testing purposes. # 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 {input_method} {all_input}'))
@when(re(f'we run "jrnl {command}"(?! and)')) @when(re(f'we run "jrnl {command}"(?! and)'))
@when('we run "jrnl"') @when('we run "jrnl"')