def instance_of(s): """Constructs a new data structure instance conforming to given schema. This function proceeds recursively. """ if isinstance(s, Mapping): res = (OrderedDict() if isinstance(s, OrderedDict) else dict()) items = s.iteritems() else: res = [None] * len(s) items = enumerate(s) for key, value in items: if is_arbitrary(value): value = to_arbitrary(value) res[key] = next(value) elif isinstance(value, Iterable): res[key] = instance_of(value) else: res[key] = value if isinstance(s, tuple): res = tuple(res) return res
def __init__(self, func, data, tests_count=None): """Constructor. Callers should specify the function which encodes the testing property, and arbitrary values' generator for test data (function arguments). """ self.func = self.__coerce_to_generator_func(func) self.data = dict((k, to_arbitrary(v) if is_arbitrary(v) else v) for k, v in data.iteritems()) if tests_count is not None: self.tests_count = tests_count
def apply(func, *args, **kwargs): """Generator that applies a specific function to objects returned by given generator(s). Any number of generators can be passed as arguments, and they can be both positional (``args``) or keyword arguments (``kwargs``). In either case, the same invocation style (i.e. positional or keyword) will be used when calling the ``func`` with actual values obtained from given generators. As an example, the following call:: apply(json.dumps, dict_(items=two(str))) will create a generator that yields results of ``json.dumps(d)``, where ``d`` is an arbitrary dictionary that maps strings to strings. Similarly, using :func:`apply` as shown below:: apply(itertools.product, list_(of=int), repeat=4) gets us a generator that produces results of ``itertools.product(l, repeat=4)``, where ``l`` is an arbitrary list of ``int``\ s. """ if not func: raise ValueError("no function provided") if not callable(func): raise TypeError("expected a callable") func_args = [next(to_arbitrary(arg)) for arg in args] func_kwargs = dict((k, next(to_arbitrary(v))) for (k, v) in kwargs.iteritems()) @arbitrary def generator(): return func(*func_args, **func_kwargs) return generator
def elements(*args, **kwargs): """Generator that returns random elements from given set. Elements can be passed either directly as arguments:: elements(1, 2, 3) or as a list:: elements([1, 2, 3]) Every element has equal probability of being chosen. :param count: Optional number of elements in every returned subset. If omitted, a single element will be yield every time. If provided, it should always be passed as keyword argument, e.g. ``elements(range(10), count=3)``. This can be also a generator - such as :func:`int_` - if there's a need to randomize the subset size, too. .. note:: There is difference between ``elements(foo)`` and ``elements(foo, count=1)``. The first form returns random element from the set ``foo``, while the second returns random *1-element subset* of ``foo`` - ``x`` vs ``[x]``, essentially. """ if not args: raise ValueError("cannot pick random elements from empty sequence") count = kwargs.get('count', None) if count is None: return random.choice(args) if is_arbitrary(count): count = next(to_arbitrary(count)) count = min(count, len(args)) return random.sample(args, count)
def combinator(func): """Decorator for arbitrary combinator functions which take a collection of arguments as either an actual list/sequence, or as positional arguments. In other words, it makes it possible to use the following two forms of invocation:: func([1, 2, 3]) func(1, 2, 3) In both cases ``func`` receives 1, 2 and 3 as positional arguments (``*args``). """ _2arbitrary = recursive( lambda obj: to_arbitrary(obj) if is_arbitrary(obj) else obj) @arbitrary @functools.wraps(func) def wrapped(*args, **kwargs): if not args: return func(**kwargs) new_args = [] for arg in args: arg_collection = (isinstance(arg, Iterable) and not is_arbitrary(arg)) if arg_collection: arg = map(_2arbitrary, arg) new_args.extend(arg) else: arg = _2arbitrary(arg) new_args.append(arg) return func(*new_args, **kwargs) return wrapped