Source code for hbutils.testing.generator.aetg

import random
from typing import Iterator, Mapping, Optional, List, Tuple

from hbutils.model import visual, hasheq, accessor, asitems
from .base import BaseGenerator
from ...reflection import nested_for, progressive_for

__all__ = [
    'AETGGenerator',
]


@hasheq()
@visual()
@accessor(readonly=True)
@asitems(['name', 'value'])
class _NameValueTuple:
    def __init__(self, name, value):
        self.__name = name
        self.__value = value


@hasheq()
@visual()
@accessor(readonly=True)
@asitems(['items'])
class _AETGValuePair:
    def __init__(self, *pairs):
        self.__items = tuple(sorted(pairs, key=lambda x: (x.name, x.value)))


def _create_init_pairs(names: List[str]) -> List[Tuple[str, ...]]:
    return [(iname, jname) for iname, jname in progressive_for(names, 2)]


def _process_pairs(pairs: List[Tuple[str, ...]], names: List[str]) -> List[Tuple[str, ...]]:
    _name_set = set(names)
    _name_id_dict = {name: i for i, name in enumerate(names)}

    _init_pairs = []
    for pair in pairs:
        actual_pair_ids = sorted(set(map(lambda x: _name_id_dict[x], pair)))
        actual_pair = tuple(names[i] for i in actual_pair_ids)
        _init_pairs.append(actual_pair)

    _init_pairs = sorted(_init_pairs, key=lambda x: (len(x), x), reverse=True)
    _final_pairs = []
    for pair in _init_pairs:
        sp = set(pair)
        is_included = False
        for exist_pair in _final_pairs:
            sep = set(exist_pair)
            if sp & sep == sp:
                is_included = True
                break

        if not is_included:
            _final_pairs.append(pair)

    return _final_pairs[::-1]


# noinspection PyProtectedMember
_DEFAULT_RANDOM = random._inst


[docs]class AETGGenerator(BaseGenerator): """ Full AETG model, test cases will be generated to make sure the required pairs will be all tested.. """
[docs] def __init__(self, values, names: Optional[List[str]] = None, pairs: Optional[List[Tuple[str, ...]]] = None, rnd: Optional[random.Random] = None): """ Constructor of the :class:`hbutils.testing.AETGGenerator` class. :param values: Selection values, such as ``{'a': [2, 3], 'b': ['b', 'c']}``. :param names: Names of the given generator, default is ``None`` which means use the sorted \ key set of the values. :param pairs: Pairs required to be all tested, default is ``None`` which means all the \ binary pairs will be included. :param rnd: Random object, default is ``None`` which means the default random object will be used. """ BaseGenerator.__init__(self, values, names) if pairs is None: pairs = _create_init_pairs(self.names) self.__pairs = _process_pairs(pairs, self.names) self.__rnd = rnd or _DEFAULT_RANDOM self.__node_cnt = None self.__non_exist_pairs = None
@property def pairs(self) -> List[Tuple[str, ...]]: """ Pairs required to be all tested. """ return self.__pairs def __get_init_info(self): if self.__node_cnt is None: node_cnt = {} non_exist_pairs = set() for one_pair in self.__pairs: for value_items in nested_for(*[self.values[name] for name in one_pair]): pair_items = [] for name, value in zip(one_pair, value_items): tp = _NameValueTuple(name, value) pair_items.append(tp) node_cnt[tp] = node_cnt.get(tp, 0) + 1 non_exist_pairs.add(_AETGValuePair(*pair_items)) self.__node_cnt = node_cnt self.__non_exist_pairs = non_exist_pairs return dict(self.__node_cnt), set(self.__non_exist_pairs)
[docs] def cases(self) -> Iterator[Mapping[str, object]]: """ Get the cases in this AETG model. Examples:: >>> from hbutils.testing import AETGGenerator >>> gene = AETGGenerator({'a': (1, 2), 'b': (3, 4), 'c': (5, 6), 'd': (7, 8), 'e': (9, 10)}) >>> for p in gene.cases(): ... print(p) {'a': 1, 'b': 3, 'c': 6, 'd': 8, 'e': 10} {'a': 2, 'b': 4, 'c': 5, 'd': 7, 'e': 9} {'a': 2, 'b': 3, 'c': 6, 'd': 7, 'e': 9} {'a': 2, 'b': 4, 'c': 6, 'd': 8, 'e': 10} {'a': 1, 'b': 3, 'c': 5, 'd': 7, 'e': 10} {'a': 1, 'b': 4, 'c': 5, 'd': 8, 'e': 9} >>> gene = AETGGenerator( ... {'a': (1, 2), 'b': (3, 4), 'c': (5, 6), 'd': (7, 8), 'e': (9, 10)}, ... pairs=[('a', 'c'), ('b', 'd'), ('e',)] ... ) >>> for p in gene.cases(): ... print(p) {'a': 2, 'b': 3, 'c': 6, 'd': 8, 'e': 9} {'a': 1, 'b': 4, 'c': 5, 'd': 7, 'e': 10} {'a': 2, 'b': 4, 'c': 5, 'd': 8, 'e': 9} {'a': 1, 'b': 3, 'c': 6, 'd': 7, 'e': 10} """ n = len(self.names) m = len(self.__pairs[-1]) if self.__pairs else 0 node_cnt, non_exist_pairs = self.__get_init_info() while non_exist_pairs: first_pair = non_exist_pairs.pop() non_exist_pairs.add(first_pair) tnames = [] seqs = [] for pair_item in first_pair.items: tnames.append(pair_item.name) seqs.append(pair_item) _tname_set = set(tnames) other_names = [name for name in self.names if name not in _tname_set] self.__rnd.shuffle(other_names) tnames += other_names for i in range(len(seqs), n): iname = tnames[i] curpair, curxk = None, None for ivalue in self.values[iname]: ituple = _NameValueTuple(iname, ivalue) litems = [ituple] non_exists = 0 for j in range(0, min(m, i + 1)): if j > 0: litems.append(seqs[i - j]) now_pair = _AETGValuePair(*litems) if now_pair in non_exist_pairs: non_exists += 1 xk = (non_exists, node_cnt.get(ituple, 0), self.__rnd.random()) if curxk is None or xk > curxk: curpair = ituple curxk = xk seqs.append(curpair) px = {pair.name: pair.value for pair in seqs} for one_pair in self.__pairs: new_pair = _AETGValuePair(*(_NameValueTuple(name, px[name]) for name in one_pair)) if new_pair in non_exist_pairs: non_exist_pairs.remove(new_pair) for np in new_pair.items: node_cnt[np] -= 1 yield {name: px[name] for name in self.names}