Source code for moptipy.tests.component

"""Functions that can be used to test component implementations."""
from typing import Final

from pycommons.types import type_error, type_name_of

from moptipy.api import logging
from moptipy.api.component import Component
from moptipy.utils.logger import (
    KEY_VALUE_SEPARATOR,
    SECTION_END,
    SECTION_START,
    InMemoryLogger,
)
from moptipy.utils.strings import sanitize_name


[docs] def validate_component(component: Component) -> None: """ Check whether an object is a valid moptipy component. This test checks the conversion to string and the logging of parameters. This method must be called before the other `validate_*` methods provided in this package. :param component: the component to test :raises ValueError: if `component` is not a valid :class:`~moptipy.api.component.Component` instance :raises TypeError: if a type is wrong or `component` is not even an instance of :class:`~moptipy.api.component.Component` """ if not isinstance(component, Component): raise type_error(component, "component", Component) if component.__class__ == Component: raise ValueError( "component cannot be an instance of Component directly.") name = str(component) if not isinstance(name, str): raise type_error(name, "str(component)", str) if len(name) <= 0: raise ValueError("str(component) must return a non-empty string, " f"but returns a {name!r}.") if name.strip() != name: raise ValueError("str(component) must return a string without " "leading or trailing white space, " f"but returns a {name!r}.") clean_name = sanitize_name(name) if clean_name != name: raise ValueError( "str(component) must return a string which does not " f"change when being sanitized, but returned {name!r}," f" which becomes {clean_name!r}.") name = str(component) if clean_name != name: raise ValueError("str(component) must always return the same value, " f"but returns a {name!r} and {clean_name!r}.") name = repr(component) if name != clean_name: raise ValueError("repr(component) must equal str(component), but " f"got {clean_name!r} vs. {name!r}.") if not (hasattr(component, "initialize") and callable(getattr(component, "initialize"))): raise ValueError("component must have method initialize.") component.initialize() if name != str(component): raise ValueError(f"name changed to {str(component)!r} " f"from {name!r} after initialize!") # test the logging of parameter values if not (hasattr(component, "log_parameters_to") and callable(getattr(component, "log_parameters_to"))): raise ValueError("component must have method log_parameters_to.") secname: Final[str] = "KV" with InMemoryLogger() as log: with log.key_values(secname) as kv: component.log_parameters_to(kv) lines = log.get_log() ll = len(lines) - 1 if (lines[0] != SECTION_START + secname) or \ (lines[ll] != SECTION_END + secname): raise ValueError("Invalid log data produced '" + "\n".join(lines) + "'.") kvs: Final[str] = KEY_VALUE_SEPARATOR lines = lines[1:ll] if len(lines) < 2: raise ValueError("A component must produce at least two lines of " f"key-value data, but produced {len(lines)}.") done_keys: set[str] = set() idx: int = 0 key: str = logging.KEY_NAME done_keys.add(key) keystr: str = f"{key}{kvs}" line: str = lines[idx] idx += 1 if not line.startswith(keystr): raise ValueError( f"First log line must begin with {keystr!r}, but starts" f" with {line!r}.") rest = line[len(keystr):] if rest != name: raise ValueError( f"value of key {keystr!r} should equal " f"{name!r} but is {rest!r}.") key = logging.KEY_CLASS keystr = f"{key}{kvs}" done_keys.add(key) line = lines[idx] idx += 1 if not line.startswith(keystr): raise ValueError( f"Second log line must begin with {keystr!r}, but " f"starts with {line!r}.") rest = line[len(keystr):] want = type_name_of(component) if rest != want: raise ValueError( f"value of key {keystr!r} should equal " f"{want!r} but is {rest!r}.") for line in lines[idx:]: i = line.index(kvs) key = line[0:i].strip() b = line[i + len(kvs)].strip() if (len(key) <= 0) or (len(b) <= 0): raise ValueError( f"Invalid key-value pair {line!r} - " f"splits to {(key + kvs + b)!r}!") if key in done_keys: raise ValueError(f"key {key!r} appears twice!") done_keys.add(key)