mirror of
https://github.com/emilybache/GildedRose-Refactoring-Kata.git
synced 2026-02-09 03:31:28 +00:00
432 lines
16 KiB
Python
432 lines
16 KiB
Python
import argparse
|
|
import xml.dom.minidom
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from typing import Any, ByteString, Callable, Iterator, List, Optional
|
|
|
|
import approvaltests.namer.default_namer_factory
|
|
import approvaltests.reporters.default_reporter_factory
|
|
from approval_utilities import utils
|
|
from approval_utilities.approvaltests.core.executable_command import ExecutableCommand
|
|
from approval_utilities.approvaltests.core.verifiable import Verifiable
|
|
from approval_utilities.list_utils import format_list
|
|
from approval_utilities.utilities.exceptions.exception_utils import to_string
|
|
from approval_utilities.utilities.map_reduce import first
|
|
from approvaltests.approval_exception import ApprovalException
|
|
from approvaltests.binary_writer import BinaryWriter
|
|
from approvaltests.core import Reporter, Writer
|
|
from approvaltests.core.format_wrapper import AlwaysMatch, FormatWrapper
|
|
from approvaltests.core.namer import Namer
|
|
from approvaltests.core.options import Options
|
|
from approvaltests.core.scenario_namer import ScenarioNamer
|
|
from approvaltests.existing_file_writer import ExistingFileWriter
|
|
from approvaltests.file_approver import FileApprover
|
|
from approvaltests.namer.namer_base import NamerBase
|
|
from approvaltests.namer.stack_frame_namer import StackFrameNamer
|
|
from approvaltests.reporters.diff_reporter import DiffReporter
|
|
from approvaltests.reporters.executable_command_reporter import (
|
|
ExecutableCommandReporter,
|
|
)
|
|
from approvaltests.string_writer import StringWriter
|
|
from approvaltests.verifiable_objects.formatter_of_argparse_namespace import (
|
|
FormatterWrapperOfArgparseNamespace,
|
|
)
|
|
|
|
__unittest = True
|
|
__tracebackhide__ = True
|
|
|
|
|
|
class Settings:
|
|
def allow_multiple_verify_calls_for_this_method(self) -> None:
|
|
class_and_method = get_default_namer().get_file_name()
|
|
|
|
def allow_method(filename: str) -> bool:
|
|
return class_and_method in filename
|
|
|
|
# make it so we do not raise an eerror if someonee calls it twice
|
|
FileApprover.add_allowed_duplicates(allow_method)
|
|
|
|
|
|
def settings() -> Settings:
|
|
return Settings()
|
|
|
|
|
|
def set_default_reporter(reporter: Reporter) -> None:
|
|
return approvaltests.reporters.default_reporter_factory.set_default_reporter(
|
|
reporter
|
|
)
|
|
|
|
|
|
def get_default_reporter() -> Reporter:
|
|
return approvaltests.reporters.default_reporter_factory.get_default_reporter()
|
|
|
|
|
|
def get_reporter(reporter: Optional[Reporter]) -> Reporter:
|
|
return approvaltests.reporters.default_reporter_factory.get_reporter(reporter)
|
|
|
|
|
|
def get_default_namer(extension: Optional[str] = None) -> NamerBase:
|
|
from approvaltests.namer.default_name import get_default_namer as get_namer
|
|
|
|
return get_namer(extension)
|
|
|
|
|
|
def verify(
|
|
data: Any,
|
|
reporter: Optional[Reporter] = None,
|
|
namer: Optional[Namer] = None,
|
|
encoding: Optional[str] = None,
|
|
errors: Optional[str] = None,
|
|
newline: Optional[str] = None,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
"""Verify string data against a previously approved version of the string.
|
|
|
|
Args:
|
|
data: A string containing the data to be compared with approved data from a previous run.
|
|
On Python 2 this can be a bytes, str, or unicode object. On Python 3 this should be
|
|
a str object.
|
|
|
|
reporter: An optional Reporter. If None (the default), the default reporter
|
|
will be used; see get_default_reporter().
|
|
|
|
encoding: An optional encoding to be used when serialising the data to a byte stream for
|
|
comparison. If None (the default) a locale-dependent encoding will be used; see
|
|
locale.getpreferredencoding().
|
|
|
|
errors: An optional string that specifies how encoding and decoding errors are to be handled
|
|
If None (the default) or 'strict', raise a ValueError exception if there is an encoding
|
|
error. Pass 'ignore' to ignore encoding errors. Pass 'replace' to use a replacement
|
|
marker (such as '?') when there is malformed data.
|
|
|
|
newline: An optional string that controls how universal newlines work when comparing data.
|
|
It can be None, '', '\n', '\r', and '\r\n'. If None (the default) universal newlines are
|
|
enabled and any '\n' characters are translated to the system default line separator
|
|
given by os.linesep. If newline is '', no translation takes place. If newline is any of
|
|
the other legal values, any '\n' characters written are translated to the given string.
|
|
|
|
Raises:
|
|
ApprovalException: If the verification fails because the given string does not match the
|
|
approved string.
|
|
|
|
ValueError: If data cannot be encoded using the specified encoding when errors is set to
|
|
None or 'strict'.
|
|
"""
|
|
data = find_formatter_for_specified_class(data)
|
|
|
|
options = initialize_options(options, reporter)
|
|
|
|
if isinstance(data, Verifiable):
|
|
parameters = data.get_verify_parameters(options)
|
|
options = parameters.options
|
|
|
|
namer_to_use = namer or options.namer
|
|
verify_with_namer(
|
|
data,
|
|
namer_to_use,
|
|
encoding=encoding,
|
|
errors=errors,
|
|
newline=newline,
|
|
options=options,
|
|
)
|
|
|
|
|
|
format_wrappers = [FormatterWrapperOfArgparseNamespace(), AlwaysMatch()]
|
|
|
|
|
|
@contextmanager
|
|
def register_formatter(formatter: FormatWrapper) -> Iterator[None]:
|
|
format_wrappers.insert(0, formatter)
|
|
yield
|
|
format_wrappers.remove(formatter)
|
|
|
|
|
|
def find_formatter_for_specified_class(data: Any) -> Any:
|
|
wrapper = first(format_wrappers, lambda f: f.is_match(data))
|
|
return wrapper.wrap(data)
|
|
|
|
|
|
def verify_binary(
|
|
data: ByteString,
|
|
file_extension_with_dot: str,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
options = initialize_options(options, None).for_file.with_extension(
|
|
file_extension_with_dot
|
|
)
|
|
verify_with_namer_and_writer(
|
|
options.namer,
|
|
BinaryWriter(data, file_extension_with_dot),
|
|
None,
|
|
options=options,
|
|
)
|
|
|
|
|
|
def initialize_options(
|
|
options: Optional[Options], reporter: Optional[Reporter] = None
|
|
) -> Options:
|
|
if options is None:
|
|
options = Options()
|
|
if reporter is not None:
|
|
options = options.with_reporter(reporter)
|
|
return options
|
|
|
|
|
|
def verify_with_namer(
|
|
data: Any,
|
|
namer: Namer,
|
|
reporter: Optional[Reporter] = None,
|
|
encoding: Optional[str] = None,
|
|
errors: Optional[str] = None,
|
|
newline: Optional[str] = None,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
"""Verify string data against a previously approved version of the string.
|
|
|
|
Args:
|
|
data: A string containing the data to be compared with approved data from a previous run.
|
|
On Python 2 this can be a bytes, str, or unicode object. On Python 3 this should be
|
|
a str object.
|
|
|
|
namer: A Namer instance used for naming approved and received data files.
|
|
|
|
reporter: An optional Reporter. If None (the default), the default reporter
|
|
will be used; see get_default_reporter().
|
|
|
|
encoding: An optional encoding to be used when serialising the data to a byte stream for
|
|
comparison. If None (the default) a locale-dependent encoding will be used; see
|
|
locale.getpreferredencoding().
|
|
|
|
errors: An optional string that specifies how encoding and decoding errors are to be handled
|
|
If None (the default) or 'strict', raise a ValueError exception if there is an encoding
|
|
error. Pass 'ignore' to ignore encoding errors. Pass 'replace' to use a replacement
|
|
marker (such as '?') when there is malformed data.
|
|
|
|
newline: An optional string that controls how universal newlines work when comparing data.
|
|
It can be None, '', '\n', '\r', and '\r\n'. If None (the default) universal newlines are
|
|
enabled and any '\n' characters are translated to the system default line separator
|
|
given by os.linesep. If newline is '', no translation takes place. If newline is any of
|
|
the other legal values, any '\n' characters written are translated to the given string.
|
|
|
|
Raises:
|
|
ApprovalException: If the verification fails because the given string does not match the
|
|
approved string.
|
|
|
|
ValueError: If data cannot be encoded using the specified encoding when errors is set to
|
|
None or 'strict'.
|
|
"""
|
|
options = initialize_options(options, reporter)
|
|
writer = StringWriter(
|
|
options.scrub(str(data)), encoding=encoding, errors=errors, newline=newline
|
|
)
|
|
verify_with_namer_and_writer(namer, writer, options=options)
|
|
|
|
|
|
def verify_with_namer_and_writer(
|
|
namer: Namer,
|
|
writer: Writer,
|
|
reporter: Optional[Reporter] = None,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
options = initialize_options(options, reporter)
|
|
error = FileApprover.verify(namer, writer, options.reporter, options.comparator)
|
|
if error:
|
|
raise ApprovalException(error)
|
|
|
|
|
|
# begin-snippet: verify_as_json
|
|
def verify_as_json(
|
|
object_to_verify: Any,
|
|
reporter: Optional[Reporter] = None,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
deserialize_json_fields: bool = False,
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
if deserialize_json_fields:
|
|
object_to_verify = utils.deserialize_json_fields(object_to_verify)
|
|
options = initialize_options(options, reporter)
|
|
json_text = utils.to_json(object_to_verify) + "\n"
|
|
verify(
|
|
json_text,
|
|
None,
|
|
encoding="utf-8",
|
|
newline="\n",
|
|
options=options.for_file.with_extension(".json"),
|
|
)
|
|
|
|
|
|
# end-snippet
|
|
|
|
|
|
def verify_xml(
|
|
xml_string: str,
|
|
reporter: Optional[Reporter] = None,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
options = initialize_options(options, reporter)
|
|
try:
|
|
dom = xml.dom.minidom.parseString(xml_string)
|
|
pretty_xml = dom.toprettyxml()
|
|
except Exception:
|
|
pretty_xml = xml_string
|
|
|
|
verify(pretty_xml, options=options.for_file.with_extension(".xml"))
|
|
|
|
|
|
def verify_html(
|
|
html_string: str,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
options = initialize_options(options)
|
|
try:
|
|
from bs4 import BeautifulSoup
|
|
|
|
pretty_html = BeautifulSoup(html_string, "html.parser").prettify()
|
|
except Exception:
|
|
pretty_html = html_string
|
|
|
|
verify(pretty_html, options=options.for_file.with_extension(".html"))
|
|
|
|
|
|
def verify_file(
|
|
file_name: str,
|
|
reporter: Optional[Reporter] = None,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
"""Verify the contents of a text file against previously approved contents.
|
|
|
|
Args:
|
|
file_name: The path to a file. The file will be opened in text mode.
|
|
|
|
reporter: An optional Reporter. If None (the default), the default reporter
|
|
will be used; see get_default_reporter().
|
|
|
|
|
|
Raises:
|
|
ApprovalException: If the verification fails because the given string does not match the
|
|
approved string.
|
|
|
|
ValueError: If data cannot be encoded using the specified encoding when errors is set to
|
|
None or 'strict'.
|
|
"""
|
|
options = initialize_options(options, reporter)
|
|
options = options.for_file.with_extension(Path(file_name).suffix, no_override=True)
|
|
verify_with_namer_and_writer(
|
|
options.namer, ExistingFileWriter(file_name, options), None, options=options
|
|
)
|
|
|
|
|
|
def verify_all(
|
|
header: str,
|
|
alist: List[Any],
|
|
formatter: Optional[Callable] = None,
|
|
reporter: Optional[DiffReporter] = None,
|
|
encoding: None = None,
|
|
errors: None = None,
|
|
newline: None = None,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
"""Verify a collection of items against a previously approved collection.
|
|
|
|
Args:
|
|
header: A header line string to be included before the list of items.
|
|
|
|
alist: An iterable series of objects, a string representation of each of which will be
|
|
included in an aggregated string for comparison.
|
|
|
|
formatter: An optional object which must have a print_item(x) method such that
|
|
formatter.print_item(x) will return a string representation of a single item from alist.
|
|
|
|
reporter: An optional Reporter. If None (the default), the default reporter
|
|
will be used; see get_default_reporter().
|
|
|
|
encoding: An optional encoding to be used when serialising the data to a byte stream for
|
|
comparison. If None (the default) a locale-dependent encoding will be used; see
|
|
locale.getpreferredencoding().
|
|
|
|
errors: An optional string that specifies how encoding and decoding errors are to be handled
|
|
If None (the default) or 'strict', raise a ValueError exception if there is an encoding
|
|
error. Pass 'ignore' to ignore encoding errors. Pass 'replace' to use a replacement
|
|
marker (such as '?') when there is malformed data.
|
|
|
|
newline: An optional string that controls how universal newlines work when comparing data.
|
|
It can be None, '', '\n', '\r', and '\r\n'. If None (the default) universal newlines are
|
|
enabled and any '\n' characters are translated to the system default line separator
|
|
given by os.linesep. If newline is '', no translation takes place. If newline is any of
|
|
the other legal values, any '\n' characters written are translated to the given string.
|
|
|
|
Raises:
|
|
ApprovalException: If the verification fails because the given string does not match the
|
|
approved string.
|
|
|
|
ValueError: If data cannot be encoded using the specified encoding when errors is set to
|
|
None or 'strict'.
|
|
"""
|
|
options = initialize_options(options, reporter)
|
|
text = format_list(alist, formatter, header)
|
|
verify(
|
|
text, None, encoding=encoding, errors=errors, newline=newline, options=options
|
|
)
|
|
|
|
|
|
def verify_exception(
|
|
code_that_throws_exception: Callable,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
result = ""
|
|
try:
|
|
code_that_throws_exception()
|
|
result = "No exception was thrown"
|
|
except BaseException as exception:
|
|
result = to_string(exception)
|
|
verify(result, options=options)
|
|
|
|
|
|
def get_scenario_namer(*scenario_name: Any) -> ScenarioNamer:
|
|
return ScenarioNamer(get_default_namer(), *scenario_name)
|
|
|
|
|
|
def delete_approved_file() -> None:
|
|
filename = Path(get_default_namer().get_approved_filename())
|
|
if filename.exists():
|
|
filename.unlink()
|
|
|
|
|
|
def verify_executable_command(
|
|
command: ExecutableCommand,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
options = initialize_options(options)
|
|
verify(
|
|
command.get_command(),
|
|
options=options.with_reporter(
|
|
ExecutableCommandReporter(command, options.reporter)
|
|
),
|
|
)
|
|
|
|
|
|
def verify_argument_parser(
|
|
parser: argparse.ArgumentParser,
|
|
*, # enforce keyword arguments - https://www.python.org/dev/peps/pep-3102/
|
|
options: Optional[Options] = None,
|
|
) -> None:
|
|
parser.formatter_class = lambda prog: argparse.HelpFormatter(prog, width=200)
|
|
options = options or Options()
|
|
scrubber = lambda t: t.replace("options:", "<optional header>:").replace(
|
|
"optional arguments:", "<optional header>:"
|
|
)
|
|
verify(
|
|
parser.format_help(),
|
|
options=options.with_scrubber(scrubber),
|
|
)
|