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.
Files changed (39) hide show
  1. {optimism-3.1.2 → optimism-3.1.4}/PKG-INFO +3 -2
  2. {optimism-3.1.2 → optimism-3.1.4}/optimism/__init__.py +1 -1
  3. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/code.py +16 -0
  4. optimism-3.1.4/optimism/examples/memoryReports.py +25 -0
  5. {optimism-3.1.2 → optimism-3.1.4}/optimism/optimism.py +109 -15
  6. {optimism-3.1.2 → optimism-3.1.4}/optimism/test_examples.py +109 -60
  7. {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/PKG-INFO +3 -2
  8. optimism-3.1.2/optimism/examples/memoryReports.py +0 -9
  9. {optimism-3.1.2 → optimism-3.1.4}/LICENSE.txt +0 -0
  10. {optimism-3.1.2 → optimism-3.1.4}/README.md +0 -0
  11. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/a.txt +0 -0
  12. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/b.txt +0 -0
  13. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/bad.py +0 -0
  14. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/basic.py +0 -0
  15. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/bits.py +0 -0
  16. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/block.py +0 -0
  17. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/catch_and_release.py +0 -0
  18. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/core.py +0 -0
  19. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/doc_example.py +0 -0
  20. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/doctests.py +0 -0
  21. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/errors.py +0 -0
  22. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/files.py +0 -0
  23. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/fragments.py +0 -0
  24. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/io_example.py +0 -0
  25. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/literals.py +0 -0
  26. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/multi.py +0 -0
  27. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/reverse_tracing.py +0 -0
  28. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/runfile.py +0 -0
  29. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/skip.py +0 -0
  30. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/structure.py +0 -0
  31. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/suites.py +0 -0
  32. {optimism-3.1.2 → optimism-3.1.4}/optimism/examples/tracing.py +0 -0
  33. {optimism-3.1.2 → optimism-3.1.4}/optimism/py.typed +0 -0
  34. {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/SOURCES.txt +0 -0
  35. {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/dependency_links.txt +0 -0
  36. {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/requires.txt +0 -0
  37. {optimism-3.1.2 → optimism-3.1.4}/optimism.egg-info/top_level.txt +0 -0
  38. {optimism-3.1.2 → optimism-3.1.4}/pyproject.toml +0 -0
  39. {optimism-3.1.2 → optimism-3.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: optimism
3
- Version: 3.1.2
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.2"
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.2"
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
- Type = type
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[Type[T], Tuple['ClassInfo', ...], Type[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[Iterable[ExtendedLoopType]] = None,
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
- (ast.UnaryOp, ast.BinOp, ast.BoolOp, ast.Compare)
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, NoneType]
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({cObj})} is already"
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
- result = copy.deepcopy(obj)
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 1 partial match(es):
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 1 partial match(es):
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 52 partial match(es):
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 52...
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 52 partial match(es):
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 52...
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.chdir(os.path.join(os.path.dirname(__file__), "examples"))
1338
- sys.path.insert(0, '.')
1339
- for (mname, inputs) in EXAMPLES:
1340
- optimism.detailLevel(0)
1341
- optimism.deleteAllTestSuites()
1342
- optimism.colors(False)
1343
- module, output, stdout = run_example(mname, inputs)
1344
- # Replace tracebacks with generic forms so we can match on
1345
- # specific errors being reported while ignoring the files & lines
1346
- # where they are reported.
1347
- stdout = re.sub(
1348
- r"Traceback \(most recent call last\):.*?^([ a-zA-Z]*Error:)",
1349
- "Traceback omitted\n\\1",
1350
- stdout,
1351
- flags=re.MULTILINE | re.DOTALL
1352
- )
1353
- output = re.sub(
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
- # Redundant, but makes issues easier to diagnose
1376
- for line in exp.splitlines():
1377
- if line not in output:
1378
- print("\n\nLINE:\n" + line)
1379
- print("\n\nOUTPUT:\n" + output)
1380
- assert (line in output), (line, output)
1381
- if exp not in output:
1382
- print("\n\nEXP:\n" + exp)
1383
- print("\n\nOUTPUT:\n" + output)
1384
- assert (exp in output), (exp, output)
1385
- if mname in STDOUT_EXPECT:
1386
- for ex in STDOUT_EXPECT[mname]:
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=os.path.split(module.__file__)[0],
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
- assert (line in stdout), stdout
1395
- assert (exp in stdout), stdout
1396
- os.chdir("..")
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
1
+ Metadata-Version: 2.4
2
2
  Name: optimism
3
- Version: 3.1.2
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
 
@@ -1,9 +0,0 @@
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))
File without changes
File without changes
File without changes
File without changes
File without changes