~mwshinn/+junk/verify

1 by Max Shinn
Initial commit
1
#!/usr/bin/python
2
12 by Max Shinn
Added automatic unit tests
3
import sys
1 by Max Shinn
Initial commit
4
import math
2 by Max Shinn
Ranges, and, or, and cleanups
5
import random
6
import itertools
3 by Max Shinn
Simple argument condition requirements
7
import inspect
6 by Max Shinn
Only one extra stack frame for all decorators
8
import functools
8 by Max Shinn
Better exceptions
9
from exceptions import *
2 by Max Shinn
Ranges, and, or, and cleanups
10
9 by Max Shinn
Even better asserts
11
_FUN_PROPS = "__verify__"
12
2 by Max Shinn
Ranges, and, or, and cleanups
13
def TypeFactory(v):
14 by Max Shinn
Better documentation and code organization
14
    """Ensure `v` is a valid Type.
15
16
    This function is used to convert user-specified types into
17
    internal types for the verification engine.  It allows Type
18
    subclasses, Type subclass instances, Python type, and user-defined
19
    classes to be passed.  Returns an instance of the type of `v`.
20
21
    Users should never access this function directly.
22
    """
23
2 by Max Shinn
Ranges, and, or, and cleanups
24
    if issubclass(type(v), Type):
25
        return v
26
    elif issubclass(v, Type):
27
        return v()
28
    elif issubclass(type(v), type):
29
        return Generic(v)
30
    else:
8 by Max Shinn
Better exceptions
31
        raise InvalidTypeError("Invalid type %s" % v)
1 by Max Shinn
Initial commit
32
33
class Type():
34
    def test(self, v):
35
        pass
36
37
class Generic(Type):
2 by Max Shinn
Ranges, and, or, and cleanups
38
    def __init__(self, typ):
39
        super().__init__()
3 by Max Shinn
Simple argument condition requirements
40
        assert isinstance(typ, type)
2 by Max Shinn
Ranges, and, or, and cleanups
41
        self.type = typ
42
    def test(self, v):
3 by Max Shinn
Simple argument condition requirements
43
        assert isinstance(v, self.type)
2 by Max Shinn
Ranges, and, or, and cleanups
44
    def generate(self):
12 by Max Shinn
Added automatic unit tests
45
        if hasattr(self.type, "_generate") and callable(self.type._generate):
46
            return self.type._generate()
47
        else:
48
            raise NoGeneratorError
2 by Max Shinn
Ranges, and, or, and cleanups
49
7 by Max Shinn
None type
50
class Nothing(Type):
51
    def test(self, v):
52
        assert v is None
53
    def generate(self):
54
        return [None]
55
2 by Max Shinn
Ranges, and, or, and cleanups
56
class Numeric(Type):
57
    def test(self, v):
58
        super().test(v)
8 by Max Shinn
Better exceptions
59
        assert isinstance(v, (int, float)), "Invalid numeric"
2 by Max Shinn
Ranges, and, or, and cleanups
60
    def generate(self):
61
        # Check infinity, nan, 0, +/- numbers, a float, a small/big number
62
        return [math.inf, -math.inf, math.nan, 0, 1, -1, 3.141, 1e-10, 1e10]
63
64
class Number(Numeric):
1 by Max Shinn
Initial commit
65
    def test(self, v):
66
        super().test(v)
67
        assert isinstance(v, (int, float)), "Invalid number"
68
        assert not math.isnan(v), "Number cannot be nan"
2 by Max Shinn
Ranges, and, or, and cleanups
69
        assert not math.isinf(v), "Number must be finite"
70
    def generate(self):
71
        return [0, 1, -1, 3.141, 1e-10, 1e10]
1 by Max Shinn
Initial commit
72
73
class Integer(Number):
74
    def test(self, v):
75
        super().test(v)
76
        assert v // 1 == v, "Invalid integer"
2 by Max Shinn
Ranges, and, or, and cleanups
77
    def generate(self):
78
        return [-100, -1, 0, 1, 100]
1 by Max Shinn
Initial commit
79
80
class Natural0(Integer):
81
    def test(self, v):
82
        super().test(v)
83
        assert v >= 0, "Must be greater than or equal to 0"
2 by Max Shinn
Ranges, and, or, and cleanups
84
    def generate(self):
85
        return [0, 1, 10, 100]
1 by Max Shinn
Initial commit
86
87
class Natural1(Natural0):
88
    def test(self, v):
89
        super().test(v)
90
        assert v > 0, "Must be greater than 0"
2 by Max Shinn
Ranges, and, or, and cleanups
91
    def generate(self):
92
        return [1, 2, 10, 100]
1 by Max Shinn
Initial commit
93
94
class Range(Number):
2 by Max Shinn
Ranges, and, or, and cleanups
95
    def __init__(self, low, high):
96
        super().__init__()
97
        assert isinstance(low, (float, int)) and isinstance(high, (float, int)), "Invalid bounds"
8 by Max Shinn
Better exceptions
98
        assert low < high, "Low %s must be strictly greater than high %s" % (low, high)
2 by Max Shinn
Ranges, and, or, and cleanups
99
        self.low = low if low is not None else -math.inf
100
        self.high = high if low is not None else math.inf
1 by Max Shinn
Initial commit
101
    def test(self, v):
102
        super().test(v)
12 by Max Shinn
Added automatic unit tests
103
        assert self.low <= v <= self.high, "Value %f must be greater than %f and less than %f" % (v, self.low, self.high)
4 by Max Shinn
Better ranges
104
    def _generate_quantiles(self):
105
        EPSILON = 1e-5
2 by Max Shinn
Ranges, and, or, and cleanups
106
        if not (math.isinf(self.low) or math.isinf(self.high)):
107
            l = self.low
108
            h = self.high
12 by Max Shinn
Added automatic unit tests
109
            return [(l+h)*EPSILON, (l+h)*.5, (l+h)*.25, (l+h)*.75, (l+h)*(1-EPSILON)]
4 by Max Shinn
Better ranges
110
        elif math.isinf(self.low):
111
            return [self.high-EPSILON]
112
        elif math.isinf(self.high):
113
            return [self.low-EPSILON]
9 by Max Shinn
Even better asserts
114
        raise AssertionError("Invalid Range bounds")
4 by Max Shinn
Better ranges
115
    def generate(self):
116
        return [self.low, self.high] + self._generate_quantiles()
2 by Max Shinn
Ranges, and, or, and cleanups
117
118
class RangeClosedOpen(Range):
119
    def test(self, v):
120
        super().test(v)
8 by Max Shinn
Better exceptions
121
        assert v != self.high, "Value must be strictly greater than %f" % self.high
4 by Max Shinn
Better ranges
122
    def generate(self):
123
        return [self.low] + self._generate_quantiles()
2 by Max Shinn
Ranges, and, or, and cleanups
124
125
class RangeOpenClosed(Range):
126
    def test(self, v):
127
        super().test(v)
8 by Max Shinn
Better exceptions
128
        assert v != self.low, "Value must be strictly less than %f" % self.low
4 by Max Shinn
Better ranges
129
    def generate(self):
130
        return [self.high] + self._generate_quantiles()
2 by Max Shinn
Ranges, and, or, and cleanups
131
132
class RangeOpen(RangeClosedOpen, RangeOpenClosed):
133
    def test(self, v):
134
        super().test(v)
4 by Max Shinn
Better ranges
135
    def generate(self):
136
        return self._generate_quantiles()
1 by Max Shinn
Initial commit
137
138
class Set(Type):
139
    def __init__(self, els):
2 by Max Shinn
Ranges, and, or, and cleanups
140
        super().__init__()
1 by Max Shinn
Initial commit
141
        assert hasattr(els, "__contains__") and callable(els.__contains__)
142
        self.els = els
143
    def test(self, v):
144
        super().test(v)
8 by Max Shinn
Better exceptions
145
        assert v in self.els, "Value %v in set" % v
2 by Max Shinn
Ranges, and, or, and cleanups
146
    def generate(self):
147
        return [e for e in self.els]
1 by Max Shinn
Initial commit
148
149
class String(Type):
150
    def test(self, v):
151
        super().test(v)
8 by Max Shinn
Better exceptions
152
        assert isinstance(v, str), "Non-string passed"
2 by Max Shinn
Ranges, and, or, and cleanups
153
    def generate(self):
154
        return ["", "a"*1000, "{100}", " ", "abc123", "two words", "\\", "%s", "1", "баклажан"]
1 by Max Shinn
Initial commit
155
156
class List(Type):
157
    def __init__(self, t):
2 by Max Shinn
Ranges, and, or, and cleanups
158
        super().__init__()
159
        self.type = TypeFactory(t)
1 by Max Shinn
Initial commit
160
    def test(self, v):
161
        super().test(v)
8 by Max Shinn
Better exceptions
162
        assert isinstance(v, list), "Non-list passed"
2 by Max Shinn
Ranges, and, or, and cleanups
163
        for e in v:
164
            self.type.test(e)
165
    def generate(self):
166
        return [[], self.type.generate(), [self.type.generate()[0]]*1000]
1 by Max Shinn
Initial commit
167
168
class Dict(Type):
169
    def __init__(self, k, v):
2 by Max Shinn
Ranges, and, or, and cleanups
170
        self.valtype = TypeFactory(v)
171
        self.keytype = TypeFactory(k)
1 by Max Shinn
Initial commit
172
    def test(self, v):
173
        super().test(v)
8 by Max Shinn
Better exceptions
174
        assert isinstance(v, dict), "Non-dict passed"
1 by Max Shinn
Initial commit
175
        for e in v.keys():
176
            self.keytype.test(e)
177
        for e in v.values():
178
            self.valtype.test(e)
2 by Max Shinn
Ranges, and, or, and cleanups
179
    def generate(self):
180
        return [{}, dict(zip(self.keytype.generate(), self.valtype.generate()))]
181
182
class And(Type):
183
    def __init__(self, *types):
184
        self.types = [TypeFactory(a) for a in types]
185
    def test(self, v):
186
        for t in self.types:
187
            t.test(v)
12 by Max Shinn
Added automatic unit tests
188
    def generate(self):
13 by Max Shinn
Better generate() function for And and Or
189
        all_generated = [e for t in self.types for e in t.generate()]
190
        valid_generated = []
191
        for g in all_generated:
192
            try:
193
                for t in self.types:
194
                    t.test(g)
195
            except AssertionError:
196
                continue
197
            else:
198
                valid_generated.append(g)
199
        return valid_generated
2 by Max Shinn
Ranges, and, or, and cleanups
200
201
class Or(Type):
202
    def __init__(self, *types):
203
        self.types = [TypeFactory(a) for a in types]
204
    def test(self, v):
205
        passed = False
206
        for t in self.types:
207
            try:
208
                t.test(v)
209
                passed = True
210
            except AssertionError:
211
                continue
212
        if passed == False:
8 by Max Shinn
Better exceptions
213
            raise AssertionError("Neither type in Or holds")
13 by Max Shinn
Better generate() function for And and Or
214
    def generate(self):
215
        return [e for t in self.types for e in t.generate()]
1 by Max Shinn
Initial commit
216
6 by Max Shinn
Only one extra stack frame for all decorators
217
def has_fun_prop(f, k):
14 by Max Shinn
Better documentation and code organization
218
    """Test whether function `f` has property `k`.
219
220
    We define properties as annotations added to a function throughout
221
    the process of defining a function for verification, e.g. the
222
    argument types.  If `f` is an unannotated function, this returns
223
    False.  If `f` has the property named `k`, it returns True.
224
    Otherwise, it returns False.
225
226
    Users should never access this function directly.
227
    """
6 by Max Shinn
Only one extra stack frame for all decorators
228
    if not hasattr(f, _FUN_PROPS):
229
        return False
230
    if not isinstance(getattr(f, _FUN_PROPS), dict):
231
        return False
232
    if k not in getattr(f, _FUN_PROPS).keys():
233
        return False
234
    return True
235
236
def get_fun_prop(f, k):
14 by Max Shinn
Better documentation and code organization
237
    """Get the value of property `k` from function `f`.
238
239
    We define properties as annotations added to a function throughout
240
    the process of defining a function for verification, e.g. the
241
    argument types.  If `f` does not have a property named `k`, this
242
    throws an error.  If `f` has the property named `k`, it returns
243
    the value of it.
244
245
    Users should never access this function directly.
246
    """
9 by Max Shinn
Even better asserts
247
    if not has_fun_prop(f, k):
248
        raise InternalError("Function %s has no property %s" % (str(f), k))
6 by Max Shinn
Only one extra stack frame for all decorators
249
    return getattr(f, _FUN_PROPS)[k]
250
251
def set_fun_prop(f, k, v):
14 by Max Shinn
Better documentation and code organization
252
    """Set the value of property `k` to be `v` in function `f`.
253
254
    We define properties as annotations added to a function throughout
255
    the process of defining a function for verification, e.g. the
256
    argument types.  This sets function `f`'s property named `k` to be
257
    value `v`.
258
259
    Users should never access this function directly.
260
    """
6 by Max Shinn
Only one extra stack frame for all decorators
261
    if not hasattr(f, _FUN_PROPS):
262
        setattr(f, _FUN_PROPS, {})
263
    if not isinstance(getattr(f, _FUN_PROPS), dict):
9 by Max Shinn
Even better asserts
264
        raise InternalError("Invalid function properties dictionary for %s" % str(f))
6 by Max Shinn
Only one extra stack frame for all decorators
265
    getattr(f, _FUN_PROPS)[k] = v
266
14 by Max Shinn
Better documentation and code organization
267
def _wrap(func):
268
    def _decorated(*args, **kwargs):
6 by Max Shinn
Only one extra stack frame for all decorators
269
        # @accepts decorator
270
        if has_fun_prop(func, "argtypes") and has_fun_prop(func, "kwargtypes"):
271
            argtypes = get_fun_prop(func, "argtypes")
272
            kwargtypes = get_fun_prop(func, "kwargtypes")
273
            if argtypes:
9 by Max Shinn
Even better asserts
274
                if len(argtypes) != len(args):
275
                    raise ArgumentTypeError("Invalid argument specification to @accepts")
6 by Max Shinn
Only one extra stack frame for all decorators
276
                for i,t in enumerate(argtypes):
9 by Max Shinn
Even better asserts
277
                    try:
278
                        t.test(args[i])
279
                    except AssertionError as e:
280
                        raise ArgumentTypeError("Invalid argument type %s" % args[i])
6 by Max Shinn
Only one extra stack frame for all decorators
281
            if kwargtypes:
9 by Max Shinn
Even better asserts
282
                if all(k in kwargs.keys() for k in kwargtypes.keys()):
283
                    raise ArgumentTypeError("Invalid keyword argument specification to @accepts")
6 by Max Shinn
Only one extra stack frame for all decorators
284
                for k,t in kwargtypes.items():
9 by Max Shinn
Even better asserts
285
                    try:
286
                        t.test(kwargs[k])
287
                    except AssertionError as e:
288
                        raise ArgumentTypeError("Invalid argument type for %s: %s" % (k, kwargs[k]))
6 by Max Shinn
Only one extra stack frame for all decorators
289
        # @requires decorator
290
        if has_fun_prop(func, "requires"):
291
            argspec = inspect.getargspec(func)
292
            # Function named arguments
293
            #full_locals = locals().copy()
294
            #full_locals.update({k : v for k,v in zip(argspec.args, args)})
295
            full_locals = {k : v for k,v in zip(argspec.args, args)}
296
            # Unnamed positional arguments
297
            if argspec.varargs is not None:
298
                positional_args = {argspec.varargs: args[len(argspec.args):]}
299
                full_locals.update(positional_args)
300
            # TODO kw arguments
301
            for requirement in get_fun_prop(func, "requires"):
9 by Max Shinn
Even better asserts
302
                if not eval(requirement, globals(), full_locals):
303
                    raise EntryConditionsError("Function requirement '%s' failed" % requirement)
6 by Max Shinn
Only one extra stack frame for all decorators
304
        # The actual function
305
        returnvalue = func(*args, **kwargs)
306
        # @returns decorator
307
        if has_fun_prop(func, "returntype"):
9 by Max Shinn
Even better asserts
308
            try:
309
                get_fun_prop(func, "returntype").test(returnvalue)
310
            except AssertionError as e:
311
                raise ReturnTypeError("Invalid return type of %s" % returnvalue )
6 by Max Shinn
Only one extra stack frame for all decorators
312
        # @ensures decorator
313
        if has_fun_prop(func, "ensures"):
314
            argspec = inspect.getargspec(func)
315
            # Function named arguments
316
            limited_locals = {k : v for k,v in zip(argspec.args, args)}
8 by Max Shinn
Better exceptions
317
            # Return value
318
            limited_locals['__RETURN__'] = returnvalue
6 by Max Shinn
Only one extra stack frame for all decorators
319
            # Unnamed positional arguments
320
            if argspec.varargs is not None:
321
                positional_args = {argspec.varargs: args[len(argspec.args):]}
322
                limited_locals.update(positional_args)
10 by Max Shinn
Refer to previous execution values in ensures
323
            if any("`" in ens for ens in get_fun_prop(func, "ensures")) : # Cache if we refer to previous executions
324
                if has_fun_prop(func, "exec_cache"):
325
                   exec_cache = get_fun_prop(func, "exec_cache")
326
                else:
327
                   exec_cache = []
328
                exec_cache.append(limited_locals.copy())
329
                set_fun_prop(func, "exec_cache", exec_cache) #TODO is this line necessary?
6 by Max Shinn
Only one extra stack frame for all decorators
330
            # TODO kw arguments
8 by Max Shinn
Better exceptions
331
            for ensurement in get_fun_prop(func, "ensures"):
332
                e = ensurement.replace("return", "__RETURN__")
11 by Max Shinn
Changed from => to --> notation for implies so that it is not as easily confused with >= or <=
333
                if "<-->" in e:
334
                    e_parts = e.split("<-->")
10 by Max Shinn
Refer to previous execution values in ensures
335
                    assert len(e_parts) == 2, "Only one implies per statement"
336
                    e = "((%s) if (%s) else True) and ((%s) if (%s) else True)" % (e_parts[1], e_parts[0], e_parts[0], e_parts[1])
11 by Max Shinn
Changed from => to --> notation for implies so that it is not as easily confused with >= or <=
337
                if "-->" in e: # TODO won't throw error if --> and <--> are in e
338
                    e_parts = e.split("-->")
10 by Max Shinn
Refer to previous execution values in ensures
339
                    assert len(e_parts) == 2, "Only one implies per statement"
340
                    e = "(%s) if (%s) else True" % (e_parts[1], e_parts[0])
341
                if "`" in e:
342
                    bt = "__BACKTICK__"
343
                    exec_cache = get_fun_prop(func, "exec_cache")
344
                    for cache_item in exec_cache:
345
                        limited_locals.update({k+bt : v for k,v in cache_item.items()})
346
                        e = e.replace("`", bt)
347
                        if not eval(e, globals(), limited_locals):
348
                            raise ExitConditionsError("Ensures statement '%s' failed" % ensurement)
349
                elif not eval(e, globals(), limited_locals):
9 by Max Shinn
Even better asserts
350
                    raise ExitConditionsError("Ensures statement '%s' failed" % ensurement)
6 by Max Shinn
Only one extra stack frame for all decorators
351
        return returnvalue
352
    if has_fun_prop(func, "active"):
353
        return func
354
    else:
355
        set_fun_prop(func, "active", True)
356
        assign = functools.WRAPPER_ASSIGNMENTS + (_FUN_PROPS,)
14 by Max Shinn
Better documentation and code organization
357
        wrapped = functools.wraps(func, assigned=assign)(_decorated)
358
        if "__ALL_FUNCTIONS" in globals().keys():
359
            global __ALL_FUNCTIONS
360
            __ALL_FUNCTIONS.append(wrapped)
12 by Max Shinn
Added automatic unit tests
361
        return wrapped
14 by Max Shinn
Better documentation and code organization
362
    
6 by Max Shinn
Only one extra stack frame for all decorators
363
1 by Max Shinn
Initial commit
364
def accepts(*argtypes, **kwargtypes):
14 by Max Shinn
Better documentation and code organization
365
    argtypes = [TypeFactory(a) for a in argtypes]
366
    kwargtypes = {k : TypeFactory(a) for k,a in kwargtypes.items()}
367
    def _decorator(func):
368
        # @accepts decorator
369
        if argtypes is not None:
370
            if has_fun_prop(func, "argtypes"):
371
                raise ValueError("Cannot set argument types twice")
372
            set_fun_prop(func, "argtypes", argtypes)
373
        if kwargtypes is not None:
374
            if has_fun_prop(func, "kwargtypes"):
375
                raise ValueError("Cannot set set keyword argument types twice")
376
            set_fun_prop(func, "kwargtypes", kwargtypes)
377
        return _wrap(func)
378
    return _decorator
379
1 by Max Shinn
Initial commit
380
def returns(returntype):
14 by Max Shinn
Better documentation and code organization
381
    returntype = TypeFactory(returntype)
382
    def _decorator(func):
383
        # @returns decorator
384
        if has_fun_prop(func, "returntype"):
385
            raise ValueError("Cannot set return type twice")
386
        set_fun_prop(func, "returntype", returntype)
387
        return _wrap(func)
388
    return _decorator
389
            
3 by Max Shinn
Simple argument condition requirements
390
def requires(condition):
14 by Max Shinn
Better documentation and code organization
391
    def _decorator(func):
392
        # @requires decorator
393
        if has_fun_prop(func, "requires"):
394
            if not isinstance(get_fun_prop(func, "requires"), list):
395
                raise InternalError("Invalid requires structure")
396
            base_requires = get_fun_prop(func, "requires")
397
        else:
398
            base_requires = []
399
        set_fun_prop(func, "requires", [condition]+base_requires)
400
        return _wrap(func)
401
    return _decorator
402
8 by Max Shinn
Better exceptions
403
def ensures(condition):
14 by Max Shinn
Better documentation and code organization
404
    def _decorator(func):
405
    # @ensures decorator
406
        if has_fun_prop(func, "ensures"):
407
            if not isinstance(get_fun_prop(func, "ensures"), list):
408
                raise InternalError("Invalid ensures strucutre")
409
            base_ensures = get_fun_prop(func, "ensures")
410
        else:
411
            base_ensures = []
412
        set_fun_prop(func, "ensures", [condition]+base_ensures)
413
        return _wrap(func)
414
    return _decorator
1 by Max Shinn
Initial commit
415
2 by Max Shinn
Ranges, and, or, and cleanups
416
def test_function(func):
14 by Max Shinn
Better documentation and code organization
417
    """Perform a unit test of a single function.
418
419
    Create a series of test cases by finding the type of each of the
420
    function arguments, and creating all possible combinations of test
421
    cases for each type using the type's generate() function.  Then,
422
    test the function with each input.  This is roughly equivalent to
423
    just running the function with each input, because the exceptions
424
    should be caught at runtime.
425
426
    Assuming runtime checking is enabled (as it should be), running
427
    this on a function only tests it for inputs it is likely to
428
    encounter in the future.
429
    """
430
    # Ensure the user has specified argument types using the @accepts
431
    # decorator.
432
    assert has_fun_prop(func, "argtypes"), "No argument annotations"
433
    # Generate all combinations of arguments as test cases.  Some
434
    # argument types cannot be generated automatically.  If we
435
    # encounter one of these, unit testing won't work.
12 by Max Shinn
Added automatic unit tests
436
    try:
437
        testcases = itertools.product(*[e.generate() for e in get_fun_prop(func, "argtypes")])
438
    except NoGeneratorError:
14 by Max Shinn
Better documentation and code organization
439
        testcases = []
440
    if not testcases:
12 by Max Shinn
Added automatic unit tests
441
        print("Warning: %s could not be tested" % func.__name__)
14 by Max Shinn
Better documentation and code organization
442
        return
443
    # If entry conditions are met, simply running the function will be
444
    # enough of a test, since all values are checked at runtime.  So
445
    # execute the function once for each combination of arguments.
446
    for tc in testcases:
447
        try:
448
            func(*tc)
449
        except EntryConditionsError:
450
            continue
451
452
# If called as "python3 -m verify script_file_name.py", then unit test
453
# script_file_name.py.  By this, we mean call the function
454
# test_function on each function which has annotations.  Note that we
455
# must execute the entire file script_file_name.py in order to do
456
# this, so any time-intensive code should be in a __name__ ==
457
# "__main__" guard, which will not be executed.
458
#
459
# We know if a function has annotations because all annotations are
460
# passed through the _decorator function (so that we can ensure we
461
# only use at most one more stack frame no matter how many function
462
# annotations we have).  The _decorator function will look for the
463
# global variable __ALL_FUNCTIONS to be defined within the verify
464
# module.  If it is defined, it will add one copy of each decorated
465
# function to the list.  Then, we can perform the unit test by
466
# iterating through each of these.  This has the advantage of allowing
467
# nested functions and object methods to be discovered for
468
# verification.
12 by Max Shinn
Added automatic unit tests
469
if __name__ == "__main__":
14 by Max Shinn
Better documentation and code organization
470
    # Pass exactly one argement: the python file to check
471
    if len(sys.argv) != 2: # len == 2 because the path to the module is arg 1.
472
        exit("Invalid argument, please pass a python file")
473
    # Add the __ALL_FUNCTIONS to the global scope of the verify module
474
    # and then run the script.  Save the global variables from script
475
    # execution so that we can find the __ALL_FUNCTIONS variable once
476
    # the script has finished executing.
477
    globs = {} # Global variables from script execution
478
    prefix = "import verify as __verifymod\n__verifymod.__ALL_FUNCTIONS = []\n"
479
    exec(prefix+open(sys.argv[1], "r").read(), globs)
480
    all_functions = globs["__verifymod"].__ALL_FUNCTIONS
481
    # Test each function from the script.
12 by Max Shinn
Added automatic unit tests
482
    for f in all_functions:
14 by Max Shinn
Better documentation and code organization
483
        assert hasattr(f, _FUN_PROPS), "Internal error"
484
        # Some function executions might take a while, so we print to
485
        # the screen when we begin testing a function, and then erase
486
        # it after we finish testing the function.  This is like a
487
        # make-shift progress bar.
488
        start_text = "    Testing %s..." % f.__name__
489
        print(start_text, end="", flush=True)
12 by Max Shinn
Added automatic unit tests
490
        test_function(f)
14 by Max Shinn
Better documentation and code organization
491
        import time
492
        time.sleep(1)
493
        # Extra spaces after "Tested %s" compensate for the fact that
494
        # the string to signal the start of testing function f is
495
        # longer than the string to signal function f has been tested,
496
        # so this avoids leaving extra characters on the terminal.
497
        print("\b"*len(start_text)+"    Tested %s    " % f.__name__, flush=True)
498
    print("Tested %i functions in %s." % (len(all_functions), sys.argv[1]))