optimism 3.1.2__tar.gz → 3.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {optimism-3.1.2 → optimism-3.1.4}/PKG-INFO +3 -2
- {optimism-3.1.2 → optimism-3.1.4}/optimism/__init__.py +1 -1
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/code.py +16 -0
- optimism-3.1.4/optimism/examples/memoryReports.py +25 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/optimism.py +109 -15
- {optimism-3.1.2 → optimism-3.1.4}/optimism/test_examples.py +109 -60
- {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/PKG-INFO +3 -2
- optimism-3.1.2/optimism/examples/memoryReports.py +0 -9
- {optimism-3.1.2 → optimism-3.1.4}/LICENSE.txt +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/README.md +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/a.txt +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/b.txt +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/bad.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/basic.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/bits.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/block.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/catch_and_release.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/core.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/doc_example.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/doctests.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/errors.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/files.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/fragments.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/io_example.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/literals.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/multi.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/reverse_tracing.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/runfile.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/skip.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/structure.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/suites.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/tracing.py +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism/py.typed +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/SOURCES.txt +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/dependency_links.txt +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/requires.txt +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/top_level.txt +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/pyproject.toml +0 -0
- {optimism-3.1.2 → optimism-3.1.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: optimism
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.4
|
|
4
4
|
Summary: Very simple testing framework suitable for beginner programmers, with easy functions for accomplishing most common testing tasks like providing input. Requires Python 3.8+.
|
|
5
5
|
Home-page: https://cs.wellesley.edu/~pmwh/optimism/docs/optimism.html
|
|
6
6
|
Author: Peter Mawhorter
|
|
@@ -19,6 +19,7 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
License-File: LICENSE.txt
|
|
20
20
|
Provides-Extra: test
|
|
21
21
|
Requires-Dist: pytest; extra == "test"
|
|
22
|
+
Dynamic: license-file
|
|
22
23
|
|
|
23
24
|
# `optimism`
|
|
24
25
|
|
|
@@ -13,7 +13,7 @@ from .optimism import *
|
|
|
13
13
|
# version from optimism, but we want the version to be defined in that
|
|
14
14
|
# file so that a single-file distribution is equivalent to this packaged
|
|
15
15
|
# distribution. So we define a version here as well, but...
|
|
16
|
-
__version__ = "3.1.
|
|
16
|
+
__version__ = "3.1.4"
|
|
17
17
|
|
|
18
18
|
# ...then we assert it matches so testing will catch it if I forget to
|
|
19
19
|
# keep them in sync before a release. T_T
|
|
@@ -94,3 +94,19 @@ fileTester.checkCodeContains(optimism.Constant(4.0 / 2, float)) # ✗
|
|
|
94
94
|
fileTester.checkCodeContains(optimism.Constant(4.0 / 2, int)) # ✓
|
|
95
95
|
fileTester.checkCodeContains(optimism.Constant(5.0, int)) # ✗
|
|
96
96
|
fileTester.checkCodeContains(optimism.Constant(5.0, float)) # ✓
|
|
97
|
+
|
|
98
|
+
# Test augmented assignment matching
|
|
99
|
+
def aug():
|
|
100
|
+
x = 3
|
|
101
|
+
x += 4
|
|
102
|
+
x /= 10
|
|
103
|
+
|
|
104
|
+
fileTester.checkCodeContains(
|
|
105
|
+
optimism.Def().contains(optimism.Operator('+'))
|
|
106
|
+
) # ✓
|
|
107
|
+
fileTester.checkCodeContains(
|
|
108
|
+
optimism.Def().contains(optimism.Operator('/'))
|
|
109
|
+
) # ✓
|
|
110
|
+
fileTester.checkCodeContains(
|
|
111
|
+
optimism.Def(name="aug").contains(optimism.Operator('/', max=0))
|
|
112
|
+
) # ✗
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import optimism as opt
|
|
2
|
+
|
|
3
|
+
words = ['hi', 'bye']
|
|
4
|
+
pair = [words, words]
|
|
5
|
+
print(opt.memoryReport(words=words, pair=pair))
|
|
6
|
+
|
|
7
|
+
knot = [pair, words]
|
|
8
|
+
knot.append(knot)
|
|
9
|
+
print(opt.memoryReport(pair=pair, words=words, knot=knot))
|
|
10
|
+
|
|
11
|
+
code = """\
|
|
12
|
+
a = []
|
|
13
|
+
b = []
|
|
14
|
+
b.append([])
|
|
15
|
+
a.append(b)
|
|
16
|
+
a.append([])
|
|
17
|
+
import math # don't allow deep copy of env for this code
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
tester = opt.testBlock(code)
|
|
21
|
+
case = tester.case()
|
|
22
|
+
case.checkVariableStructure('a', [[[]], []])
|
|
23
|
+
case.checkCustom(
|
|
24
|
+
lambda result: result['scope']['a'][0] is result['scope']['b']
|
|
25
|
+
)
|
|
@@ -200,6 +200,18 @@ TODO: Workaround for tracing in interactive console?
|
|
|
200
200
|
|
|
201
201
|
## Changelog
|
|
202
202
|
|
|
203
|
+
- Version 3.1.4 treats augmented assignments as operators for
|
|
204
|
+
`checkCodeContains` purposes, as they should have been all along. It
|
|
205
|
+
adds a new test for that in the `code.py` example file, and also fixes
|
|
206
|
+
up some modern mypy type warnings (even though they were mostly
|
|
207
|
+
spurious).
|
|
208
|
+
- Version 3.1.3 Fixes a numbering bug with memory reports and also fixes
|
|
209
|
+
a pernicious `deepish_copy` bug: Structures that were supposed to be
|
|
210
|
+
duplicated would have their top levels duplicated twice separately
|
|
211
|
+
because the memo from `deepish_copy` was not being passed into
|
|
212
|
+
`deepcopy` (of course, fixing that the simple way caused more problems
|
|
213
|
+
when `deepcopy` would update the memo before erroring out). New tests
|
|
214
|
+
to catch these bugs were added, including doctests for `deepish_copy`.
|
|
203
215
|
- Version 3.1.2 Adds shims to support Python versions 3.8-3.10 again,
|
|
204
216
|
rather than just 3.11+.
|
|
205
217
|
- Version 3.1.1 reformulates the module as a package so that it can add
|
|
@@ -346,7 +358,7 @@ TODO: Workaround for tracing in interactive console?
|
|
|
346
358
|
|
|
347
359
|
# TODO: Cache compiled ASTs!
|
|
348
360
|
|
|
349
|
-
__version__ = "3.1.
|
|
361
|
+
__version__ = "3.1.4"
|
|
350
362
|
|
|
351
363
|
from typing import (
|
|
352
364
|
Any, Dict, List, Optional, Tuple, Literal as LiteralType, Callable,
|
|
@@ -371,7 +383,7 @@ SPLIT_CONSTANTS = hasattr(ast, "Num")
|
|
|
371
383
|
|
|
372
384
|
# Type is required before 3.9 but deprecated in that version
|
|
373
385
|
if sys.version_info >= (3, 9):
|
|
374
|
-
|
|
386
|
+
from builtins import type as Type
|
|
375
387
|
else:
|
|
376
388
|
from typing import Type
|
|
377
389
|
|
|
@@ -390,19 +402,19 @@ except ImportError:
|
|
|
390
402
|
def __getitem__(self, _):
|
|
391
403
|
return Any
|
|
392
404
|
|
|
393
|
-
Unpack = UnpackShim()
|
|
405
|
+
Unpack = UnpackShim() # type: ignore
|
|
394
406
|
|
|
395
407
|
# Versions of Python before 3.10 don't support TypeAlias
|
|
396
408
|
try:
|
|
397
409
|
from typing import TypeAlias
|
|
398
410
|
except ImportError:
|
|
399
|
-
TypeAlias = Type
|
|
411
|
+
TypeAlias = Type # type: ignore
|
|
400
412
|
|
|
401
413
|
# Versions of Python before 3.10 don't support NoneType
|
|
402
414
|
try:
|
|
403
415
|
from types import NoneType
|
|
404
416
|
except ImportError:
|
|
405
|
-
NoneType = type(None)
|
|
417
|
+
NoneType = type(None) # type: ignore
|
|
406
418
|
|
|
407
419
|
#----------------#
|
|
408
420
|
# Monkey Patches #
|
|
@@ -723,7 +735,11 @@ Type variable for `ClassInfo` definition.
|
|
|
723
735
|
|
|
724
736
|
|
|
725
737
|
if sys.version_info > (3, 10):
|
|
726
|
-
ClassInfo: TypeAlias = Union[
|
|
738
|
+
ClassInfo: TypeAlias = Union[
|
|
739
|
+
Type[T],
|
|
740
|
+
Tuple['ClassInfo', ...],
|
|
741
|
+
Type[Union]
|
|
742
|
+
]
|
|
727
743
|
else:
|
|
728
744
|
ClassInfo: TypeAlias = Union[Type[T], Tuple['ClassInfo', ...]]
|
|
729
745
|
"""
|
|
@@ -3906,7 +3922,7 @@ class QuietSkipManager(TestManager[SilentCase]):
|
|
|
3906
3922
|
# Test factories #
|
|
3907
3923
|
#----------------#
|
|
3908
3924
|
|
|
3909
|
-
def testFunction(fn: FunctionType) -> FunctionManager:
|
|
3925
|
+
def testFunction(fn: Union[FunctionType, Callable]) -> FunctionManager:
|
|
3910
3926
|
"""
|
|
3911
3927
|
Creates a test-manager for the given function.
|
|
3912
3928
|
"""
|
|
@@ -5998,7 +6014,9 @@ class Loop(ASTRequirement):
|
|
|
5998
6014
|
"""
|
|
5999
6015
|
def __init__(
|
|
6000
6016
|
self,
|
|
6001
|
-
only: Optional[
|
|
6017
|
+
only: Optional[
|
|
6018
|
+
Union[ExtendedLoopType, Iterable[ExtendedLoopType]]
|
|
6019
|
+
] = None,
|
|
6002
6020
|
**kwargs: Unpack[NumArgs]
|
|
6003
6021
|
) -> None:
|
|
6004
6022
|
"""
|
|
@@ -6860,7 +6878,13 @@ class Operator(ASTRequirement):
|
|
|
6860
6878
|
) -> Iterator[Tuple[ast.AST, bool]]:
|
|
6861
6879
|
for node in self._walkNodesOfType(
|
|
6862
6880
|
syntax_tree,
|
|
6863
|
-
(
|
|
6881
|
+
(
|
|
6882
|
+
ast.UnaryOp,
|
|
6883
|
+
ast.BinOp,
|
|
6884
|
+
ast.BoolOp,
|
|
6885
|
+
ast.Compare,
|
|
6886
|
+
ast.AugAssign
|
|
6887
|
+
)
|
|
6864
6888
|
):
|
|
6865
6889
|
# Determine not/partial/full status of match...
|
|
6866
6890
|
match: LiteralType[True, "partial", False] = False
|
|
@@ -7139,7 +7163,7 @@ which has slots that used to contain complex sub-objects replaced with
|
|
|
7139
7163
|
`MemReference` objects. See `memoryMap`.
|
|
7140
7164
|
"""
|
|
7141
7165
|
|
|
7142
|
-
SmallTypes: Tuple[Type] = (int, float, complex, bool, NoneType)
|
|
7166
|
+
SmallTypes: Tuple[Type, ...] = (int, float, complex, bool, NoneType)
|
|
7143
7167
|
"""
|
|
7144
7168
|
The object types which are small enough to fit into a single field
|
|
7145
7169
|
without using a pointer in theory, which we thus represent without using
|
|
@@ -7150,7 +7174,7 @@ with `isinstance`.
|
|
|
7150
7174
|
# later so it cannot just be None. If we only wanted to support Python
|
|
7151
7175
|
# 3.10+, we could use the union directly in isinstance.
|
|
7152
7176
|
|
|
7153
|
-
SmallType: TypeAlias = Union[int, float, complex, bool,
|
|
7177
|
+
SmallType: TypeAlias = Union[int, float, complex, bool, None]
|
|
7154
7178
|
"""
|
|
7155
7179
|
A union of the `SmallTypes`.
|
|
7156
7180
|
"""
|
|
@@ -7224,6 +7248,7 @@ def memoryMap(
|
|
|
7224
7248
|
or (INLINE_STRINGS_IN_MEMORY_REPORTS and isinstance(obj, str))
|
|
7225
7249
|
):
|
|
7226
7250
|
# Simple values are used as-is:
|
|
7251
|
+
obj = cast(SmallType, obj)
|
|
7227
7252
|
return None, obj
|
|
7228
7253
|
elif isinstance(obj, (tuple, list, set)):
|
|
7229
7254
|
# Structures are made shallow and referenced
|
|
@@ -7270,7 +7295,7 @@ def memoryMap(
|
|
|
7270
7295
|
# not made shallow
|
|
7271
7296
|
assigned[id(obj)] = (my_ref, obj)
|
|
7272
7297
|
if count_from > 0:
|
|
7273
|
-
return count_from, my_ref
|
|
7298
|
+
return count_from - 1, my_ref
|
|
7274
7299
|
else:
|
|
7275
7300
|
return None, my_ref
|
|
7276
7301
|
|
|
@@ -7341,6 +7366,16 @@ def memoryReport(*objs: Any, **named: Any) -> str:
|
|
|
7341
7366
|
>>> print(memoryReport(x=5, y=12.0), end='')
|
|
7342
7367
|
x: 5
|
|
7343
7368
|
y: 12.0
|
|
7369
|
+
>>> a = ['hi', 'bye']
|
|
7370
|
+
>>> b = [a, a]
|
|
7371
|
+
>>> inlineStringsInMemoryReports(False)
|
|
7372
|
+
>>> print(memoryReport(a=a, b=b), end='')
|
|
7373
|
+
a: @0
|
|
7374
|
+
b: @3
|
|
7375
|
+
@0: [@1, @2]
|
|
7376
|
+
@3: [@0, @0]
|
|
7377
|
+
@1: 'hi'
|
|
7378
|
+
@2: 'bye'
|
|
7344
7379
|
"""
|
|
7345
7380
|
refs: MemoryMap = {}
|
|
7346
7381
|
top = -1
|
|
@@ -7358,6 +7393,7 @@ def memoryReport(*objs: Any, **named: Any) -> str:
|
|
|
7358
7393
|
|
|
7359
7394
|
# Order by named values (in order given) and then numeric values (by
|
|
7360
7395
|
# number assigned)
|
|
7396
|
+
# TODO: Consider ordering name refs by their names?
|
|
7361
7397
|
|
|
7362
7398
|
result = ''
|
|
7363
7399
|
handled = set()
|
|
@@ -7861,7 +7897,7 @@ def reportObjDiffs(
|
|
|
7861
7897
|
rev = [key for key in env if env[key] == cObj][0]
|
|
7862
7898
|
yield (
|
|
7863
7899
|
f"{prefix}: reference {repr(aObj)} cannot match"
|
|
7864
|
-
f" {repr(cObj)} because {repr(
|
|
7900
|
+
f" {repr(cObj)} because {repr(cObj)} is already"
|
|
7865
7901
|
f" assigned as the match for {repr(rev)}. This"
|
|
7866
7902
|
f" means you have used two references to"
|
|
7867
7903
|
f" different objects where the correct solution"
|
|
@@ -9863,6 +9899,11 @@ def get_ref_src(src: str, node: ast.AST) -> str:
|
|
|
9863
9899
|
|
|
9864
9900
|
# We're going to have to do this ourself: find the start of the
|
|
9865
9901
|
# expression and state-machine to find its end
|
|
9902
|
+
if not hasattr(node, "lineno") or not hasattr(node, "col_offset"):
|
|
9903
|
+
raise TypeError(
|
|
9904
|
+
f"Node {node!r} is missing lineno and/or col_offset"
|
|
9905
|
+
f" attribute(s)."
|
|
9906
|
+
)
|
|
9866
9907
|
start = get_src_index(src, node.lineno, node.col_offset)
|
|
9867
9908
|
|
|
9868
9909
|
# Figure out the end point
|
|
@@ -9913,16 +9954,69 @@ def deepish_copy(obj: T, memo: Optional[dict] = None) -> T:
|
|
|
9913
9954
|
copy.deepcopy wherever possible and making shallower copies
|
|
9914
9955
|
elsewhere. Basically a middle-ground between copy.deepcopy and
|
|
9915
9956
|
copy.copy.
|
|
9957
|
+
|
|
9958
|
+
TODO: Why does this break in a notebook?!?
|
|
9959
|
+
|
|
9960
|
+
For example:
|
|
9961
|
+
|
|
9962
|
+
>>> import math # copy module is not deep-copyable
|
|
9963
|
+
>>> stuff = ['a', math]
|
|
9964
|
+
>>> outside = [stuff, math, stuff]
|
|
9965
|
+
>>> copy.deepcopy(outside) # can't copy list w/ module inside
|
|
9966
|
+
Traceback (most recent call last):
|
|
9967
|
+
...
|
|
9968
|
+
TypeError...
|
|
9969
|
+
>>> clone = deepish_copy(outside)
|
|
9970
|
+
>>> clone is outside
|
|
9971
|
+
False
|
|
9972
|
+
>>> clone # doctest: +ELLIPSIS
|
|
9973
|
+
[['a', <module 'math'...>], <module 'math'...>, ['a', <module 'math'...>]]
|
|
9974
|
+
>>> outside # doctest: +ELLIPSIS
|
|
9975
|
+
[['a', <module 'math'...>], <module 'math'...>, ['a', <module 'math'...>]]
|
|
9976
|
+
>>> clone == outside
|
|
9977
|
+
True
|
|
9978
|
+
>>> clone[0] is stuff
|
|
9979
|
+
False
|
|
9980
|
+
>>> clone[0] == stuff
|
|
9981
|
+
True
|
|
9982
|
+
>>> clone[1] is math # can't copy modules, so it is tangled
|
|
9983
|
+
True
|
|
9984
|
+
>>> clone[1] == math
|
|
9985
|
+
True
|
|
9986
|
+
>>> clone[0][0] == 'a'
|
|
9987
|
+
True
|
|
9988
|
+
>>> clone[0][1] is math
|
|
9989
|
+
True
|
|
9990
|
+
>>> clone[1] is clone[0][1] # alias preserved
|
|
9991
|
+
True
|
|
9992
|
+
>>> clone[0] is clone[2]
|
|
9993
|
+
True
|
|
9994
|
+
>>> # Now a test with a dictionary
|
|
9995
|
+
>>> d = {'a': outside, 'b': stuff}
|
|
9996
|
+
>>> copy.deepcopy(d) # can't copy dictionary w/ module inside
|
|
9997
|
+
Traceback (most recent call last):
|
|
9998
|
+
...
|
|
9999
|
+
TypeError...
|
|
10000
|
+
>>> dc = deepish_copy(d)
|
|
10001
|
+
>>> dc['a'] is outside
|
|
10002
|
+
False
|
|
10003
|
+
>>> dc['a'] == outside
|
|
10004
|
+
True
|
|
10005
|
+
>>> dc['a'][0] is dc['b']
|
|
10006
|
+
True
|
|
10007
|
+
>>> dc['a'][2] is dc['b']
|
|
10008
|
+
True
|
|
9916
10009
|
"""
|
|
9917
10010
|
if memo is None:
|
|
9918
10011
|
memo = {}
|
|
9919
10012
|
if id(obj) in memo:
|
|
9920
10013
|
return memo[id(obj)]
|
|
9921
|
-
|
|
9922
10014
|
result: T
|
|
9923
10015
|
try:
|
|
9924
|
-
|
|
10016
|
+
save = copy.copy(memo)
|
|
10017
|
+
result = copy.deepcopy(obj, save) # TODO: Is memo always compatible?
|
|
9925
10018
|
# not sure about memo dict compatibility
|
|
10019
|
+
memo.update(save) # if we didn't crash out, update real memo
|
|
9926
10020
|
memo[id(obj)] = result
|
|
9927
10021
|
return result
|
|
9928
10022
|
|
|
@@ -29,7 +29,8 @@ EXAMPLES = [
|
|
|
29
29
|
("suites", []),
|
|
30
30
|
("literals", []),
|
|
31
31
|
("structure", []),
|
|
32
|
-
("doctests", [])
|
|
32
|
+
("doctests", []),
|
|
33
|
+
("memoryReports", [])
|
|
33
34
|
#("reverse_tracing", []),
|
|
34
35
|
# TODO: Include this once the machinery for it is there
|
|
35
36
|
]
|
|
@@ -517,8 +518,9 @@ All 4 expectation(s) were met.
|
|
|
517
518
|
Code does not contain the expected structure:
|
|
518
519
|
at least 1 definition(s) of functionName
|
|
519
520
|
Although it does partially satisfy the requirement:
|
|
520
|
-
Requirement partially satisfied via 0 full and
|
|
521
|
+
Requirement partially satisfied via 0 full and 2 partial match(es):
|
|
521
522
|
Partial match: FunctionDef on line 10
|
|
523
|
+
Partial match: FunctionDef on line 99
|
|
522
524
|
checked code in file '{full_file}'""",
|
|
523
525
|
"✓ {file}:44",
|
|
524
526
|
"""\
|
|
@@ -527,8 +529,9 @@ All 4 expectation(s) were met.
|
|
|
527
529
|
at least 1 function definition(s) that also contain(s):
|
|
528
530
|
at least 1 loop(s) or generator expression(s)
|
|
529
531
|
Although it does partially satisfy the requirement:
|
|
530
|
-
Requirement partially satisfied via 0 full and
|
|
532
|
+
Requirement partially satisfied via 0 full and 2 partial match(es):
|
|
531
533
|
Partial match: FunctionDef on line 10
|
|
534
|
+
Partial match: FunctionDef on line 99
|
|
532
535
|
checked code in file '{full_file}'""",
|
|
533
536
|
"""\
|
|
534
537
|
✗ {file}:46
|
|
@@ -600,24 +603,36 @@ All 4 expectation(s) were met.
|
|
|
600
603
|
Code does not contain the expected structure:
|
|
601
604
|
at least 1 constant 2.0
|
|
602
605
|
Although it does partially satisfy the requirement:
|
|
603
|
-
Requirement partially satisfied via 0 full and
|
|
606
|
+
Requirement partially satisfied via 0 full and 60 partial match(es):
|
|
604
607
|
""" + ("""\
|
|
605
608
|
Partial match: Constant on line 1
|
|
606
609
|
Partial match: Constant on line 7""" if better_line_numbers else """\
|
|
607
610
|
Partial match: Str on line 3
|
|
608
|
-
Partial match: NameConstant on line 7"""), # not listing all
|
|
611
|
+
Partial match: NameConstant on line 7"""), # not listing all 60...
|
|
609
612
|
"✓ {file}:94",
|
|
610
613
|
"""\
|
|
611
614
|
✗ {file}:95
|
|
612
615
|
Code does not contain the expected structure:
|
|
613
616
|
at least 1 constant 5.0
|
|
614
617
|
Although it does partially satisfy the requirement:
|
|
615
|
-
Requirement partially satisfied via 0 full and
|
|
618
|
+
Requirement partially satisfied via 0 full and 60 partial match(es):
|
|
616
619
|
""" + ("""\
|
|
617
620
|
Partial match: Constant on line 1
|
|
618
621
|
Partial match: Constant on line 7""" if better_line_numbers else """\
|
|
619
622
|
Partial match: Str on line 3
|
|
620
|
-
Partial match: NameConstant on line 7"""), # not listing all
|
|
623
|
+
Partial match: NameConstant on line 7"""), # not listing all 60...
|
|
624
|
+
"✓ {file}:104",
|
|
625
|
+
"✓ {file}:107",
|
|
626
|
+
"""\
|
|
627
|
+
✗ {file}:110
|
|
628
|
+
Code does not contain the expected structure:
|
|
629
|
+
at least 1 definition(s) of aug that also contain(s):
|
|
630
|
+
no operator '/'
|
|
631
|
+
Although it does partially satisfy the requirement:
|
|
632
|
+
Requirement partially satisfied via 0 full and 2 partial match(es):
|
|
633
|
+
Partial match: FunctionDef on line 99
|
|
634
|
+
Partial match: FunctionDef on line 10
|
|
635
|
+
"""
|
|
621
636
|
],
|
|
622
637
|
|
|
623
638
|
"reverse_tracing": [
|
|
@@ -1247,6 +1262,10 @@ All 4 expectation(s) were met.
|
|
|
1247
1262
|
Found only 2 matching distinct doctest example(s) (required 3).
|
|
1248
1263
|
checked code in file 'doc_example.py'
|
|
1249
1264
|
"""
|
|
1265
|
+
],
|
|
1266
|
+
"memoryReports": [
|
|
1267
|
+
"✓ {file}:22",
|
|
1268
|
+
"✓ {file}:23",
|
|
1250
1269
|
]
|
|
1251
1270
|
}
|
|
1252
1271
|
|
|
@@ -1290,6 +1309,27 @@ Outcomes in suite A (again):
|
|
|
1290
1309
|
"No more outcomes in suite A: []",
|
|
1291
1310
|
"No more outcomes in suite B: []",
|
|
1292
1311
|
"No more suite B.",
|
|
1312
|
+
],
|
|
1313
|
+
|
|
1314
|
+
"memoryReports": [
|
|
1315
|
+
"""\
|
|
1316
|
+
words: @0
|
|
1317
|
+
pair: @3
|
|
1318
|
+
@0: [@1, @2]
|
|
1319
|
+
@3: [@0, @0]
|
|
1320
|
+
@1: 'hi'
|
|
1321
|
+
@2: 'bye'
|
|
1322
|
+
""",
|
|
1323
|
+
"""\
|
|
1324
|
+
pair: @0
|
|
1325
|
+
words: @1
|
|
1326
|
+
knot: @4
|
|
1327
|
+
@0: [@1, @1]
|
|
1328
|
+
@1: [@2, @3]
|
|
1329
|
+
@4: [@0, @1, @4]
|
|
1330
|
+
@2: 'hi'
|
|
1331
|
+
@3: 'bye'
|
|
1332
|
+
""",
|
|
1293
1333
|
]
|
|
1294
1334
|
}
|
|
1295
1335
|
|
|
@@ -1334,63 +1374,72 @@ def test_all():
|
|
|
1334
1374
|
Tests each example file. The `examples` directory must be in the
|
|
1335
1375
|
same place that this `test_examples.py` file lives.
|
|
1336
1376
|
"""
|
|
1337
|
-
os.
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
r"Traceback \(most recent call last\):.*?^([ a-zA-Z]*Error:)",
|
|
1355
|
-
"Traceback omitted\n\\1",
|
|
1356
|
-
output,
|
|
1357
|
-
flags=re.MULTILINE | re.DOTALL
|
|
1358
|
-
)
|
|
1359
|
-
#output = re.sub(r"Traceback \(most recent call last\).*?NameError:", "HAHAHA", output, flags=re.MULTILINE | re.DOTALL)
|
|
1360
|
-
# Scrub line numbers from tracebacks so our expectations don't
|
|
1361
|
-
# become hopelessly fragile
|
|
1362
|
-
stdout = re.sub(r", line \d+,", ", line X,", stdout)
|
|
1363
|
-
output = re.sub(r", line \d+,", ", line X,", output)
|
|
1364
|
-
for ex in EXPECTATIONS[mname]:
|
|
1365
|
-
exp = ex.format(
|
|
1366
|
-
file=os.path.basename(module.__file__),
|
|
1367
|
-
dir=(
|
|
1368
|
-
os.path.split(module.__file__)[0] + '/'
|
|
1369
|
-
if better_line_numbers
|
|
1370
|
-
else ''
|
|
1371
|
-
),
|
|
1372
|
-
full_file=module.__file__,
|
|
1373
|
-
opt_file=optimism.__file__,
|
|
1377
|
+
olddir = os.getcwd()
|
|
1378
|
+
try:
|
|
1379
|
+
os.chdir(os.path.join(os.path.dirname(__file__), "examples"))
|
|
1380
|
+
sys.path.insert(0, '.')
|
|
1381
|
+
for (mname, inputs) in EXAMPLES:
|
|
1382
|
+
optimism.detailLevel(0)
|
|
1383
|
+
optimism.deleteAllTestSuites()
|
|
1384
|
+
optimism.colors(False)
|
|
1385
|
+
module, output, stdout = run_example(mname, inputs)
|
|
1386
|
+
# Replace tracebacks with generic forms so we can match on
|
|
1387
|
+
# specific errors being reported while ignoring the files & lines
|
|
1388
|
+
# where they are reported.
|
|
1389
|
+
stdout = re.sub(
|
|
1390
|
+
r"Traceback \(most recent call last\):.*?^([ a-zA-Z]*Error:)",
|
|
1391
|
+
"Traceback omitted\n\\1",
|
|
1392
|
+
stdout,
|
|
1393
|
+
flags=re.MULTILINE | re.DOTALL
|
|
1374
1394
|
)
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
for ex in
|
|
1395
|
+
output = re.sub(
|
|
1396
|
+
r"Traceback \(most recent call last\):.*?^([ a-zA-Z]*Error:)",
|
|
1397
|
+
"Traceback omitted\n\\1",
|
|
1398
|
+
output,
|
|
1399
|
+
flags=re.MULTILINE | re.DOTALL
|
|
1400
|
+
)
|
|
1401
|
+
#output = re.sub(r"Traceback \(most recent call last\).*?NameError:", "HAHAHA", output, flags=re.MULTILINE | re.DOTALL)
|
|
1402
|
+
# Scrub line numbers from tracebacks so our expectations don't
|
|
1403
|
+
# become hopelessly fragile
|
|
1404
|
+
stdout = re.sub(r", line \d+,", ", line X,", stdout)
|
|
1405
|
+
output = re.sub(r", line \d+,", ", line X,", output)
|
|
1406
|
+
for ex in EXPECTATIONS[mname]:
|
|
1387
1407
|
exp = ex.format(
|
|
1388
1408
|
file=os.path.basename(module.__file__),
|
|
1389
|
-
dir=
|
|
1409
|
+
dir=(
|
|
1410
|
+
os.path.split(module.__file__)[0] + '/'
|
|
1411
|
+
if better_line_numbers
|
|
1412
|
+
else ''
|
|
1413
|
+
),
|
|
1390
1414
|
full_file=module.__file__,
|
|
1391
1415
|
opt_file=optimism.__file__,
|
|
1392
1416
|
)
|
|
1417
|
+
# Redundant, but makes issues easier to diagnose
|
|
1393
1418
|
for line in exp.splitlines():
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1419
|
+
if line not in output:
|
|
1420
|
+
print("\n\nLINE:\n" + line)
|
|
1421
|
+
print("\n\nOUTPUT:\n" + output)
|
|
1422
|
+
assert (line in output), (line, output)
|
|
1423
|
+
if exp not in output:
|
|
1424
|
+
print("\n\nEXP:\n" + exp)
|
|
1425
|
+
print("\n\nOUTPUT:\n" + output)
|
|
1426
|
+
assert (exp in output), (exp, output)
|
|
1427
|
+
if mname in STDOUT_EXPECT:
|
|
1428
|
+
for ex in STDOUT_EXPECT[mname]:
|
|
1429
|
+
exp = ex.format(
|
|
1430
|
+
file=os.path.basename(module.__file__),
|
|
1431
|
+
dir=os.path.split(module.__file__)[0],
|
|
1432
|
+
full_file=module.__file__,
|
|
1433
|
+
opt_file=optimism.__file__,
|
|
1434
|
+
)
|
|
1435
|
+
for line in exp.splitlines():
|
|
1436
|
+
assert (line in stdout), stdout
|
|
1437
|
+
assert (exp in stdout), stdout
|
|
1438
|
+
finally:
|
|
1439
|
+
os.chdir(olddir)
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
if __name__ == "__main__":
|
|
1443
|
+
print('Testing example files...')
|
|
1444
|
+
test_all()
|
|
1445
|
+
print('...all tests passed.')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: optimism
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.4
|
|
4
4
|
Summary: Very simple testing framework suitable for beginner programmers, with easy functions for accomplishing most common testing tasks like providing input. Requires Python 3.8+.
|
|
5
5
|
Home-page: https://cs.wellesley.edu/~pmwh/optimism/docs/optimism.html
|
|
6
6
|
Author: Peter Mawhorter
|
|
@@ -19,6 +19,7 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
License-File: LICENSE.txt
|
|
20
20
|
Provides-Extra: test
|
|
21
21
|
Requires-Dist: pytest; extra == "test"
|
|
22
|
+
Dynamic: license-file
|
|
22
23
|
|
|
23
24
|
# `optimism`
|
|
24
25
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|