mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-06-27 21:16:14 +02:00
Use pytest-bdd 6 (#1685)
* update pytest-bdd to 6.0 * update lock file * fix first test (inject command fixture to request) * fix some more tests * fix cli_run fixture * fix password fixture * Remove unused import * Fix greedy should_or_should_not parsing problems while also consolidating its parse/transformation-to-bool code * Prevent greedy matching in "we run" by using regular expression lookahead * Add missing "Outline" in scenario outlines with examples * Split "we use the config" and "we use no config" so pytest won't try to consume config_file as a fixture * Fix missing ShouldOrShouldNot * Formatting * fix get_fixture function * change output of failing test to be a little more useful * update lock file * update type builder to for should/should not to be in it's own file, rename some vars for readability * add parse-type new dev/testing dependency * update lock file --------- Co-authored-by: Jonathan Wren <jonathan@nowandwren.com>
This commit is contained in:
parent
2d0b15ea7b
commit
1a67fd5dec
11 changed files with 136 additions and 127 deletions
|
@ -25,13 +25,16 @@ Feature: Change entry times in journal
|
|||
Scenario Outline: Change flag changes prompted entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -1"
|
||||
Then the output should contain "2020-09-24 09:14 The third entry finally"
|
||||
When we run "jrnl --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
2020-09-24 09:14 The third entry finally after weeks without writing.
|
||||
When we run "jrnl --change-time '2022-04-23 10:30'" and enter
|
||||
Y
|
||||
N
|
||||
Y
|
||||
When we run "jrnl -99 --short"
|
||||
When we run "jrnl --short"
|
||||
Then the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
2022-04-23 10:30 Entry the first.
|
||||
|
|
|
@ -125,7 +125,7 @@ Feature: Searching in a journal
|
|||
| basic_dayone.yaml |
|
||||
|
||||
|
||||
Scenario: Searching for unstarred entries
|
||||
Scenario Outline: Searching for unstarred entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -not -starred"
|
||||
|
@ -138,7 +138,7 @@ Feature: Searching in a journal
|
|||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario: Searching for tagged entries
|
||||
Scenario Outline: Searching for tagged entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -tagged"
|
||||
|
@ -151,7 +151,7 @@ Feature: Searching in a journal
|
|||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario: Searching for untagged entries
|
||||
Scenario Outline: Searching for untagged entries
|
||||
Given we use the config "empty_folder.yaml"
|
||||
When we run "jrnl Tagged entry. This one has a @tag."
|
||||
Then we should get no error
|
||||
|
|
|
@ -201,6 +201,16 @@ def input_method():
|
|||
return ""
|
||||
|
||||
|
||||
@fixture
|
||||
def all_input():
|
||||
return ""
|
||||
|
||||
|
||||
@fixture
|
||||
def command():
|
||||
return ""
|
||||
|
||||
|
||||
@fixture
|
||||
def cache_dir():
|
||||
return {"exists": False, "path": ""}
|
||||
|
@ -221,13 +231,15 @@ def mock_user_input(request, password_input, stdin_input):
|
|||
def _mock_user_input():
|
||||
# user_input needs to be here because we don't know it until cli_run starts
|
||||
user_input = get_fixture(request, "all_input", None)
|
||||
|
||||
if user_input is None:
|
||||
user_input = Exception("Unexpected call for user input")
|
||||
else:
|
||||
user_input = iter(user_input.splitlines())
|
||||
|
||||
def mock_console_input(**kwargs):
|
||||
if kwargs["password"] and not isinstance(password_input, Exception):
|
||||
pw = kwargs.get("password", False)
|
||||
if pw and not isinstance(password_input, Exception):
|
||||
return password_input
|
||||
|
||||
if isinstance(user_input, Iterable):
|
||||
|
@ -236,7 +248,7 @@ def mock_user_input(request, password_input, stdin_input):
|
|||
return "" if input_line == r"\n" else input_line
|
||||
|
||||
# exceptions
|
||||
return user_input if not kwargs["password"] else password_input
|
||||
return user_input if not pw else password_input
|
||||
|
||||
mock_console = Mock(wraps=Console(stderr=True))
|
||||
mock_console.input = Mock(side_effect=mock_console_input)
|
||||
|
|
|
@ -19,7 +19,6 @@ from jrnl.time import __get_pdt_calendar
|
|||
from tests.lib.fixtures import FailedKeyring
|
||||
from tests.lib.fixtures import NoKeyring
|
||||
from tests.lib.fixtures import TestKeyring
|
||||
from tests.lib.helpers import get_fixture
|
||||
|
||||
|
||||
@given(parse("we {editor_method} to the editor if opened\n{editor_input}"))
|
||||
|
@ -84,16 +83,16 @@ def we_have_type_of_keyring(keyring_type):
|
|||
return TestKeyring()
|
||||
|
||||
|
||||
@given(parse('we use the config "{config_file}"'), target_fixture="config_path")
|
||||
@given(parse("we use no config"), target_fixture="config_path")
|
||||
def we_use_the_config(request, temp_dir, working_dir):
|
||||
config_file = get_fixture(request, "config_file")
|
||||
def we_use_no_config(temp_dir):
|
||||
os.chdir(temp_dir.name) # @todo move this step to a more universal place
|
||||
return os.path.join(temp_dir.name, "non_existing_config.yaml")
|
||||
|
||||
|
||||
@given(parse('we use the config "{config_file}"'), target_fixture="config_path")
|
||||
def we_use_the_config(request, temp_dir, working_dir, config_file):
|
||||
# Move into temp dir as cwd
|
||||
os.chdir(temp_dir.name)
|
||||
|
||||
if not config_file:
|
||||
return os.path.join(temp_dir.name, "non_existing_config.yaml")
|
||||
os.chdir(temp_dir.name) # @todo move this step to a more universal place
|
||||
|
||||
# Copy the config file over
|
||||
config_source = os.path.join(working_dir, "data", "configs", config_file)
|
||||
|
@ -133,7 +132,7 @@ def config_exists(config_file, temp_dir, working_dir):
|
|||
shutil.copy2(config_source, config_dest)
|
||||
|
||||
|
||||
@given(parse('we use the password "{password}" if prompted'))
|
||||
@given(parse('we use the password "{password}" if prompted'), target_fixture="password")
|
||||
def use_password_forever(password):
|
||||
return password
|
||||
|
||||
|
|
|
@ -32,17 +32,6 @@ def does_directory_contain_n_files(directory_path, number):
|
|||
return int(number) == count
|
||||
|
||||
|
||||
def parse_should_or_should_not(should_or_should_not):
|
||||
if should_or_should_not == "should":
|
||||
return True
|
||||
elif should_or_should_not == "should not":
|
||||
return False
|
||||
else:
|
||||
raise Exception(
|
||||
"should_or_should_not valid values are 'should' or 'should not'"
|
||||
)
|
||||
|
||||
|
||||
def assert_equal_tags_ignoring_order(
|
||||
actual_line, expected_line, actual_content, expected_content
|
||||
):
|
||||
|
@ -81,7 +70,7 @@ def spy_wrapper(wrapped_function):
|
|||
|
||||
|
||||
def get_fixture(request, name, default=None):
|
||||
result = default
|
||||
if name in request.node.fixturenames:
|
||||
result = request.getfixturevalue(name)
|
||||
return result
|
||||
try:
|
||||
return request.getfixturevalue(name)
|
||||
except LookupError:
|
||||
return default
|
||||
|
|
|
@ -15,7 +15,9 @@ from tests.lib.helpers import assert_equal_tags_ignoring_order
|
|||
from tests.lib.helpers import does_directory_contain_files
|
||||
from tests.lib.helpers import does_directory_contain_n_files
|
||||
from tests.lib.helpers import get_nested_val
|
||||
from tests.lib.helpers import parse_should_or_should_not
|
||||
from tests.lib.type_builders import should_choice
|
||||
|
||||
SHOULD_DICT = {"Should": should_choice}
|
||||
|
||||
|
||||
@then("we should get no error")
|
||||
|
@ -31,40 +33,38 @@ def output_should_match(regex, cli_run):
|
|||
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}"
|
||||
|
||||
|
||||
@then(parse("the output {should_or_should_not} contain\n{expected_output}"))
|
||||
@then(parse('the output {should_or_should_not} contain "{expected_output}"'))
|
||||
@then(parse("the output {it_should:Should} contain\n{expected_output}", SHOULD_DICT))
|
||||
@then(parse('the output {it_should:Should} contain "{expected_output}"', SHOULD_DICT))
|
||||
@then(
|
||||
parse(
|
||||
"the {which_output_stream} output {should_or_should_not} contain\n{expected_output}"
|
||||
"the {which_output_stream} output {it_should:Should} contain\n{expected_output}",
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(
|
||||
parse(
|
||||
'the {which_output_stream} output {should_or_should_not} contain "{expected_output}"'
|
||||
'the {which_output_stream} output {it_should:Should} contain "{expected_output}"',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
def output_should_contain(
|
||||
expected_output, which_output_stream, cli_run, should_or_should_not
|
||||
):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
def output_should_contain(expected_output, which_output_stream, cli_run, it_should):
|
||||
output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}"
|
||||
assert expected_output
|
||||
if which_output_stream is None:
|
||||
assert ((expected_output in cli_run["stdout"]) == we_should) or (
|
||||
(expected_output in cli_run["stderr"]) == we_should
|
||||
assert ((expected_output in cli_run["stdout"]) == it_should) or (
|
||||
(expected_output in cli_run["stderr"]) == it_should
|
||||
), output_str
|
||||
|
||||
elif which_output_stream == "standard":
|
||||
assert (expected_output in cli_run["stdout"]) == we_should, output_str
|
||||
assert (expected_output in cli_run["stdout"]) == it_should, output_str
|
||||
|
||||
elif which_output_stream == "error":
|
||||
assert (expected_output in cli_run["stderr"]) == we_should, output_str
|
||||
assert (expected_output in cli_run["stderr"]) == it_should, output_str
|
||||
|
||||
else:
|
||||
assert (
|
||||
expected_output in cli_run[which_output_stream]
|
||||
) == we_should, output_str
|
||||
) == it_should, output_str
|
||||
|
||||
|
||||
@then(parse("the output should not contain\n{expected_output}"))
|
||||
|
@ -78,7 +78,7 @@ def output_should_not_contain(expected_output, cli_run):
|
|||
def output_should_be(expected_output, cli_run):
|
||||
actual = cli_run["stdout"].strip()
|
||||
expected = expected_output.strip()
|
||||
assert expected == actual
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@then("the output should be empty")
|
||||
|
@ -130,19 +130,19 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir
|
|||
|
||||
@then(
|
||||
parse(
|
||||
'the config for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"'
|
||||
'the config for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(
|
||||
parse(
|
||||
'the config for journal "{journal_name}" {should_or_should_not} contain\n{some_yaml}'
|
||||
'the config for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(parse('the config {should_or_should_not} contain "{some_yaml}"'))
|
||||
@then(parse("the config {should_or_should_not} contain\n{some_yaml}"))
|
||||
def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_yaml):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
@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:
|
||||
actual = actual["journals"][journal_name]
|
||||
|
@ -154,26 +154,28 @@ def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_
|
|||
# `expected` objects formatted in yaml only compare one level deep
|
||||
actual_slice = {key: actual.get(key, None) for key in expected.keys()}
|
||||
|
||||
assert (expected == actual_slice) == we_should
|
||||
assert (expected == actual_slice) == it_should
|
||||
|
||||
|
||||
@then(
|
||||
parse(
|
||||
'the config in memory for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"'
|
||||
'the config in memory for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(
|
||||
parse(
|
||||
'the config in memory for journal "{journal_name}" {should_or_should_not} contain\n{some_yaml}'
|
||||
'the config in memory for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(parse('the config in memory {should_or_should_not} contain "{some_yaml}"'))
|
||||
@then(parse("the config in memory {should_or_should_not} contain\n{some_yaml}"))
|
||||
def config_var_in_memory(
|
||||
config_in_memory, journal_name, should_or_should_not, some_yaml
|
||||
):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
@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:
|
||||
actual = actual["journals"][journal_name]
|
||||
|
@ -185,7 +187,7 @@ def config_var_in_memory(
|
|||
# `expected` objects formatted in yaml only compare one level deep
|
||||
actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()}
|
||||
|
||||
assert (expected == actual_slice) == we_should
|
||||
assert (expected == actual_slice) == it_should
|
||||
|
||||
|
||||
@then("we should be prompted for a password")
|
||||
|
@ -224,31 +226,27 @@ def journal_directory_should_not_exist(config_on_disk, journal_name):
|
|||
), f'Journal "{journal_name}" does exist'
|
||||
|
||||
|
||||
@then(parse("the journal {should_or_should_not} exist"))
|
||||
def journal_should_not_exist(config_on_disk, should_or_should_not):
|
||||
@then(parse("the journal {it_should:Should} exist", SHOULD_DICT))
|
||||
def journal_should_not_exist(config_on_disk, it_should):
|
||||
scoped_config = scope_config(config_on_disk, "default")
|
||||
expected_path = scoped_config["journal"]
|
||||
|
||||
contains_files = does_directory_contain_files(expected_path, ".")
|
||||
|
||||
if should_or_should_not == "should":
|
||||
assert contains_files
|
||||
elif should_or_should_not == "should not":
|
||||
assert not contains_files
|
||||
else:
|
||||
raise Exception(
|
||||
"should_or_should_not valid values are 'should' or 'should not'"
|
||||
)
|
||||
assert contains_files == it_should
|
||||
|
||||
|
||||
@then(parse('the journal "{journal_name}" directory {should_or_should_not} exist'))
|
||||
def directory_should_not_exist(config_on_disk, should_or_should_not, journal_name):
|
||||
@then(
|
||||
parse(
|
||||
'the journal "{journal_name}" directory {it_should:Should} exist', SHOULD_DICT
|
||||
)
|
||||
)
|
||||
def directory_should_not_exist(config_on_disk, it_should, journal_name):
|
||||
scoped_config = scope_config(config_on_disk, journal_name)
|
||||
expected_path = scoped_config["journal"]
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
dir_exists = os.path.isdir(expected_path)
|
||||
|
||||
assert dir_exists == we_should
|
||||
assert dir_exists == it_should
|
||||
|
||||
|
||||
@then(parse('the content of file "{file_path}" in the cache should be\n{file_content}'))
|
||||
|
@ -383,26 +381,23 @@ def count_elements(number, item, cli_run):
|
|||
assert len(xml_tree.findall(".//" + item)) == number
|
||||
|
||||
|
||||
@then(parse("the editor {should_or_should_not} have been called"))
|
||||
@then(parse("the editor {it_should:Should} have been called", SHOULD_DICT))
|
||||
@then(
|
||||
parse(
|
||||
"the editor {should_or_should_not} have been called with {num_args} arguments"
|
||||
"the editor {it_should:Should} have been called with {num_args} arguments",
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
def count_editor_args(num_args, cli_run, editor_state, should_or_should_not):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
assert cli_run["mocks"]["editor"].called == we_should
|
||||
def count_editor_args(num_args, cli_run, editor_state, it_should):
|
||||
assert cli_run["mocks"]["editor"].called == it_should
|
||||
|
||||
if isinstance(num_args, int):
|
||||
assert len(editor_state["command"]) == int(num_args)
|
||||
|
||||
|
||||
@then(parse("the stdin prompt {should_or_should_not} have been called"))
|
||||
def stdin_prompt_called(cli_run, should_or_should_not):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
assert cli_run["mocks"]["stdin_input"].called == we_should
|
||||
@then(parse("the stdin prompt {it_should:Should} have been called", SHOULD_DICT))
|
||||
def stdin_prompt_called(cli_run, it_should):
|
||||
assert cli_run["mocks"]["stdin_input"].called == it_should
|
||||
|
||||
|
||||
@then(parse('the editor filename should end with "{suffix}"'))
|
||||
|
|
11
tests/lib/type_builders.py
Normal file
11
tests/lib/type_builders.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Copyright © 2012-2023 jrnl contributors
|
||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
from parse_type import TypeBuilder
|
||||
|
||||
should_choice = TypeBuilder.make_enum(
|
||||
{
|
||||
"should": True,
|
||||
"should not": False,
|
||||
}
|
||||
)
|
|
@ -7,6 +7,7 @@ from contextlib import ExitStack
|
|||
from pytest_bdd import when
|
||||
from pytest_bdd.parsers import parse
|
||||
from pytest_bdd.parsers import re
|
||||
from pytest_bdd.steps import inject_fixture
|
||||
|
||||
from jrnl.main import run
|
||||
|
||||
|
@ -29,13 +30,20 @@ all_input = '("(?P<all_input>[^"]*)")'
|
|||
|
||||
@when(parse('we run "jrnl {command}" and {input_method}\n{all_input}'))
|
||||
@when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}'))
|
||||
@when(parse('we run "jrnl {command}"'))
|
||||
@when(re(f'we run "jrnl {command}"(?! and)'))
|
||||
@when('we run "jrnl"')
|
||||
def we_run_jrnl(cli_run, capsys, keyring):
|
||||
def we_run_jrnl(capsys, keyring, request, command, input_method, all_input):
|
||||
from keyring import set_keyring
|
||||
|
||||
set_keyring(keyring)
|
||||
|
||||
# fixture injection (pytest-bdd >=6.0)
|
||||
inject_fixture(request, "command", command)
|
||||
inject_fixture(request, "input_method", input_method)
|
||||
inject_fixture(request, "all_input", all_input)
|
||||
|
||||
cli_run = request.getfixturevalue("cli_run")
|
||||
|
||||
with ExitStack() as stack:
|
||||
mocks = cli_run["mocks"]
|
||||
factories = cli_run["mock_factories"]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue