Skip to content

Commit

Permalink
Implement characters strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
kxepal committed Sep 22, 2015
1 parent 1842705 commit e79211d
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 5 deletions.
38 changes: 34 additions & 4 deletions src/hypothesis/searchstrategy/strings.py
Expand Up @@ -40,6 +40,20 @@ class OneCharStringStrategy(SearchStrategy):
)
zero_point = ord(u'0')

def __init__(self, exact=None, categories=None, exclude=None):
if exclude is None and exact is None and categories is None:
self.exact = frozenset([])
self.categories = frozenset(['Cs'])
self.exclude = True
else:
assert exact or categories, \
'no rule for characters generation provided'
if exclude is None:
exclude = False
self.exact = frozenset(exact or [])
self.exclude = bool(exclude)
self.categories = frozenset(categories or [])

def draw_parameter(self, random):
alphabet_size = 1 + dist.geometric(random, 0.1)
alphabet = []
Expand All @@ -66,11 +80,24 @@ def draw_parameter(self, random):
if self.is_good(char):
alphabet.append(char)
if u'\n' not in alphabet and not random.randint(0, 10):
alphabet.append(u'\n')
if self.is_good(u'\n'):
alphabet.append(u'\n')
return tuple(alphabet)

def is_good(self, char):
return unicodedata.category(char) != u'Cs'
if char in self.exact:
if self.exclude:
return False
return True

if unicodedata.category(char) in self.categories:
if self.exclude:
return False
return True

if self.exclude:
return True
return False

def draw_template(self, random, p):
return random.choice(p)
Expand Down Expand Up @@ -112,14 +139,17 @@ def accept(random, template):
def try_ascii(self, random, template):
if template < u'0':
for i in hrange(ord(template) + 1, self.zero_point + 1):
yield hunichr(i)
char = hunichr(i)
if self.is_good(char):
yield char

for i in self.ascii_characters:
if i < u'0':
continue
if i >= template:
break
yield i
if self.is_good(i):
yield i

def to_basic(self, template):
return template
Expand Down
29 changes: 28 additions & 1 deletion src/hypothesis/strategies.py
Expand Up @@ -38,7 +38,7 @@

'booleans', 'integers', 'floats', 'complex_numbers', 'fractions',
'decimals',
'text', 'binary',
'characters', 'text', 'binary',
'tuples', 'lists', 'sets', 'frozensets',
'dictionaries', 'fixed_dictionaries',
'sampled_from',
Expand Down Expand Up @@ -511,6 +511,33 @@ def streaming(elements):
return StreamStrategy(elements)


@defines_strategy
def characters(exact=None, categories=None, exclude=None):
"""Generates unicode text type (unicode on python 2, str on python 3)
characters by specifying rules.
With `exact` list you ensures that only specified characters will be
produced.
`categories` accepts unicode categories names in order to produce only
characters that matches them.
`exclude` flag when is ``True`` reverses policy and excludes characters
that matches specified parameters.
By default produces all characters except unicode surrogates.
"""
if exact is not None and categories is None and not exclude:
return sampled_from(exact)

from hypothesis.searchstrategy.strings import OneCharStringStrategy

return OneCharStringStrategy(exact=exact,
categories=categories,
exclude=exclude)


@defines_strategy
def text(
alphabet=None,
Expand Down
69 changes: 69 additions & 0 deletions tests/cover/test_simple_characters.py
@@ -0,0 +1,69 @@
# coding=utf-8

# This file is part of Hypothesis (https://github.com/DRMacIver/hypothesis)

# Most of this work is copyright (C) 2013-2015 David R. MacIver
# (david@drmaciver.com), but it contains contributions by others. See
# https://github.com/DRMacIver/hypothesis/blob/master/CONTRIBUTING.rst for a
# full list of people who may hold copyright, and consult the git log if you
# need to determine who owns an individual contribution.

# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at http://mozilla.org/MPL/2.0/.

# END HEADER

from __future__ import division, print_function, absolute_import

import unicodedata

import pytest
from hypothesis import find
from hypothesis.errors import NoSuchExample
from hypothesis.strategies import characters
from hypothesis.searchstrategy.misc import SampledFromStrategy


def test_default_excludes_surrogates():
with pytest.raises(NoSuchExample):
find(characters(), lambda c: unicodedata.category(c) == 'Cs')


def test_exact_characters_is_sampled_from():
assert isinstance(
characters(exact='abc').wrapped_strategy.wrapped_strategy,
SampledFromStrategy)


def test_exclude_specific_characters():
st = characters(exact='123', exclude=True)

with pytest.raises(NoSuchExample):
find(st, lambda c: c in '123')


def test_characters_of_specific_groups():
st = characters(categories=('Lu', 'Nd'))

find(st, lambda c: unicodedata.category(c) == 'Lu')
find(st, lambda c: unicodedata.category(c) == 'Nd')

with pytest.raises(NoSuchExample):
find(st, lambda c: unicodedata.category(c) not in ('Lu', 'Nd'))


def test_exclude_characters_of_specific_groups():
st = characters(categories=('Lu', 'Nd'), exclude=True)

find(st, lambda c: unicodedata.category(c) != 'Lu')
find(st, lambda c: unicodedata.category(c) != 'Nd')

with pytest.raises(NoSuchExample):
find(st, lambda c: unicodedata.category(c) in ('Lu', 'Nd'))


def test_can_find_exact_character_with_foreign_unicode_category():
st = characters(exact='a', categories=['Nd'])

assert 'a' == find(st, lambda c: unicodedata.category(c) == 'Ll')

0 comments on commit e79211d

Please sign in to comment.