From 8992612f74730e0f2edc498b0ab56b237f5f963d Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 23 Aug 2023 13:01:00 -0500 Subject: [PATCH 01/19] show the repr of a string object when logging --- screenpy/actions/see.py | 4 +- screenpy/pacing.py | 3 +- screenpy/resolutions/contains_the_entry.py | 4 +- screenpy/resolutions/contains_the_item.py | 10 ++++- screenpy/resolutions/contains_the_key.py | 10 ++++- screenpy/resolutions/contains_the_text.py | 10 ++++- screenpy/resolutions/contains_the_value.py | 10 ++++- .../sequence_containing_pattern.py | 4 +- screenpy/resolutions/ends_with.py | 10 ++++- screenpy/resolutions/reads_exactly.py | 10 ++++- screenpy/resolutions/starts_with.py | 10 ++++- screenpy/speech_tools.py | 23 +++++++++- tests/test_narration_integration.py | 2 +- tests/test_resolutions.py | 43 +++++++++++++------ tests/test_speech_tools.py | 14 +++++- 15 files changed, 129 insertions(+), 38 deletions(-) diff --git a/screenpy/actions/see.py b/screenpy/actions/see.py index fb4a75c0..ef0349dd 100644 --- a/screenpy/actions/see.py +++ b/screenpy/actions/see.py @@ -9,7 +9,7 @@ from screenpy.actor import Actor from screenpy.pacing import aside, beat from screenpy.protocols import Answerable, ErrorKeeper, Resolvable -from screenpy.speech_tools import get_additive_description +from screenpy.speech_tools import get_additive_description, tostring SelfSee = TypeVar("SelfSee", bound="See") T_Q = Union[Answerable, object] @@ -56,7 +56,7 @@ def perform_as(self: SelfSee, the_actor: Actor) -> None: else: # must be a value instead of a question! value = self.question - aside(f"the actual value is: {value}") + aside(f"the actual value is: {tostring(value)}") reason = "" if isinstance(self.question, ErrorKeeper): diff --git a/screenpy/pacing.py b/screenpy/pacing.py index d0b8a29a..6d454065 100644 --- a/screenpy/pacing.py +++ b/screenpy/pacing.py @@ -9,6 +9,7 @@ from typing import Any, Callable, Optional from screenpy.narration import Narrator, StdOutAdapter +from screenpy.speech_tools import tostring Function = Callable[..., Any] the_narrator: Narrator = Narrator(adapters=[StdOutAdapter()]) @@ -88,7 +89,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: with the_narrator.stating_a_beat(func, completed_line, gravitas) as n_func: retval = n_func(*args, **kwargs) if retval is not None: - aside(f"=> {retval}") + aside(f"=> {tostring(retval)}") return retval return wrapper diff --git a/screenpy/resolutions/contains_the_entry.py b/screenpy/resolutions/contains_the_entry.py index 2dc12553..b0778a04 100644 --- a/screenpy/resolutions/contains_the_entry.py +++ b/screenpy/resolutions/contains_the_entry.py @@ -78,4 +78,6 @@ def __init__(self, *kv_args: Any, **kv_kwargs: Any) -> None: ] self.entries = dict(pairs, **kv_kwargs) self.entry_plural = "entries" if len(self.entries) != 1 else "entry" - self.entries_to_log = ", ".join(f"{k}->{v}" for k, v in self.entries.items()) + self.entries_to_log = ", ".join( + f"{k!r}->{v!r}" for k, v in self.entries.items() + ) diff --git a/screenpy/resolutions/contains_the_item.py b/screenpy/resolutions/contains_the_item.py index c9c3f548..104f5e9a 100644 --- a/screenpy/resolutions/contains_the_item.py +++ b/screenpy/resolutions/contains_the_item.py @@ -8,6 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import tostring T = TypeVar("T") @@ -24,9 +25,14 @@ class ContainsTheItem: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f'A sequence containing "{self.item}".' + return f"A sequence containing {tostring(self.item)}." - @beat('... hoping it contains "{item}".') + @property + def beatmsg(self) -> str: + """format string meant for beat msg""" + return f"... hoping it contains {tostring(self.item)}." + + @beat("{beatmsg}") def resolve(self) -> Matcher[Sequence[T]]: """Produce the Matcher to make the assertion.""" return has_item(self.item) diff --git a/screenpy/resolutions/contains_the_key.py b/screenpy/resolutions/contains_the_key.py index e5194c6b..34129a61 100644 --- a/screenpy/resolutions/contains_the_key.py +++ b/screenpy/resolutions/contains_the_key.py @@ -8,6 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import tostring K = TypeVar("K", bound=Hashable) @@ -22,9 +23,14 @@ class ContainsTheKey(Generic[K]): def describe(self) -> str: """Describe the Resolution's expectation.""" - return f'Containing the key "{self.key}".' + return f"Containing the key {tostring(self.key)}." - @beat('... hoping it\'s a dict containing the key "{key}".') + @property + def beatmsg(self) -> str: + """format string meant for beat msg""" + return f"... hoping it's a dict containing the key {tostring(self.key)}." + + @beat("{beatmsg}") def resolve(self) -> Matcher[Mapping[K, Any]]: """Produce the Matcher to make the assertion.""" return has_key(self.key) diff --git a/screenpy/resolutions/contains_the_text.py b/screenpy/resolutions/contains_the_text.py index cd874ba4..5dd236e1 100644 --- a/screenpy/resolutions/contains_the_text.py +++ b/screenpy/resolutions/contains_the_text.py @@ -6,6 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import tostring class ContainsTheText: @@ -20,9 +21,14 @@ class ContainsTheText: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f'Containing the text "{self.text}".' + return f"Containing the text {tostring(self.text)}." - @beat('... hoping it contains "{text}".') + @property + def beatmsg(self) -> str: + """format string meant for beat msg""" + return f"... hoping it contains {tostring(self.text)}." + + @beat("{beatmsg}") def resolve(self) -> Matcher[str]: """Produce the Matcher to make the assertion.""" return contains_string(self.text) diff --git a/screenpy/resolutions/contains_the_value.py b/screenpy/resolutions/contains_the_value.py index c8d3b63b..d376c35f 100644 --- a/screenpy/resolutions/contains_the_value.py +++ b/screenpy/resolutions/contains_the_value.py @@ -8,6 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import tostring V = TypeVar("V") @@ -24,9 +25,14 @@ class ContainsTheValue(Generic[V]): def describe(self) -> str: """Describe the Resolution's expectation.""" - return f'Containing the value "{self.value}".' + return f"Containing the value {tostring(self.value)}." - @beat('... hoping it contains the value "{value}".') + @property + def beatmsg(self) -> str: + """format string meant for beat msg""" + return f"... hoping it contains the value {tostring(self.value)}." + + @beat("{beatmsg}") def resolve(self) -> Matcher[Mapping[Any, V]]: """Produce the Matcher to form the assertion.""" return has_value(self.value) diff --git a/screenpy/resolutions/custom_matchers/sequence_containing_pattern.py b/screenpy/resolutions/custom_matchers/sequence_containing_pattern.py index afd575b6..dc2fb783 100644 --- a/screenpy/resolutions/custom_matchers/sequence_containing_pattern.py +++ b/screenpy/resolutions/custom_matchers/sequence_containing_pattern.py @@ -28,7 +28,7 @@ def _matches(self, item: Sequence[str]) -> bool: def describe_to(self, description: Description) -> None: """Describe the passing case.""" description.append_text( - f"a sequence containing an element which matches {self.pattern}" + f'a sequence containing an element which matches r"{self.pattern}"' ) def describe_match(self, _: Sequence[str], match_description: Description) -> None: @@ -43,7 +43,7 @@ def describe_mismatch( mismatch_description.append_text("was not a sequence") return mismatch_description.append_text( - f"did not contain an item matching {self.pattern}" + f'did not contain an item matching r"{self.pattern}"' ) diff --git a/screenpy/resolutions/ends_with.py b/screenpy/resolutions/ends_with.py index 00b1996f..894fc629 100644 --- a/screenpy/resolutions/ends_with.py +++ b/screenpy/resolutions/ends_with.py @@ -6,6 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import tostring class EndsWith: @@ -20,9 +21,14 @@ class EndsWith: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f'Ending with "{self.postfix}".' + return f"Ending with {tostring(self.postfix)}." - @beat('... hoping it ends with "{postfix}".') + @property + def beatmsg(self) -> str: + """format string meant for beat msg""" + return f"... hoping it ends with {tostring(self.postfix)}." + + @beat("{beatmsg}") def resolve(self) -> Matcher[str]: """Produce the Matcher to make the assertion.""" return ends_with(self.postfix) diff --git a/screenpy/resolutions/reads_exactly.py b/screenpy/resolutions/reads_exactly.py index 310da5fc..62f3080f 100644 --- a/screenpy/resolutions/reads_exactly.py +++ b/screenpy/resolutions/reads_exactly.py @@ -6,6 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import tostring class ReadsExactly: @@ -20,9 +21,14 @@ class ReadsExactly: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f'"{self.text}", verbatim.' + return f"{tostring(self.text)}, verbatim." - @beat('... hoping it\'s "{text}", verbatim.') + @property + def beatmsg(self) -> str: + """format string meant for beat msg""" + return f"... hoping it's {tostring(self.text)}, verbatim." + + @beat("{beatmsg}") def resolve(self) -> Matcher[object]: """Produce the Matcher to make the assertion.""" return has_string(self.text) diff --git a/screenpy/resolutions/starts_with.py b/screenpy/resolutions/starts_with.py index 290080e2..bbb6c499 100644 --- a/screenpy/resolutions/starts_with.py +++ b/screenpy/resolutions/starts_with.py @@ -6,6 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import tostring class StartsWith: @@ -20,9 +21,14 @@ class StartsWith: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f'Starting with "{self.prefix}".' + return f"Starting with {tostring(self.prefix)}." - @beat('... hoping it starts with "{prefix}".') + @property + def beatmsg(self) -> str: + """format string meant for beat msg""" + return f"... hoping it starts with {tostring(self.prefix)}." + + @beat("{beatmsg}") def resolve(self) -> Matcher[str]: """Produce the Matcher to make the assertion.""" return starts_with(self.prefix) diff --git a/screenpy/speech_tools.py b/screenpy/speech_tools.py index 83fefd1a..314019cb 100644 --- a/screenpy/speech_tools.py +++ b/screenpy/speech_tools.py @@ -3,12 +3,14 @@ """ import re -from typing import Any, Union +from typing import TypeVar, Union, overload from screenpy.protocols import Answerable, Describable, Performable, Resolvable +T = TypeVar("T") -def get_additive_description(describable: Union[Describable, Any]) -> str: + +def get_additive_description(describable: Union[Describable, T]) -> str: """Extract a description that can be placed within a sentence. The ``describe`` method of Describables will provide a description, @@ -44,3 +46,20 @@ class name: replace each capital letter with a space and a lower-case description = f"the {describable.__class__.__name__}" return description + + +@overload +def tostring(item: str) -> str: + ... + + +@overload +def tostring(item: T) -> T: + ... + + +def tostring(item: str | T) -> str | T: + """help convert objects to a proper format for logging""" + if isinstance(item, str): + return repr(item) + return item diff --git a/tests/test_narration_integration.py b/tests/test_narration_integration.py index bf1f7353..f859ee50 100644 --- a/tests/test_narration_integration.py +++ b/tests/test_narration_integration.py @@ -43,7 +43,7 @@ def _assert_stdout_correct(caplog) -> None: assert caplog.messages[1] == f"Scene: {TEST_SCENE.title()}" assert caplog.messages[2] == TEST_BEAT assert caplog.messages[3] == f"{INDENT}{TEST_ASIDE}" - assert caplog.messages[4] == f"{INDENT}=> {TEST_RETVAL}" + assert caplog.messages[4] == f"{INDENT}=> {TEST_RETVAL!r}" class TestNarrateToStdOut: diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index e9d9c6ca..9f9a0cbc 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -123,8 +123,7 @@ def test_can_be_instantiated(self) -> None: def test_the_test(self) -> None: cim = ContainsItemMatching(r"([Ss]pam ?)+").resolve() - - assert cim.matches(["Spam", "Eggs", "Spam and eggs"]) + assert cim.matches(["Spam", "Eggs", "Spam and eggs"], ) assert not cim.matches(["Porridge"]) def test_description(self) -> None: @@ -150,10 +149,10 @@ def test_the_test(self) -> None: """Matches dictionaries containing the entry(/ies)""" cte_single = ContainsTheEntry(key="value").resolve() cte_multiple = ContainsTheEntry(key1="value1", key2="value2").resolve() - cte_alt2 = ContainsTheEntry({"key2": "something2"}).resolve() + cte_alt2 = ContainsTheEntry({"key2": 12345}).resolve() cte_alt3 = ContainsTheEntry("key3", "something3").resolve() - assert cte_alt2.matches({"key2": "something2"}) + assert cte_alt2.matches({"key2": 12345}) assert cte_alt3.matches({"key3": "something3"}) assert cte_single.matches({"key": "value"}) assert cte_single.matches({"key": "value", "play": "Hamlet"}) @@ -166,15 +165,15 @@ def test_the_test(self) -> None: def test_description(self) -> None: test_entry = {"spam": "eggs"} - test_entries = {"tree": "larch", "spam": "eggs"} + test_entries = {"tree": 1234, "spam": "eggs"} cte_single = ContainsTheEntry(**test_entry) cte_multiple = ContainsTheEntry(**test_entries) - expected_description_single = "A mapping with the entry spam->eggs." + expected_description_single = "A mapping with the entry 'spam'->'eggs'." expected_description_multiple = ( "A mapping with the entries" - f" {', '.join(f'{k}->{v}' for k, v in test_entries.items())}." + f" {', '.join(f'{k!r}->{v!r}' for k, v in test_entries.items())}." ) assert cte_single.describe() == expected_description_single assert cte_multiple.describe() == expected_description_multiple @@ -198,7 +197,15 @@ def test_description(self) -> None: cti = ContainsTheItem(test_item) - expected_description = f'A sequence containing "{test_item}".' + expected_description = f'A sequence containing {test_item}.' + assert cti.describe() == expected_description + + def test_description_str(self) -> None: + test_item = "1" + + cti = ContainsTheItem(test_item) + + expected_description = f'A sequence containing {test_item!r}.' assert cti.describe() == expected_description @@ -221,7 +228,7 @@ def test_description(self) -> None: ctk = ContainsTheKey(test_key) - expected_description = f'Containing the key "{test_key}".' + expected_description = f'Containing the key {test_key!r}.' assert ctk.describe() == expected_description @@ -243,7 +250,7 @@ def test_description(self) -> None: ctt = ContainsTheText(test_text) - expected_description = f'Containing the text "{test_text}".' + expected_description = f'Containing the text {test_text!r}.' assert ctt.describe() == expected_description @@ -266,7 +273,15 @@ def test_description(self) -> None: ctv = ContainsTheValue(test_value) - expected_description = f'Containing the value "{test_value}".' + expected_description = f'Containing the value {test_value}.' + assert ctv.describe() == expected_description + + def test_description_str(self) -> None: + test_value = "42" + + ctv = ContainsTheValue(test_value) + + expected_description = f'Containing the value {test_value!r}.' assert ctv.describe() == expected_description @@ -306,7 +321,7 @@ def test_description(self) -> None: ew = EndsWith(test_postfix) - expected_description = f'Ending with "{test_postfix}".' + expected_description = f'Ending with {test_postfix!r}.' assert ew.describe() == expected_description @@ -585,7 +600,7 @@ def test_description(self) -> None: re_ = ReadsExactly(test_text) - expected_description = f'"{test_text}", verbatim.' + expected_description = f'{test_text!r}, verbatim.' assert re_.describe() == expected_description @@ -606,5 +621,5 @@ def test_description(self) -> None: sw = StartsWith(test_prefix) - expected_description = f'Starting with "{test_prefix}".' + expected_description = f'Starting with {test_prefix!r}.' assert sw.describe() == expected_description diff --git a/tests/test_speech_tools.py b/tests/test_speech_tools.py index e3ca076d..4351f29d 100644 --- a/tests/test_speech_tools.py +++ b/tests/test_speech_tools.py @@ -1,6 +1,6 @@ import pytest -from screenpy.speech_tools import get_additive_description +from screenpy.speech_tools import get_additive_description, tostring class ThisIsADescribableWithADescribe: @@ -52,3 +52,15 @@ def test_indescribable(self) -> None: description = get_additive_description(Indescribable()) assert description == "something indescribable" + + +class TestTostring: + def test_str(self): + val = "hello\nworld!" + + assert tostring(val) == f"{val!r}" + + def test_int(self): + val = 1234 + + assert tostring(val) == val From 1417aa3c2cfa8b2a5dbc7f3981cd7d433b06cbe5 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 23 Aug 2023 15:57:08 -0500 Subject: [PATCH 02/19] forgot you can't use the 'and' syntax before python 3.10 --- screenpy/speech_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screenpy/speech_tools.py b/screenpy/speech_tools.py index 314019cb..6c12d905 100644 --- a/screenpy/speech_tools.py +++ b/screenpy/speech_tools.py @@ -58,7 +58,7 @@ def tostring(item: T) -> T: ... -def tostring(item: str | T) -> str | T: +def tostring(item: Union[str, T]) -> Union[str, T]: """help convert objects to a proper format for logging""" if isinstance(item, str): return repr(item) From 9e9c24758f77ca86f083d42fdf875173699c17e8 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 13:23:11 -0500 Subject: [PATCH 03/19] This line should not have changed. I must have accidentally hit reformat. --- tests/test_resolutions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 9f9a0cbc..94ea9003 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -123,7 +123,7 @@ def test_can_be_instantiated(self) -> None: def test_the_test(self) -> None: cim = ContainsItemMatching(r"([Ss]pam ?)+").resolve() - assert cim.matches(["Spam", "Eggs", "Spam and eggs"], ) + assert cim.matches(["Spam", "Eggs", "Spam and eggs"]) assert not cim.matches(["Porridge"]) def test_description(self) -> None: From 2f4b20a35e2e7f4aa332ee4d6e9f1598ce592e92 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 14:01:36 -0500 Subject: [PATCH 04/19] making adjustments based on feedback --- screenpy/resolutions/contains_the_item.py | 10 +++------- screenpy/resolutions/contains_the_key.py | 10 +++------- screenpy/resolutions/contains_the_text.py | 10 +++------- screenpy/resolutions/contains_the_value.py | 10 +++------- screenpy/resolutions/ends_with.py | 10 +++------- screenpy/resolutions/reads_exactly.py | 10 +++------- screenpy/resolutions/starts_with.py | 10 +++------- tests/test_resolutions.py | 2 +- 8 files changed, 22 insertions(+), 50 deletions(-) diff --git a/screenpy/resolutions/contains_the_item.py b/screenpy/resolutions/contains_the_item.py index 104f5e9a..2a0a8277 100644 --- a/screenpy/resolutions/contains_the_item.py +++ b/screenpy/resolutions/contains_the_item.py @@ -25,17 +25,13 @@ class ContainsTheItem: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"A sequence containing {tostring(self.item)}." + return f"A sequence containing {self.item_to_log}." - @property - def beatmsg(self) -> str: - """format string meant for beat msg""" - return f"... hoping it contains {tostring(self.item)}." - - @beat("{beatmsg}") + @beat("... hoping it contains {item_to_log}.") def resolve(self) -> Matcher[Sequence[T]]: """Produce the Matcher to make the assertion.""" return has_item(self.item) def __init__(self, item: T) -> None: self.item = item + self.item_to_log = tostring(item) diff --git a/screenpy/resolutions/contains_the_key.py b/screenpy/resolutions/contains_the_key.py index 34129a61..537a777b 100644 --- a/screenpy/resolutions/contains_the_key.py +++ b/screenpy/resolutions/contains_the_key.py @@ -23,17 +23,13 @@ class ContainsTheKey(Generic[K]): def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Containing the key {tostring(self.key)}." + return f"Containing the key {self.key_to_log}." - @property - def beatmsg(self) -> str: - """format string meant for beat msg""" - return f"... hoping it's a dict containing the key {tostring(self.key)}." - - @beat("{beatmsg}") + @beat("... hoping it's a dict containing the key {key_to_log}.") def resolve(self) -> Matcher[Mapping[K, Any]]: """Produce the Matcher to make the assertion.""" return has_key(self.key) def __init__(self, key: K) -> None: self.key = key + self.key_to_log = tostring(key) diff --git a/screenpy/resolutions/contains_the_text.py b/screenpy/resolutions/contains_the_text.py index 5dd236e1..18eb4e62 100644 --- a/screenpy/resolutions/contains_the_text.py +++ b/screenpy/resolutions/contains_the_text.py @@ -21,17 +21,13 @@ class ContainsTheText: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Containing the text {tostring(self.text)}." + return f"Containing the text {self.text_to_log}." - @property - def beatmsg(self) -> str: - """format string meant for beat msg""" - return f"... hoping it contains {tostring(self.text)}." - - @beat("{beatmsg}") + @beat("... hoping it contains {text_to_log}.") def resolve(self) -> Matcher[str]: """Produce the Matcher to make the assertion.""" return contains_string(self.text) def __init__(self, text: str) -> None: self.text = text + self.text_to_log = tostring(text) diff --git a/screenpy/resolutions/contains_the_value.py b/screenpy/resolutions/contains_the_value.py index d376c35f..9aedabb3 100644 --- a/screenpy/resolutions/contains_the_value.py +++ b/screenpy/resolutions/contains_the_value.py @@ -25,17 +25,13 @@ class ContainsTheValue(Generic[V]): def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Containing the value {tostring(self.value)}." + return f"Containing the value {self.value_to_log}." - @property - def beatmsg(self) -> str: - """format string meant for beat msg""" - return f"... hoping it contains the value {tostring(self.value)}." - - @beat("{beatmsg}") + @beat("... hoping it contains the value {value_to_log}.") def resolve(self) -> Matcher[Mapping[Any, V]]: """Produce the Matcher to form the assertion.""" return has_value(self.value) def __init__(self, value: V) -> None: self.value = value + self.value_to_log = tostring(value) diff --git a/screenpy/resolutions/ends_with.py b/screenpy/resolutions/ends_with.py index 894fc629..cf1c43e0 100644 --- a/screenpy/resolutions/ends_with.py +++ b/screenpy/resolutions/ends_with.py @@ -21,17 +21,13 @@ class EndsWith: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Ending with {tostring(self.postfix)}." + return f"Ending with {self.postfix_to_log}." - @property - def beatmsg(self) -> str: - """format string meant for beat msg""" - return f"... hoping it ends with {tostring(self.postfix)}." - - @beat("{beatmsg}") + @beat("... hoping it ends with {postfix_to_log}.") def resolve(self) -> Matcher[str]: """Produce the Matcher to make the assertion.""" return ends_with(self.postfix) def __init__(self, postfix: str) -> None: self.postfix = postfix + self.postfix_to_log = tostring(postfix) diff --git a/screenpy/resolutions/reads_exactly.py b/screenpy/resolutions/reads_exactly.py index 62f3080f..e411b950 100644 --- a/screenpy/resolutions/reads_exactly.py +++ b/screenpy/resolutions/reads_exactly.py @@ -21,17 +21,13 @@ class ReadsExactly: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"{tostring(self.text)}, verbatim." + return f"{self.text_to_log}, verbatim." - @property - def beatmsg(self) -> str: - """format string meant for beat msg""" - return f"... hoping it's {tostring(self.text)}, verbatim." - - @beat("{beatmsg}") + @beat("... hoping it's {text_to_log}, verbatim.") def resolve(self) -> Matcher[object]: """Produce the Matcher to make the assertion.""" return has_string(self.text) def __init__(self, text: str) -> None: self.text = text + self.text_to_log = tostring(text) diff --git a/screenpy/resolutions/starts_with.py b/screenpy/resolutions/starts_with.py index bbb6c499..e6c59726 100644 --- a/screenpy/resolutions/starts_with.py +++ b/screenpy/resolutions/starts_with.py @@ -21,17 +21,13 @@ class StartsWith: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Starting with {tostring(self.prefix)}." + return f"Starting with {self.prefix_to_log}." - @property - def beatmsg(self) -> str: - """format string meant for beat msg""" - return f"... hoping it starts with {tostring(self.prefix)}." - - @beat("{beatmsg}") + @beat("... hoping it starts with {prefix_to_log}.") def resolve(self) -> Matcher[str]: """Produce the Matcher to make the assertion.""" return starts_with(self.prefix) def __init__(self, prefix: str) -> None: self.prefix = prefix + self.prefix_to_log = tostring(prefix) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 94ea9003..3c7c6c88 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -165,7 +165,7 @@ def test_the_test(self) -> None: def test_description(self) -> None: test_entry = {"spam": "eggs"} - test_entries = {"tree": 1234, "spam": "eggs"} + test_entries = {"number": 1234, "spam": "eggs"} cte_single = ContainsTheEntry(**test_entry) cte_multiple = ContainsTheEntry(**test_entries) From 979f14cb8790133943514dd4f62e5b4da762e5b8 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 14:18:58 -0500 Subject: [PATCH 05/19] fixing ContainsTheEntry to use tostring --- screenpy/resolutions/contains_the_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screenpy/resolutions/contains_the_entry.py b/screenpy/resolutions/contains_the_entry.py index b0778a04..d1af643d 100644 --- a/screenpy/resolutions/contains_the_entry.py +++ b/screenpy/resolutions/contains_the_entry.py @@ -9,6 +9,7 @@ from screenpy.exceptions import UnableToFormResolution from screenpy.pacing import beat +from screenpy.speech_tools import tostring K = TypeVar("K", bound=Hashable) V = TypeVar("V") @@ -79,5 +80,5 @@ def __init__(self, *kv_args: Any, **kv_kwargs: Any) -> None: self.entries = dict(pairs, **kv_kwargs) self.entry_plural = "entries" if len(self.entries) != 1 else "entry" self.entries_to_log = ", ".join( - f"{k!r}->{v!r}" for k, v in self.entries.items() + f"{tostring(k)}->{tostring(v)}" for k, v in self.entries.items() ) From 530f25d73a8749799f48ca6ad3a988f5056d46bb Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 14:19:35 -0500 Subject: [PATCH 06/19] using explicit strings in tests is better. --- tests/test_resolutions.py | 27 +++++++++++++-------------- tests/test_speech_tools.py | 2 +- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 3c7c6c88..54028119 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -132,7 +132,7 @@ def test_description(self) -> None: cim = ContainsItemMatching(test_pattern) expected_description = ( - f'A sequence with an item matching the pattern r"{test_pattern}".' + 'A sequence with an item matching the pattern r".*".' ) assert cim.describe() == expected_description @@ -172,8 +172,7 @@ def test_description(self) -> None: expected_description_single = "A mapping with the entry 'spam'->'eggs'." expected_description_multiple = ( - "A mapping with the entries" - f" {', '.join(f'{k!r}->{v!r}' for k, v in test_entries.items())}." + "A mapping with the entries 'number'->1234, 'spam'->'eggs'." ) assert cte_single.describe() == expected_description_single assert cte_multiple.describe() == expected_description_multiple @@ -197,7 +196,7 @@ def test_description(self) -> None: cti = ContainsTheItem(test_item) - expected_description = f'A sequence containing {test_item}.' + expected_description = "A sequence containing 1." assert cti.describe() == expected_description def test_description_str(self) -> None: @@ -205,7 +204,7 @@ def test_description_str(self) -> None: cti = ContainsTheItem(test_item) - expected_description = f'A sequence containing {test_item!r}.' + expected_description = "A sequence containing '1'." assert cti.describe() == expected_description @@ -228,7 +227,7 @@ def test_description(self) -> None: ctk = ContainsTheKey(test_key) - expected_description = f'Containing the key {test_key!r}.' + expected_description = "Containing the key 'spam'." assert ctk.describe() == expected_description @@ -250,7 +249,7 @@ def test_description(self) -> None: ctt = ContainsTheText(test_text) - expected_description = f'Containing the text {test_text!r}.' + expected_description = "Containing the text 'Wenn ist das Nunstück git und Slotermeyer?'." assert ctt.describe() == expected_description @@ -273,7 +272,7 @@ def test_description(self) -> None: ctv = ContainsTheValue(test_value) - expected_description = f'Containing the value {test_value}.' + expected_description = "Containing the value 42." assert ctv.describe() == expected_description def test_description_str(self) -> None: @@ -281,7 +280,7 @@ def test_description_str(self) -> None: ctv = ContainsTheValue(test_value) - expected_description = f'Containing the value {test_value!r}.' + expected_description = "Containing the value '42'." assert ctv.describe() == expected_description @@ -321,7 +320,7 @@ def test_description(self) -> None: ew = EndsWith(test_postfix) - expected_description = f'Ending with {test_postfix!r}.' + expected_description = "Ending with 'got better.'." assert ew.describe() == expected_description @@ -345,7 +344,7 @@ def test_description(self) -> None: hl5 = HasLength(test_length) expected_description1 = "1 item long." - expected_description5 = f"{test_length} items long." + expected_description5 = "5 items long." assert hl1.describe() == expected_description1 assert hl5.describe() == expected_description5 @@ -578,7 +577,7 @@ def test_description(self) -> None: m = Matches(test_match) - expected_description = f'Text matching the pattern r"{test_match}".' + expected_description = 'Text matching the pattern r"(spam)+".' assert m.describe() == expected_description @@ -600,7 +599,7 @@ def test_description(self) -> None: re_ = ReadsExactly(test_text) - expected_description = f'{test_text!r}, verbatim.' + expected_description = "'I will not buy this record, it is scratched.', verbatim." assert re_.describe() == expected_description @@ -621,5 +620,5 @@ def test_description(self) -> None: sw = StartsWith(test_prefix) - expected_description = f'Starting with {test_prefix!r}.' + expected_description = "Starting with 'It was the best of times,'." assert sw.describe() == expected_description diff --git a/tests/test_speech_tools.py b/tests/test_speech_tools.py index 4351f29d..1ac816ed 100644 --- a/tests/test_speech_tools.py +++ b/tests/test_speech_tools.py @@ -58,7 +58,7 @@ class TestTostring: def test_str(self): val = "hello\nworld!" - assert tostring(val) == f"{val!r}" + assert tostring(val) == "'hello\\nworld!'" def test_int(self): val = 1234 From 9141a57591d47a364fa41b9b8176ff1da6e5c1c9 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 14:52:24 -0500 Subject: [PATCH 07/19] renaming function to represent_prop --- screenpy/actions/see.py | 4 ++-- screenpy/pacing.py | 4 ++-- screenpy/resolutions/contains_the_entry.py | 4 ++-- screenpy/resolutions/contains_the_item.py | 4 ++-- screenpy/resolutions/contains_the_key.py | 4 ++-- screenpy/resolutions/contains_the_text.py | 4 ++-- screenpy/resolutions/contains_the_value.py | 4 ++-- screenpy/resolutions/ends_with.py | 4 ++-- screenpy/resolutions/reads_exactly.py | 4 ++-- screenpy/resolutions/starts_with.py | 4 ++-- screenpy/speech_tools.py | 8 ++++---- tests/test_speech_tools.py | 6 +++--- 12 files changed, 27 insertions(+), 27 deletions(-) diff --git a/screenpy/actions/see.py b/screenpy/actions/see.py index ef0349dd..ba03e4ed 100644 --- a/screenpy/actions/see.py +++ b/screenpy/actions/see.py @@ -9,7 +9,7 @@ from screenpy.actor import Actor from screenpy.pacing import aside, beat from screenpy.protocols import Answerable, ErrorKeeper, Resolvable -from screenpy.speech_tools import get_additive_description, tostring +from screenpy.speech_tools import get_additive_description, represent_prop SelfSee = TypeVar("SelfSee", bound="See") T_Q = Union[Answerable, object] @@ -56,7 +56,7 @@ def perform_as(self: SelfSee, the_actor: Actor) -> None: else: # must be a value instead of a question! value = self.question - aside(f"the actual value is: {tostring(value)}") + aside(f"the actual value is: {represent_prop(value)}") reason = "" if isinstance(self.question, ErrorKeeper): diff --git a/screenpy/pacing.py b/screenpy/pacing.py index 6d454065..cdc585fc 100644 --- a/screenpy/pacing.py +++ b/screenpy/pacing.py @@ -9,7 +9,7 @@ from typing import Any, Callable, Optional from screenpy.narration import Narrator, StdOutAdapter -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop Function = Callable[..., Any] the_narrator: Narrator = Narrator(adapters=[StdOutAdapter()]) @@ -89,7 +89,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: with the_narrator.stating_a_beat(func, completed_line, gravitas) as n_func: retval = n_func(*args, **kwargs) if retval is not None: - aside(f"=> {tostring(retval)}") + aside(f"=> {represent_prop(retval)}") return retval return wrapper diff --git a/screenpy/resolutions/contains_the_entry.py b/screenpy/resolutions/contains_the_entry.py index d1af643d..7fac3ade 100644 --- a/screenpy/resolutions/contains_the_entry.py +++ b/screenpy/resolutions/contains_the_entry.py @@ -9,7 +9,7 @@ from screenpy.exceptions import UnableToFormResolution from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop K = TypeVar("K", bound=Hashable) V = TypeVar("V") @@ -80,5 +80,5 @@ def __init__(self, *kv_args: Any, **kv_kwargs: Any) -> None: self.entries = dict(pairs, **kv_kwargs) self.entry_plural = "entries" if len(self.entries) != 1 else "entry" self.entries_to_log = ", ".join( - f"{tostring(k)}->{tostring(v)}" for k, v in self.entries.items() + f"{represent_prop(k)}->{represent_prop(v)}" for k, v in self.entries.items() ) diff --git a/screenpy/resolutions/contains_the_item.py b/screenpy/resolutions/contains_the_item.py index 2a0a8277..29362b4b 100644 --- a/screenpy/resolutions/contains_the_item.py +++ b/screenpy/resolutions/contains_the_item.py @@ -8,7 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop T = TypeVar("T") @@ -34,4 +34,4 @@ def resolve(self) -> Matcher[Sequence[T]]: def __init__(self, item: T) -> None: self.item = item - self.item_to_log = tostring(item) + self.item_to_log = represent_prop(item) diff --git a/screenpy/resolutions/contains_the_key.py b/screenpy/resolutions/contains_the_key.py index 537a777b..946e59c4 100644 --- a/screenpy/resolutions/contains_the_key.py +++ b/screenpy/resolutions/contains_the_key.py @@ -8,7 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop K = TypeVar("K", bound=Hashable) @@ -32,4 +32,4 @@ def resolve(self) -> Matcher[Mapping[K, Any]]: def __init__(self, key: K) -> None: self.key = key - self.key_to_log = tostring(key) + self.key_to_log = represent_prop(key) diff --git a/screenpy/resolutions/contains_the_text.py b/screenpy/resolutions/contains_the_text.py index 18eb4e62..6114ba42 100644 --- a/screenpy/resolutions/contains_the_text.py +++ b/screenpy/resolutions/contains_the_text.py @@ -6,7 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop class ContainsTheText: @@ -30,4 +30,4 @@ def resolve(self) -> Matcher[str]: def __init__(self, text: str) -> None: self.text = text - self.text_to_log = tostring(text) + self.text_to_log = represent_prop(text) diff --git a/screenpy/resolutions/contains_the_value.py b/screenpy/resolutions/contains_the_value.py index 9aedabb3..909dbc94 100644 --- a/screenpy/resolutions/contains_the_value.py +++ b/screenpy/resolutions/contains_the_value.py @@ -8,7 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop V = TypeVar("V") @@ -34,4 +34,4 @@ def resolve(self) -> Matcher[Mapping[Any, V]]: def __init__(self, value: V) -> None: self.value = value - self.value_to_log = tostring(value) + self.value_to_log = represent_prop(value) diff --git a/screenpy/resolutions/ends_with.py b/screenpy/resolutions/ends_with.py index cf1c43e0..e865df60 100644 --- a/screenpy/resolutions/ends_with.py +++ b/screenpy/resolutions/ends_with.py @@ -6,7 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop class EndsWith: @@ -30,4 +30,4 @@ def resolve(self) -> Matcher[str]: def __init__(self, postfix: str) -> None: self.postfix = postfix - self.postfix_to_log = tostring(postfix) + self.postfix_to_log = represent_prop(postfix) diff --git a/screenpy/resolutions/reads_exactly.py b/screenpy/resolutions/reads_exactly.py index e411b950..fe2bf377 100644 --- a/screenpy/resolutions/reads_exactly.py +++ b/screenpy/resolutions/reads_exactly.py @@ -6,7 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop class ReadsExactly: @@ -30,4 +30,4 @@ def resolve(self) -> Matcher[object]: def __init__(self, text: str) -> None: self.text = text - self.text_to_log = tostring(text) + self.text_to_log = represent_prop(text) diff --git a/screenpy/resolutions/starts_with.py b/screenpy/resolutions/starts_with.py index e6c59726..b57960a0 100644 --- a/screenpy/resolutions/starts_with.py +++ b/screenpy/resolutions/starts_with.py @@ -6,7 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat -from screenpy.speech_tools import tostring +from screenpy.speech_tools import represent_prop class StartsWith: @@ -30,4 +30,4 @@ def resolve(self) -> Matcher[str]: def __init__(self, prefix: str) -> None: self.prefix = prefix - self.prefix_to_log = tostring(prefix) + self.prefix_to_log = represent_prop(prefix) diff --git a/screenpy/speech_tools.py b/screenpy/speech_tools.py index 6c12d905..d9a74310 100644 --- a/screenpy/speech_tools.py +++ b/screenpy/speech_tools.py @@ -49,17 +49,17 @@ class name: replace each capital letter with a space and a lower-case @overload -def tostring(item: str) -> str: +def represent_prop(item: str) -> str: ... @overload -def tostring(item: T) -> T: +def represent_prop(item: T) -> T: ... -def tostring(item: Union[str, T]) -> Union[str, T]: - """help convert objects to a proper format for logging""" +def represent_prop(item: Union[str, T]) -> Union[str, T]: + """represent items in a manner suitable for the audience (logging)""" if isinstance(item, str): return repr(item) return item diff --git a/tests/test_speech_tools.py b/tests/test_speech_tools.py index 1ac816ed..ba90e73b 100644 --- a/tests/test_speech_tools.py +++ b/tests/test_speech_tools.py @@ -1,6 +1,6 @@ import pytest -from screenpy.speech_tools import get_additive_description, tostring +from screenpy.speech_tools import get_additive_description, represent_prop class ThisIsADescribableWithADescribe: @@ -58,9 +58,9 @@ class TestTostring: def test_str(self): val = "hello\nworld!" - assert tostring(val) == "'hello\\nworld!'" + assert represent_prop(val) == "'hello\\nworld!'" def test_int(self): val = 1234 - assert tostring(val) == val + assert represent_prop(val) == val From f8c2b47e095f750456773d19ba2d686cb692c7b2 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 15:09:49 -0500 Subject: [PATCH 08/19] Missed a case --- screenpy/resolutions/is_equal_to.py | 6 ++++-- tests/test_resolutions.py | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/screenpy/resolutions/is_equal_to.py b/screenpy/resolutions/is_equal_to.py index e83e6a37..72c8aa0b 100644 --- a/screenpy/resolutions/is_equal_to.py +++ b/screenpy/resolutions/is_equal_to.py @@ -8,6 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import represent_prop class IsEqualTo: @@ -22,12 +23,13 @@ class IsEqualTo: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Equal to {self.expected}." + return f"Equal to {self.expected_to_log}." - @beat("... hoping it's equal to {expected}.") + @beat("... hoping it's equal to {expected_to_log}.") def resolve(self) -> Matcher[Any]: """Produce the Matcher to make the assertion.""" return equal_to(self.expected) def __init__(self, obj: Any) -> None: self.expected = obj + self.expected_to_log = represent_prop(obj) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 54028119..c165226e 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -387,11 +387,19 @@ def test_the_test(self) -> None: assert not ie.matches(2) def test_description(self) -> None: + test_object = 8675 + + ie = IsEqualTo(test_object) + + expected_description = "Equal to 8675." + assert ie.describe() == expected_description + + def test_description_str(self) -> None: test_object = "my Schwartz" ie = IsEqualTo(test_object) - expected_description = f"Equal to {test_object}." + expected_description = "Equal to 'my Schwartz'." assert ie.describe() == expected_description From ccaa00504790c134edb8afed8c3df1871685f9d8 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 15:10:29 -0500 Subject: [PATCH 09/19] rename test to match the function tested --- tests/test_speech_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_speech_tools.py b/tests/test_speech_tools.py index ba90e73b..ba2067d1 100644 --- a/tests/test_speech_tools.py +++ b/tests/test_speech_tools.py @@ -54,7 +54,7 @@ def test_indescribable(self) -> None: assert description == "something indescribable" -class TestTostring: +class TestRepresentProp: def test_str(self): val = "hello\nworld!" From 21f436eef8d161bbf870c2dfe49ec95fcdb26b91 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 15:43:13 -0500 Subject: [PATCH 10/19] represent all other objects with angle brackets (like hamcrest does) --- screenpy/speech_tools.py | 12 +++++++++++- tests/test_actions.py | 12 ++++++------ tests/test_resolutions.py | 12 ++++++------ tests/test_speech_tools.py | 2 +- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/screenpy/speech_tools.py b/screenpy/speech_tools.py index d9a74310..d3352760 100644 --- a/screenpy/speech_tools.py +++ b/screenpy/speech_tools.py @@ -5,6 +5,9 @@ import re from typing import TypeVar, Union, overload +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.ismock import ismock + from screenpy.protocols import Answerable, Describable, Performable, Resolvable T = TypeVar("T") @@ -60,6 +63,13 @@ def represent_prop(item: T) -> T: def represent_prop(item: Union[str, T]) -> Union[str, T]: """represent items in a manner suitable for the audience (logging)""" + if not ismock(item) and hasmethod(item, "describe_to"): + return f"{item}" if isinstance(item, str): return repr(item) - return item + + description = str(item) + if description[:1] == "<" and description[-1:] == ">": + return item + + return f"<{item}>" diff --git a/tests/test_actions.py b/tests/test_actions.py index 56ef0b8e..6ab91c48 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -917,10 +917,10 @@ def test_unabridged_set_outside_silently(self, Tester, caplog) -> None: assert [r.msg for r in caplog.records] == [ "Tester tries to Action2", - " Tester sees if simpleQuestion is equal to True.", + " Tester sees if simpleQuestion is equal to .", " Tester examines SimpleQuestion", - " => True", - " ... hoping it's equal to True.", + " => ", + " ... hoping it's equal to .", " => ", ] @@ -956,10 +956,10 @@ def perform_as(self, the_actor: Actor) -> None: "Tester tries to Action3", # you'd think this wouldn't be here! " Tester tries to Action1", " Tester tries to Action2", - " Tester sees if simpleQuestion is equal to True.", + " Tester sees if simpleQuestion is equal to .", " Tester examines SimpleQuestion", - " => True", - " ... hoping it's equal to True.", + " => ", + " ... hoping it's equal to .", " => ", ] diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index c165226e..033c4142 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -150,10 +150,10 @@ def test_the_test(self) -> None: cte_single = ContainsTheEntry(key="value").resolve() cte_multiple = ContainsTheEntry(key1="value1", key2="value2").resolve() cte_alt2 = ContainsTheEntry({"key2": 12345}).resolve() - cte_alt3 = ContainsTheEntry("key3", "something3").resolve() + cte_alt3 = ContainsTheEntry("key3", False).resolve() assert cte_alt2.matches({"key2": 12345}) - assert cte_alt3.matches({"key3": "something3"}) + assert cte_alt3.matches({"key3": False}) assert cte_single.matches({"key": "value"}) assert cte_single.matches({"key": "value", "play": "Hamlet"}) assert not cte_single.matches({"play": "Hamlet"}) @@ -172,7 +172,7 @@ def test_description(self) -> None: expected_description_single = "A mapping with the entry 'spam'->'eggs'." expected_description_multiple = ( - "A mapping with the entries 'number'->1234, 'spam'->'eggs'." + "A mapping with the entries 'number'-><1234>, 'spam'->'eggs'." ) assert cte_single.describe() == expected_description_single assert cte_multiple.describe() == expected_description_multiple @@ -196,7 +196,7 @@ def test_description(self) -> None: cti = ContainsTheItem(test_item) - expected_description = "A sequence containing 1." + expected_description = "A sequence containing <1>." assert cti.describe() == expected_description def test_description_str(self) -> None: @@ -272,7 +272,7 @@ def test_description(self) -> None: ctv = ContainsTheValue(test_value) - expected_description = "Containing the value 42." + expected_description = "Containing the value <42>." assert ctv.describe() == expected_description def test_description_str(self) -> None: @@ -391,7 +391,7 @@ def test_description(self) -> None: ie = IsEqualTo(test_object) - expected_description = "Equal to 8675." + expected_description = "Equal to <8675>." assert ie.describe() == expected_description def test_description_str(self) -> None: diff --git a/tests/test_speech_tools.py b/tests/test_speech_tools.py index ba2067d1..5963aa08 100644 --- a/tests/test_speech_tools.py +++ b/tests/test_speech_tools.py @@ -63,4 +63,4 @@ def test_str(self): def test_int(self): val = 1234 - assert represent_prop(val) == val + assert represent_prop(val) == "<1234>" From d6d905211b928b6dc17d2a94387b5b1be13fd367 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 17:44:24 -0500 Subject: [PATCH 11/19] adding case for MakeNote --- screenpy/actions/make_note.py | 6 ++++-- tests/test_actions.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/screenpy/actions/make_note.py b/screenpy/actions/make_note.py index a1eac100..617afc7f 100644 --- a/screenpy/actions/make_note.py +++ b/screenpy/actions/make_note.py @@ -9,6 +9,7 @@ from screenpy.exceptions import UnableToAct from screenpy.pacing import aside, beat from screenpy.protocols import Answerable, ErrorKeeper +from screenpy.speech_tools import represent_prop SelfMakeNote = TypeVar("SelfMakeNote", bound="MakeNote") T_Q = Union[Answerable, object] @@ -54,9 +55,9 @@ def as_(self: SelfMakeNote, key: str) -> SelfMakeNote: def describe(self: SelfMakeNote) -> str: """Describe the Action in present tense.""" - return f"Make a note under {self.key}." + return f"Make a note under {represent_prop(self.key)}." - @beat('{} jots something down under "{key}".') + @beat("{} jots something down under {key_to_log}.") def perform_as(self: SelfMakeNote, the_actor: Actor) -> None: """Direct the Actor to take a note.""" if self.key is None: @@ -81,3 +82,4 @@ def __init__( ) -> None: self.question = question self.key = key + self.key_to_log = represent_prop(key) diff --git a/tests/test_actions.py b/tests/test_actions.py index 6ab91c48..1cfe83e9 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -417,7 +417,7 @@ def test_using_note_immediately_raises_with_docs(self, Tester) -> None: assert "screenpy-docs.readthedocs.io" in str(exc.value) def test_describe(self) -> None: - assert MakeNote(None).as_("blah").describe() == "Make a note under blah." + assert MakeNote(None).as_("blah").describe() == "Make a note under 'blah'." @mock.patch("screenpy.actions.make_note.aside", autospec=True) def test_caught_exception_noted(self, mock_aside: mock.Mock, Tester) -> None: From db06f322b4d3cf64074469f0439dd0ec7da9cac1 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 24 Aug 2023 18:40:06 -0500 Subject: [PATCH 12/19] additional coverage of logging --- tests/test_actions.py | 57 ++++++++++++++++++++++++++++++++++++++++--- tests/useful_mocks.py | 6 ++--- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/tests/test_actions.py b/tests/test_actions.py index 1cfe83e9..b805deb0 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -610,9 +610,11 @@ def test_raises_assertionerror_if_one_fails(self, Tester) -> None: (FakeQuestion(), IsEqualTo(True)), ).perform_as(Tester) - def test_stops_at_first_failure(self, Tester) -> None: + def test_stops_at_first_failure(self, Tester, caplog) -> None: mock_question = FakeQuestion() + caplog.set_level(logging.INFO) + with pytest.raises(AssertionError): SeeAllOf( (mock_question, IsEqualTo(True)), @@ -622,8 +624,24 @@ def test_stops_at_first_failure(self, Tester) -> None: ).perform_as(Tester) assert mock_question.answered_by.call_count == 2 + assert [r.msg for r in caplog.records] == [ + "Tester sees if all of 4 tests pass:", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + # don't be fooled! the next few lines do not have commas on purpose + " ***ERROR***\n" + "\n" + "AssertionError: \n" + "Expected: \n" + " but: was \n", + ] - def test_passes_if_all_pass(self, Tester) -> None: + def test_passes_if_all_pass(self, Tester, caplog) -> None: + caplog.set_level(logging.INFO) # test passes if no exception is raised SeeAllOf( (FakeQuestion(), IsEqualTo(True)), @@ -632,6 +650,22 @@ def test_passes_if_all_pass(self, Tester) -> None: (FakeQuestion(), IsEqualTo(True)), ).perform_as(Tester) + assert [r.msg for r in caplog.records] == [ + "Tester sees if all of 4 tests pass:", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + ] + def test_describe(self) -> None: test = (FakeQuestion(), IsEqualTo(True)) tests = ( @@ -697,9 +731,11 @@ def test_raises_assertionerror_if_none_pass(self, Tester) -> None: assert "did not find any expected answers" in str(actual_exception) - def test_stops_at_first_pass(self, Tester) -> None: + def test_stops_at_first_pass(self, Tester, caplog) -> None: mock_question = FakeQuestion() + caplog.set_level(logging.INFO) + SeeAnyOf( (mock_question, IsEqualTo(False)), (mock_question, IsEqualTo(True)), # <-- @@ -708,6 +744,21 @@ def test_stops_at_first_pass(self, Tester) -> None: ).perform_as(Tester) assert mock_question.answered_by.call_count == 2 + assert [r.msg for r in caplog.records] == [ + "Tester sees if any of 4 tests pass:", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + # don't be fooled! the next few lines do not have commas on purpose + " ***ERROR***\n" + "\n" + "AssertionError: \n" + "Expected: \n" + " but: was \n", + " Tester sees if fakeQuestion is equal to .", + " ... hoping it's equal to .", + " => ", + ] def test_passes_with_one_pass(self, Tester) -> None: # test passes if no exception is raised diff --git a/tests/useful_mocks.py b/tests/useful_mocks.py index d1f6f328..03f66ecc 100644 --- a/tests/useful_mocks.py +++ b/tests/useful_mocks.py @@ -8,7 +8,7 @@ def get_mock_action_class() -> Any: class FakeAction(Action): def __new__(cls, *args, **kwargs): rt = mock.create_autospec(FakeAction, instance=True) - rt.describe.return_value = None + rt.describe.return_value = "FakeAction" return rt return FakeAction @@ -18,7 +18,7 @@ def get_mock_question_class() -> Any: class FakeQuestion(Question): def __new__(cls, *args, **kwargs): rt = mock.create_autospec(FakeQuestion, instance=True) - rt.describe.return_value = None + rt.describe.return_value = "FakeQuestion" rt.answered_by.return_value = True return rt @@ -30,7 +30,7 @@ class FakeResolution(Resolution): def __new__(cls, *args, **kwargs): rt = mock.create_autospec(FakeResolution, instance=True) rt.resolve.return_value = rt - rt.describe.return_value = None + rt.describe.return_value = "FakeResolution" return rt return FakeResolution From 7bdce54d70d4ddfc5434fa57f1f96cee5837e6db Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 21 Sep 2023 20:25:45 -0500 Subject: [PATCH 13/19] test formatting --- tests/test_resolutions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 033c4142..c0ded1c3 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -123,6 +123,7 @@ def test_can_be_instantiated(self) -> None: def test_the_test(self) -> None: cim = ContainsItemMatching(r"([Ss]pam ?)+").resolve() + assert cim.matches(["Spam", "Eggs", "Spam and eggs"]) assert not cim.matches(["Porridge"]) From 9ddf365ea9c819b31b44deeaa347cb5b3877b2d7 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Mon, 25 Sep 2023 12:32:56 -0500 Subject: [PATCH 14/19] adding logging tests for `See` --- tests/test_actions.py | 52 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/tests/test_actions.py b/tests/test_actions.py index b805deb0..60ef1960 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -134,7 +134,6 @@ def test_describe(self) -> None: class TestEventually: - settings_path = "screenpy.actions.eventually.settings" def test_can_be_instantiated(self) -> None: @@ -560,6 +559,36 @@ def test_describe(self) -> None: == "See if can you speak is only this sentence." ) + def test_log_passes(self, Tester, caplog) -> None: + with caplog.at_level(logging.INFO): + See(SimpleQuestion(), IsEqualTo(True)).perform_as(Tester) + + assert [r.msg for r in caplog.records] == [ + "Tester sees if simpleQuestion is equal to .", + " Tester examines SimpleQuestion", + " => ", + " ... hoping it's equal to .", + " => ", + ] + + def test_log_fails(self, Tester, caplog) -> None: + with caplog.at_level(logging.INFO), pytest.raises(AssertionError): + See(SimpleQuestion(), IsEqualTo(False)).perform_as(Tester) + + assert [r.msg for r in caplog.records] == [ + "Tester sees if simpleQuestion is equal to .", + " Tester examines SimpleQuestion", + " => ", + " ... hoping it's equal to .", + " => ", + # don't be fooled! the next few lines do not have commas on purpose + " ***ERROR***\n" + "\n" + "AssertionError: \n" + "Expected: \n" + " but: was \n", + ] + class TestSeeAllOf: def test_can_be_instantiated(self) -> None: @@ -988,6 +1017,7 @@ class Action3: The results of this test show the strange behavior. """ + @beat("{} tries to Action3") def perform_as(self, the_actor: Actor) -> None: settings.UNABRIDGED_NARRATION = True @@ -1027,6 +1057,7 @@ class Action4: The results of this test show the strange behavior. """ + @beat("{} tries to Action4") def perform_as(self, the_actor: Actor) -> None: settings.UNABRIDGED_NARRATION = True @@ -1068,9 +1099,9 @@ def test_describe(self) -> None: mock_action2 = FakeAction() mock_action2.describe.return_value = "produce stuff!" - + t = Either(mock_action1).or_(mock_action2) - assert (t.describe() == "Either do thing or produce stuff") + assert t.describe() == "Either do thing or produce stuff" def test_multi_action_describe(self) -> None: mock_action1 = FakeAction() @@ -1083,13 +1114,13 @@ def test_multi_action_describe(self) -> None: mock_action4.describe.return_value = "PerformBar." t = Either(mock_action1, mock_action2).or_(mock_action3, mock_action4) - assert (t.describe() == "Either doThing, doStuff or performFoo, performBar") + assert t.describe() == "Either doThing, doStuff or performFoo, performBar" def test_first_action_passes(self, Tester, mocker: MockerFixture) -> None: mock_clear = mocker.spy(the_narrator, "clear_backup") mock_flush = mocker.spy(the_narrator, "flush_backup") mock_kink = mocker.spy(the_narrator, "mic_cable_kinked") - + action1 = FakeAction() action2 = FakeAction() Either(action1).or_(action2).perform_as(Tester) @@ -1104,12 +1135,12 @@ def test_first_action_fails(self, Tester, mocker: MockerFixture) -> None: mock_clear = mocker.spy(the_narrator, "clear_backup") mock_flush = mocker.spy(the_narrator, "flush_backup") mock_kink = mocker.spy(the_narrator, "mic_cable_kinked") - + exc = AssertionError("Wrong!") action1 = FakeAction() action2 = FakeAction() action1.perform_as.side_effect = exc - + Either(action1).or_(action2).perform_as(Tester) assert action1.perform_as.call_count == 1 @@ -1118,7 +1149,9 @@ def test_first_action_fails(self, Tester, mocker: MockerFixture) -> None: assert mock_clear.call_count == 2 assert mock_flush.call_count == 1 - def test_first_action_fails_with_custom_exception(self, Tester, mocker: MockerFixture) -> None: + def test_first_action_fails_with_custom_exception( + self, Tester, mocker: MockerFixture + ) -> None: mock_clear = mocker.spy(the_narrator, "clear_backup") mock_flush = mocker.spy(the_narrator, "flush_backup") mock_kink = mocker.spy(the_narrator, "mic_cable_kinked") @@ -1140,7 +1173,6 @@ class CustomException(Exception): assert mock_flush.call_count == 1 def test_output_first_fails(self, Tester, caplog): - class FakeActionFail(Performable): @beat("{} tries to FakeActionFail") def perform_as(self, actor: Actor): @@ -1169,7 +1201,7 @@ def perform_as(self, actor: Actor): caplog.set_level(logging.INFO) mock_settings = ScreenPySettings(UNABRIDGED_NARRATION=True) - + with mock.patch(self.settings_path, mock_settings): Either(FakeActionFail()).or_(FakeActionPass()).perform_as(Tester) From 8ff51bb7000eb0d112ef829e6746384f9f6c46c6 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Mon, 25 Sep 2023 12:59:57 -0500 Subject: [PATCH 15/19] Updating tests based on PR comments --- tests/test_resolutions.py | 57 +++++++++++---------------------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index c0ded1c3..2831c3fd 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -192,21 +192,12 @@ def test_the_test(self) -> None: assert cti.matches(range(0, 10)) assert not cti.matches({0, 3, 5}) - def test_description(self) -> None: - test_item = 1 - - cti = ContainsTheItem(test_item) - - expected_description = "A sequence containing <1>." - assert cti.describe() == expected_description - - def test_description_str(self) -> None: - test_item = "1" + def test_description_uses_represent_prop(self) -> None: + cti_int = ContainsTheItem(1) + cti_str = ContainsTheItem("1") - cti = ContainsTheItem(test_item) - - expected_description = "A sequence containing '1'." - assert cti.describe() == expected_description + assert cti_int.describe() == "A sequence containing <1>." + assert cti_str.describe() == "A sequence containing '1'." class TestContainsTheKey: @@ -268,21 +259,12 @@ def test_the_test(self) -> None: assert ctv.matches({"key": "value", "play": "Hamlet"}) assert not ctv.matches({"play": "Hamlet"}) - def test_description(self) -> None: - test_value = 42 - - ctv = ContainsTheValue(test_value) - - expected_description = "Containing the value <42>." - assert ctv.describe() == expected_description + def test_description_uses_represent_prop(self) -> None: + ctv_int = ContainsTheValue(42) + ctv_str = ContainsTheValue("42") - def test_description_str(self) -> None: - test_value = "42" - - ctv = ContainsTheValue(test_value) - - expected_description = "Containing the value '42'." - assert ctv.describe() == expected_description + assert ctv_int.describe() == "Containing the value <42>." + assert ctv_str.describe() == "Containing the value '42'." class TestEmpty: @@ -387,21 +369,12 @@ def test_the_test(self) -> None: assert ie.matches(1) assert not ie.matches(2) - def test_description(self) -> None: - test_object = 8675 - - ie = IsEqualTo(test_object) - - expected_description = "Equal to <8675>." - assert ie.describe() == expected_description - - def test_description_str(self) -> None: - test_object = "my Schwartz" - - ie = IsEqualTo(test_object) + def test_description_uses_represent_prop(self) -> None: + ie_int = IsEqualTo(8675) + ie_str = IsEqualTo("8675") - expected_description = "Equal to 'my Schwartz'." - assert ie.describe() == expected_description + assert ie_int.describe() == "Equal to <8675>." + assert ie_str.describe() == "Equal to '8675'." class TestIsGreaterThan: From 83f5281711e3d0c04023d30fcf9cdfc398ed5dc6 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Mon, 25 Sep 2023 13:05:22 -0500 Subject: [PATCH 16/19] Missed some cases --- screenpy/resolutions/is_greater_than.py | 6 ++++-- screenpy/resolutions/is_greater_than_or_equal_to.py | 6 ++++-- screenpy/resolutions/is_less_than.py | 6 ++++-- screenpy/resolutions/is_less_than_or_equal_to.py | 6 ++++-- tests/test_resolutions.py | 8 ++++---- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/screenpy/resolutions/is_greater_than.py b/screenpy/resolutions/is_greater_than.py index 9b4a396f..8d669ad3 100644 --- a/screenpy/resolutions/is_greater_than.py +++ b/screenpy/resolutions/is_greater_than.py @@ -8,6 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import represent_prop class IsGreaterThan: @@ -20,12 +21,13 @@ class IsGreaterThan: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Greater than {self.number}." + return f"Greater than {self.number_to_log}." - @beat("... hoping it's greater than {number}.") + @beat("... hoping it's greater than {number_to_log}.") def resolve(self) -> Matcher[Any]: """Produce the Matcher to make the assertion.""" return greater_than(self.number) def __init__(self, number: float) -> None: self.number = number + self.number_to_log = represent_prop(number) diff --git a/screenpy/resolutions/is_greater_than_or_equal_to.py b/screenpy/resolutions/is_greater_than_or_equal_to.py index 533dbf05..13555f9d 100644 --- a/screenpy/resolutions/is_greater_than_or_equal_to.py +++ b/screenpy/resolutions/is_greater_than_or_equal_to.py @@ -8,6 +8,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import represent_prop class IsGreaterThanOrEqualTo: @@ -22,12 +23,13 @@ class IsGreaterThanOrEqualTo: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Greater than or equal to {self.number}." + return f"Greater than or equal to {self.number_to_log}." - @beat("... hoping it's greater than or equal to {number}.") + @beat("... hoping it's greater than or equal to {number_to_log}.") def resolve(self) -> Matcher[Any]: """Produce the Matcher to make the assertion.""" return greater_than_or_equal_to(self.number) def __init__(self, number: float) -> None: self.number = number + self.number_to_log = represent_prop(number) diff --git a/screenpy/resolutions/is_less_than.py b/screenpy/resolutions/is_less_than.py index 1bf5c058..9a5bd7c1 100644 --- a/screenpy/resolutions/is_less_than.py +++ b/screenpy/resolutions/is_less_than.py @@ -6,6 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import represent_prop class IsLessThan: @@ -20,12 +21,13 @@ class IsLessThan: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Less than {self.number}." + return f"Less than {self.number_to_log}." - @beat("... hoping it's less than {number}.") + @beat("... hoping it's less than {number_to_log}.") def resolve(self) -> Matcher[float]: """Produce the Matcher to make the assertion.""" return less_than(self.number) def __init__(self, number: float) -> None: self.number = number + self.number_to_log = represent_prop(number) diff --git a/screenpy/resolutions/is_less_than_or_equal_to.py b/screenpy/resolutions/is_less_than_or_equal_to.py index 37951f55..d58bde82 100644 --- a/screenpy/resolutions/is_less_than_or_equal_to.py +++ b/screenpy/resolutions/is_less_than_or_equal_to.py @@ -6,6 +6,7 @@ from hamcrest.core.matcher import Matcher from screenpy.pacing import beat +from screenpy.speech_tools import represent_prop class IsLessThanOrEqualTo: @@ -20,12 +21,13 @@ class IsLessThanOrEqualTo: def describe(self) -> str: """Describe the Resolution's expectation.""" - return f"Less than or equal to {self.number}." + return f"Less than or equal to {self.number_to_log}." - @beat("... hoping it's less than or equal to {number}.") + @beat("... hoping it's less than or equal to {number_to_log}.") def resolve(self) -> Matcher[float]: """Produce the Matcher to make the assertion.""" return less_than_or_equal_to(self.number) def __init__(self, number: float) -> None: self.number = number + self.number_to_log = represent_prop(number) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 2831c3fd..f1057898 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -396,7 +396,7 @@ def test_description(self) -> None: igt = IsGreaterThan(test_num) - expected_description = f"Greater than {test_num}." + expected_description = "Greater than <41>." assert igt.describe() == expected_description @@ -419,7 +419,7 @@ def test_description(self) -> None: igtoet = IsGreaterThanOrEqualTo(test_num) - expected_description = f"Greater than or equal to {test_num}." + expected_description = "Greater than or equal to <1337>." assert igtoet.describe() == expected_description @@ -493,7 +493,7 @@ def test_description(self) -> None: ilt = IsLessThan(test_num) - expected_description = f"Less than {test_num}." + expected_description = "Less than <43>." assert ilt.describe() == expected_description @@ -516,7 +516,7 @@ def test_description(self) -> None: iltoet = IsLessThanOrEqualTo(test_num) - expected_description = f"Less than or equal to {test_num}." + expected_description = "Less than or equal to <1337>." assert iltoet.describe() == expected_description From d6e611a67305bb6165069ad39292fdf3f581897c Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 28 Sep 2023 08:33:06 -0500 Subject: [PATCH 17/19] better descriptive names of tests --- tests/test_actions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_actions.py b/tests/test_actions.py index 60ef1960..c8ad76d9 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -639,7 +639,7 @@ def test_raises_assertionerror_if_one_fails(self, Tester) -> None: (FakeQuestion(), IsEqualTo(True)), ).perform_as(Tester) - def test_stops_at_first_failure(self, Tester, caplog) -> None: + def test_log_first_failure(self, Tester, caplog) -> None: mock_question = FakeQuestion() caplog.set_level(logging.INFO) @@ -669,7 +669,7 @@ def test_stops_at_first_failure(self, Tester, caplog) -> None: " but: was \n", ] - def test_passes_if_all_pass(self, Tester, caplog) -> None: + def test_log_all_pass(self, Tester, caplog) -> None: caplog.set_level(logging.INFO) # test passes if no exception is raised SeeAllOf( @@ -760,14 +760,14 @@ def test_raises_assertionerror_if_none_pass(self, Tester) -> None: assert "did not find any expected answers" in str(actual_exception) - def test_stops_at_first_pass(self, Tester, caplog) -> None: + def test_log_first_pass(self, Tester, caplog) -> None: mock_question = FakeQuestion() caplog.set_level(logging.INFO) SeeAnyOf( (mock_question, IsEqualTo(False)), - (mock_question, IsEqualTo(True)), # <-- + (mock_question, IsEqualTo(True)), (mock_question, IsEqualTo(True)), (mock_question, IsEqualTo(True)), ).perform_as(Tester) From 860ab643142f514f64d2670bcca3c304b34822b1 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 28 Sep 2023 08:49:37 -0500 Subject: [PATCH 18/19] parameterizing tests per PR comments --- tests/test_resolutions.py | 46 +++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index f1057898..9537aefb 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -132,9 +132,7 @@ def test_description(self) -> None: cim = ContainsItemMatching(test_pattern) - expected_description = ( - 'A sequence with an item matching the pattern r".*".' - ) + expected_description = 'A sequence with an item matching the pattern r".*".' assert cim.describe() == expected_description @@ -192,12 +190,13 @@ def test_the_test(self) -> None: assert cti.matches(range(0, 10)) assert not cti.matches({0, 3, 5}) - def test_description_uses_represent_prop(self) -> None: - cti_int = ContainsTheItem(1) - cti_str = ContainsTheItem("1") - - assert cti_int.describe() == "A sequence containing <1>." - assert cti_str.describe() == "A sequence containing '1'." + @pytest.mark.parametrize( + ("arg", "expected"), + ((1, "A sequence containing <1>."), ("1", "A sequence containing '1'.")), + ) + def test_description_uses_represent_prop(self, arg: object, expected: str) -> None: + cti = ContainsTheItem(arg) + assert cti.describe() == expected class TestContainsTheKey: @@ -259,12 +258,14 @@ def test_the_test(self) -> None: assert ctv.matches({"key": "value", "play": "Hamlet"}) assert not ctv.matches({"play": "Hamlet"}) - def test_description_uses_represent_prop(self) -> None: - ctv_int = ContainsTheValue(42) - ctv_str = ContainsTheValue("42") - assert ctv_int.describe() == "Containing the value <42>." - assert ctv_str.describe() == "Containing the value '42'." + @pytest.mark.parametrize( + ("arg", "expected"), + ((42, "Containing the value <42>."), ("42", "Containing the value '42'.")), + ) + def test_description_uses_represent_prop(self, arg: object, expected: str) -> None: + ctv = ContainsTheValue(arg) + assert ctv.describe() == expected class TestEmpty: @@ -369,12 +370,13 @@ def test_the_test(self) -> None: assert ie.matches(1) assert not ie.matches(2) - def test_description_uses_represent_prop(self) -> None: - ie_int = IsEqualTo(8675) - ie_str = IsEqualTo("8675") - - assert ie_int.describe() == "Equal to <8675>." - assert ie_str.describe() == "Equal to '8675'." + @pytest.mark.parametrize( + ("arg", "expected"), + ((8675, "Equal to <8675>."), ("8675", "Equal to '8675'.")), + ) + def test_description_uses_represent_prop(self, arg: object, expected: str) -> None: + ie = IsEqualTo(arg) + assert ie.describe() == expected class TestIsGreaterThan: @@ -581,7 +583,9 @@ def test_description(self) -> None: re_ = ReadsExactly(test_text) - expected_description = "'I will not buy this record, it is scratched.', verbatim." + expected_description = ( + "'I will not buy this record, it is scratched.', verbatim." + ) assert re_.describe() == expected_description From 36638477914f903def218ce2b5c6a43962769e63 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 28 Sep 2023 09:07:49 -0500 Subject: [PATCH 19/19] fixing tests after rebased latest trunk changes --- tests/test_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_actions.py b/tests/test_actions.py index c8ad76d9..a6b55f47 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -310,7 +310,7 @@ def test_mention_multiple_errors_once(self, mocked_time, Tester): Eventually(See(mock_question, IsEqualTo(False))).perform_as(Tester) assert str(actual_exception.value) == ( - "Tester tried to Eventually see if returns bool is equal to False 3 times " + "Tester tried to Eventually see if returns bool is equal to 3 times " "over 20.0 seconds, but got:\n" " AssertionError: \n" "Expected: \n"