aptakhin/regress

View on GitHub
testoot/base_testoot.py

Summary

Maintainability
A
3 hrs
Test Coverage
from typing import Optional

from testoot.base import TestootStorage, TestootSerializer, \
    CanonizePolicy, TestootContext, FileType, Comparator


class BaseTestoot:
    """Main logic object. Can test and canonize data."""

    def __init__(self, storage: TestootStorage, serializer: TestootSerializer,
                 canonize_policy: CanonizePolicy,
                 comparator: Optional[Comparator] = None):
        """Constructor

        :param storage: storage instance
        :param serializer: serializer instance
        :param canonize_policy: controls behavior when we met result
               test conflict.

               - :py:class:`.testoot.pub.NoCanonizePolicy` always raises
                 an error in assert.
               - :py:class:`.testoot.pub.AskCanonizePolicy`
                 with `--canonize` pytest flag asks user approval for
                 canonizing. If user refuses it skips for later and raises
                 an error in assert then.

        :param comparator: comparison for objects
        """
        self._storage: TestootStorage = storage
        self._serializer: TestootSerializer = serializer
        self._canonize_policy: CanonizePolicy = canonize_policy
        self._comparator: Comparator = comparator

    def test(self, obj: any, context: TestootContext,
             suffix: Optional[str] = None,
             file_type_hint: Optional[FileType] = None,
             comparator: Optional[Comparator] = None,
             serializer: Optional[TestootSerializer] = None):
        """Tests object.

        :param obj: test object
        :param context: test context
        :param suffix: test suffix for making a few tests
               in one context
        :param file_type_hint: override serializer hint for file
        :param comparator: custom comparator override
        :param serializer: custom serializer override

        :return:
        """
        serializer = self._get_serializer(serializer, context=context)

        if file_type_hint is None:
            file_type_hint = serializer.file_type_hint

        storage_name = context.get_storage_name(suffix=suffix,
                                                file_type_hint=file_type_hint)
        with self._storage.open_read(storage_name,
                                     mode=serializer.mode) as rstream:
            canon_obj = (serializer.load(rstream) if rstream is not None
                         else None)

            self._do_test(
                test_obj=obj,
                canon_obj=canon_obj,
                comparator=comparator,
                storage_name=storage_name,
                context=context,
            )

        return True

    def test_filename(self, filename: str, *, context: TestootContext,
                      comparator: Optional[Comparator] = None,
                      serializer: Optional[TestootSerializer] = None):
        """Tests file generated by software.

        :param filename: test filename
        :param context: test context
        :param comparator: custom comparator override
        :param serializer: custom serializer override

        :return:
        """
        serializer = self._get_serializer(serializer, context=context)

        from pathlib import Path
        # FIXME: this internal
        set_filename = Path(filename).relative_to(self._storage.root_dir)
        storage_name = context.get_storage_name_from_filename(set_filename)
        with self._storage.open_read(storage_name,
                                     mode=serializer.mode) as rstream:
            canon_obj = (serializer.load(rstream) if rstream is not None
                         else None)

        with open(filename, 'rb') as test_stream:
            test_obj = serializer.load(test_stream)

        self._do_test(
            test_obj=test_obj,
            canon_obj=canon_obj,
            comparator=comparator,
            storage_name=storage_name,
            context=context,
        )

        return True

    @property
    def storage(self):
        return self._storage

    @property
    def canonize_policy(self):
        return self._canonize_policy

    def clone(self, *, storage: Optional[TestootStorage] = None,
              serializer: Optional[TestootSerializer] = None,
              canonize_policy: Optional[CanonizePolicy] = None,
              comparator: Optional[Comparator] = None):
        return type(self)(
            storage=storage or self._storage,
            serializer=serializer or self._serializer,
            canonize_policy=canonize_policy or self._canonize_policy,
            comparator=comparator or self._comparator,
        )

    def _canonize(self, obj: any, *, storage_name: str,
                  serializer: TestootSerializer):
        """Canonizes result of test

        :param obj: test object
        :param storage_name: storage name

        :return:
        """
        with self._storage.open_write(storage_name,
                                      mode=serializer.mode) as wstream:
            serializer.dump(obj, wstream)

    def _do_test(self, *, test_obj: any, canon_obj: any,
                 storage_name: str, context: TestootContext,
                 comparator: Optional[Comparator] = None,
                 serializer: Optional[TestootSerializer] = None):
        comparator = self._get_comparator(comparator, context=context)
        serializer = self._get_serializer(serializer, context=context)

        if canon_obj is None:
            return self._canonize(test_obj, storage_name=storage_name,
                                  serializer=serializer)

        try:
            comparator.compare(test_obj, canon_obj)
        except Exception as e:
            test_result = context.create_test_result(test_obj=test_obj,
                                                     canon_obj=canon_obj,
                                                     exc=e)

            # TODO: refactor this strange logic
            # Asks query parameters to canonize and check it again
            do_canonize = context.ask_canonize()
            if do_canonize:
                do_canonize = self._canonize_policy.ask_canonize(
                    test_result=test_result,
                )

            if not do_canonize:
                raise

            return self._canonize(test_obj, storage_name=storage_name,
                                  serializer=serializer)

    def _get_comparator(self, comparator: Optional[Comparator], *,
                        context: TestootContext) -> Comparator:
        return next(filter(lambda x: x is not None, (
            comparator, context.get_comparator(), self._comparator)))

    def _get_serializer(self, serializer: Optional[TestootSerializer], *,
                        context: TestootContext) \
            -> TestootSerializer:
        return next(filter(lambda x: x is not None, (
            serializer, context.get_serializer(), self._serializer)))