Code

Ticket #5419: fuzz.diff

File fuzz.diff, 5.0 KB (added by frasern, 7 years ago)
Line 
1Index: django/test/testcases.py
2===================================================================
3--- django/test/testcases.py    (revision 6248)
4+++ django/test/testcases.py    (working copy)
5@@ -1,4 +1,7 @@
6+import random
7 import re
8+import sys
9+import types
10 import unittest
11 from urlparse import urlsplit
12 
13@@ -11,6 +14,30 @@
14 
15 normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
16 
17+def generate_fuzzy_value(value_type):
18+    """
19+    Returns a randome value of the specified type, or raises a ValueError if
20+    the type is not supported. If value_type is callable then it is called and
21+    its return value is returned. This allows you to specify a function that
22+    generates a random value of a type that isn't supported.
23+    """
24+    if value_type is None or issubclass(value_type, types.NoneType):
25+        return None
26+    if issubclass(value_type, types.BooleanType):
27+        return random.choice((True, False))
28+    if issubclass(value_type, types.IntType):
29+        return random.randint(-sys.maxint - 1, sys.maxint)
30+    if issubclass(value_type, types.LongType):
31+        return long(generate_fuzzy_value(int) * generate_fuzzy_value(int))
32+    if issubclass(value_type, types.FloatType):
33+        return random.random() * generate_fuzzy_value(int)
34+    if issubclass(value_type, basestring):
35+        length = random.randint(0, 1000)
36+        return u''.join([unichr(random.randint(32, 65534)) for i in range(length)])
37+    if callable(value_type):
38+        return value_type()
39+    raise ValueError("Unsupported type: %s" % value_type)
40+
41 def to_list(value):
42     """
43     Puts value into a list if it's not already one.
44@@ -186,3 +213,72 @@
45         self.failIf(template_name in template_names,
46             (u"Template '%s' was used unexpectedly in rendering the"
47              u" response") % template_name)
48+
49+    def assertModelUnicodePassesFuzzTest(self, model, repeat=10):
50+        """
51+        Asserts that after having assigned random unicode data into each of
52+        the fields of an instance of the specified model that calling the
53+        __unicode__ method of the instance doesn't raise a UnicodeError.
54+        Random data is assigned and the __unicode__ method called repeat times.
55+        """
56+        instance = model()
57+        opts = model._meta
58+        for i in range(repeat):
59+            for field in opts.fields:
60+                setattr(instance, field.name, generate_fuzzy_value(str))
61+            try:
62+                unicode(instance)
63+            except UnicodeError, e:
64+                self.fail(u"Model '%s' unexpectedly raised %s when converting to unicode: %s" %
65+                    (model.__name__, e.__class__.__name__, e))
66+
67+    def assertFunctionPassesFuzzTest(self, func, arg_types=(),
68+        permitted_return_types=(), permitted_exceptions=(), repeat=10):
69+        """
70+        Asserts that the function when called with random values of the types
71+        specified by arg_types that the return value has a type from
72+        permitted_return_types and that if an exception is raised it is one
73+        that is in permitted_exceptions. The function is called repeat times
74+        with random data.
75+        """
76+       
77+        def isiterable(o):
78+            try:
79+                iter(o)
80+            except TypeError:
81+                return False
82+            return True
83+       
84+        def cartesian_product(L, *lists):
85+            if not lists:
86+                for x in L:
87+                    yield (x,)
88+            else:
89+                for x in L:
90+                    for y in cartesian_product(lists[0], *lists[1:]):
91+                        yield (x,) + y
92+
93+        # Allow the use of None instead of types.NoneType
94+        if None in permitted_return_types and types.NoneType not in permitted_return_types:
95+            permitted_return_types = tuple(permitted_return_types)
96+            permitted_return_types += (types.NoneType,)
97+
98+        # Ensure arg_types is a sequence of iterables
99+        for i, arg_type in enumerate(arg_types):
100+            if not isiterable(arg_type): arg_types[i] = [arg_type]
101+       
102+        for i in range(repeat):
103+            for atypes in cartesian_product(*arg_types):
104+                args = []
105+                for arg_type in atypes:
106+                    args.append(generate_fuzzy_value(arg_type))
107+                try:
108+                    return_value = func(*args)
109+                except:
110+                    if permitted_exceptions:
111+                        exc_type, exc_value = sys.exc_info()[:2]
112+                        if exc_type not in permitted_exceptions:
113+                            self.fail(u"Unexpected %s encountered when calling %s with inputs %r: %s" % (exc_type.__name__, func.__name__, args, exc_value))
114+                else:
115+                    if permitted_return_types and type(return_value) not in permitted_return_types:
116+                        self.fail(u"Unexpected return value type %s encountered when calling %s with inputs %r." % (return_value.__name__, func.__name__, args))
117\ No newline at end of file