From 7434862b36a574515a7ba28b4c454c005fbb71ba Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 09:22:50 +0200 Subject: [PATCH 01/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 5e58090c..ef4d7d99 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 5e58090c59b5a382a596e67718e64bc00bce3916 +Subproject commit ef4d7d990ef4f44cd6ab23ffdda78f32786ebf9a From 245916932b82c19f43f24f3fb5fb54006008e813 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 09:23:42 +0200 Subject: [PATCH 02/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index ef4d7d99..7f0aef9e 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit ef4d7d990ef4f44cd6ab23ffdda78f32786ebf9a +Subproject commit 7f0aef9e833a8c735d4e00b55e26a86589abd4ca From b6e440b421e1cca8da5d8d8d0da6c0508772bc2d Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 09:24:02 +0200 Subject: [PATCH 03/40] do not import a test module from some other test module This causes strange behavior --- python/tests/sample.py | 124 +++++++++++++++++++++++++++++++++++++ python/tests/testDeepEq.py | 14 ++--- python/tests/testSample.py | 118 +---------------------------------- 3 files changed, 132 insertions(+), 124 deletions(-) create mode 100644 python/tests/sample.py diff --git a/python/tests/sample.py b/python/tests/sample.py new file mode 100644 index 00000000..f020c989 --- /dev/null +++ b/python/tests/sample.py @@ -0,0 +1,124 @@ +from writeYourProgram import * +import math +from untypy import typechecked +import typing + +Drink = Literal["Tea", "Coffee"] + +# berechnet wieviele Tassen ich von einem Getränk trinken darf +@typechecked +def canDrink(d: Drink) -> int: + if d == "Tea": + return 5 + elif d == "Coffee": + return 1 + +# A shape is one of the following: +# - a circle (Circle) +# - a square (Square) +# - an overlay of two shapes (Overlay) +Shape = typing.Union[typing.ForwardRef('Circle'), typing.ForwardRef('Square'), typing.ForwardRef('Overlay')] +Shape = typing.Union['Circle', 'Square', 'Overlay'] + +# A point consists of +# - x (float) +# - y (float) +@record +class Point: + x: float + y: float +# Point: (float, float) -> Point +# For some Point p +# p.x: float +# p.y: float + +# point at x=10, y=20 +p1 = Point(10, 20) + +# point at x=30, y=50 +p2 = Point(30, 50) + +# point at x=40, y=30 +p3 = Point(40, 30) + +# A circle consists of +# - center (Point) +# - radius (float) +@record +class Circle: + center: Point + radius: float + +# Circle: (Point, float) -> Circle +# For some circle c +# c.center: Point +# c.radius: float + +# circle at p2 with radius=20 +c1 = Circle(p2, 20) + +# circle at p3 with radius=15 +c2 = Circle(p3, 15) + +# A square (parallel to the coordinate system) consists of +# - lower-left corner (Point) +# - size (float) +@record +class Square: + corner: Point + size: float + +# square at p1 with size=40 +s1 = Square(p1, 40) + +# Square: (Point, float) -> Square +# For some square s +# s.corner: Point +# s.size: float + +# An overlay consists of +# - top (Shape) +# - bottom (Shape) +@record +class Overlay: + top: Shape + bottom: Shape + +# Overlay: (Shape, Shape) -> Overlay +# For some overlay: +# o.top: Shape +# o.bottom: Shape + +# overlay of circle c1 and square s1 +o1 = Overlay(c1, s1) +# Overlay of overlay o1 and circle c2 +o2 = Overlay(o1, c2) + +# Calculate the distance between two points +@typechecked +def distance(p1: Point, p2: Point) -> float: + w = p1.x - p2.x + h = p1.y - p2.y + dist = math.sqrt(w**2 + h**2) + return dist + +# Is a point within a shape? +@typechecked +def pointInShape(point: Point, shape: Shape) -> bool: + px = point.x + py = point.y + if type(shape) == Circle: + return distance(point, shape.center) <= shape.radius + elif type(shape) == Square: + corner = shape.corner + size = shape.size + return ( + px >= corner.x and + px <= corner.x + size and + py >= corner.y and + py <= corner.y + size + ) + elif type(shape) == Overlay: + return pointInShape(point, shape.top) or pointInShape(point, shape.bottom) + else: + uncoveredCase() diff --git a/python/tests/testDeepEq.py b/python/tests/testDeepEq.py index 412b73f5..3883c1b9 100644 --- a/python/tests/testDeepEq.py +++ b/python/tests/testDeepEq.py @@ -1,5 +1,5 @@ import unittest -import testSample +import sample from writeYourProgram import * from writeYourProgram import deepEq import writeYourProgram as wypp @@ -67,27 +67,27 @@ def test_eq(self): self.assertFalse(deepEq(C(42.0), D(42.0000000000001), structuralObjEq=False, floatEqWithDelta=True)) self.assertTrue(deepEq( - testSample.Point(1.0, 2.0), testSample.Point(1.00000000001, 2), + sample.Point(1.0, 2.0), sample.Point(1.00000000001, 2), structuralObjEq=True, floatEqWithDelta=True )) self.assertFalse(deepEq( - testSample.Point(1.0, 2.0), testSample.Point(1.00000000001, 2), + sample.Point(1.0, 2.0), sample.Point(1.00000000001, 2), structuralObjEq=False, floatEqWithDelta=True )) self.assertTrue(deepEq( - Point(1.0, 2.0), testSample.Point(1.0, 2.0), + Point(1.0, 2.0), sample.Point(1.0, 2.0), structuralObjEq=True, floatEqWithDelta=True) ) self.assertFalse(deepEq( - Point(1.0, 2.0), testSample.Point(1.0, 2.0), + Point(1.0, 2.0), sample.Point(1.0, 2.0), structuralObjEq=False, floatEqWithDelta=True) ) self.assertFalse(deepEq( - testSample.Point(1.0, 3.0), testSample.Point(1.00000000001, 2), + sample.Point(1.0, 3.0), sample.Point(1.00000000001, 2), structuralObjEq=True, floatEqWithDelta=True )) self.assertFalse(deepEq( - testSample.Point(1.0, 3.0), testSample.Point(1.00000000001, 2), + sample.Point(1.0, 3.0), sample.Point(1.00000000001, 2), structuralObjEq=False, floatEqWithDelta=True )) self.assertTrue(deepEq(A(2), A(2), structuralObjEq=True, floatEqWithDelta=True)) diff --git a/python/tests/testSample.py b/python/tests/testSample.py index 1c248d32..65ab52b0 100644 --- a/python/tests/testSample.py +++ b/python/tests/testSample.py @@ -1,125 +1,9 @@ from writeYourProgram import * import unittest -import math +from sample import * setDieOnCheckFailures(True) -Drink = Literal["Tea", "Coffee"] - -# berechnet wieviele Tassen ich von einem Getränk trinken darf -def canDrink(d: Drink) -> int: - if d == "Tea": - return 5 - elif d == "Coffee": - return 1 - -# A shape is one of the following: -# - a circle (Circle) -# - a square (Square) -# - an overlay of two shapes (Overlay) -Shape = Union[ForwardRef('Circle'), ForwardRef('Square'), ForwardRef('Overlay')] - -# A point consists of -# - x (float) -# - y (float) -@record -class Point: - x: float - y: float -# Point: (float, float) -> Point -# For some Point p -# p.x: float -# p.y: float - -# point at x=10, y=20 -p1 = Point(10, 20) - -# point at x=30, y=50 -p2 = Point(30, 50) - -# point at x=40, y=30 -p3 = Point(40, 30) - -# A circle consists of -# - center (Point) -# - radius (float) -@record -class Circle: - center: Point - radius: float - -# Circle: (Point, float) -> Circle -# For some circle c -# c.center: Point -# c.radius: float - -# circle at p2 with radius=20 -c1 = Circle(p2, 20) - -# circle at p3 with radius=15 -c2 = Circle(p3, 15) - -# A square (parallel to the coordinate system) consists of -# - lower-left corner (Point) -# - size (float) -@record -class Square: - corner: Point - size: float - -# square at p1 with size=40 -s1 = Square(p1, 40) - -# Square: (Point, float) -> Square -# For some square s -# s.corner: Point -# s.size: float - -# An overlay consists of -# - top (Shape) -# - bottom (Shape) -@record -class Overlay: - top: Shape - bottom: Shape - -# Overlay: (Shape, Shape) -> Overlay -# For some overlay: -# o.top: Shape -# o.bottom: Shape - -# overlay of circle c1 and square s1 -o1 = Overlay(c1, s1) -# Overlay of overlay o1 and circle c2 -o2 = Overlay(o1, c2) - -# Calculate the distance between two points -def distance(p1: Point, p2: Point) -> float: - w = p1.x - p2.x - h = p1.y - p2.y - dist = math.sqrt(w**2 + h**2) - return dist - -# Is a point within a shape? -def pointInShape(point: Point, shape: Shape) -> bool: - px = point.x - py = point.y - if type(shape) == Circle: - return distance(point, shape.center) <= shape.radius - elif type(shape) == Square: - corner = shape.corner - size = shape.size - return ( - px >= corner.x and - px <= corner.x + size and - py >= corner.y and - py <= corner.y + size - ) - elif type(shape) == Overlay: - return pointInShape(point, shape.top) or pointInShape(point, shape.bottom) - else: - uncoveredCase() - class TestSample(unittest.TestCase): def test_sample(self): check(pointInShape(p2, c1), True) From ee7b63c14b5c8190ae10d1ca253e470409ad8c02 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 09:52:50 +0200 Subject: [PATCH 04/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 7f0aef9e..3c0759d3 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 7f0aef9e833a8c735d4e00b55e26a86589abd4ca +Subproject commit 3c0759d345e3f5eb88cb8dae691593989a98db7c From 467b4f750adba670384bc108b362dbd600340bc3 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 09:53:09 +0200 Subject: [PATCH 05/40] fix execution of integration tests --- python/runTestsForPyVersion.sh | 22 +++++++++++++++++----- python/src/runner.py | 19 +++++++++---------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/python/runTestsForPyVersion.sh b/python/runTestsForPyVersion.sh index be275b8c..f27d7677 100755 --- a/python/runTestsForPyVersion.sh +++ b/python/runTestsForPyVersion.sh @@ -1,11 +1,20 @@ #!/bin/bash set -e +set -u cd $(dirname $0) unit_test_path=src:tests:deps/untypy -integ_test_path=integration-tests + +function prepare_integration_tests() +{ + echo "Preparing integration tests by install the WYPP library" + local d=$(mktemp -d) + trap "rm -rf $d" EXIT + WYPP_INSTALL_DIR=$d python3 src/runYourProgram.py --install-mode installOnly + integ_test_path=integration-tests:$d +} function usage() { @@ -13,10 +22,12 @@ function usage() exit 1 } -if [ -z "$1" ]; then - echo "Running all unit tests" +if [ -z "${1:-}" ]; then + echo "Running all unit tests, PYTHONPATH=$unit_test_path" PYTHONPATH=$unit_test_path python3 -m unittest tests/test*.py - echo "Running all integration tests" + echo + prepare_integration_tests + echo "Running all integration tests, PYTHONPATH=$integ_test_path" PYTHONPATH=$integ_test_path python3 -m unittest integration-tests/test*.py else if [ "$1" == "--unit" ]; then @@ -26,12 +37,13 @@ else elif [ "$1" == "--integration" ]; then what="integration" dir=integration-tests + prepare_integration_tests p=$integ_test_path else usage fi shift - echo "Running $what tests $@" + echo "Running $what tests $@ with PYTHONPATH=$p" if [ -z "$1" ]; then PYTHONPATH=$p python3 -m unittest $dir/test*.py ecode=$? diff --git a/python/src/runner.py b/python/src/runner.py index 081af5ee..9c697b8a 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -125,13 +125,12 @@ def isSameFile(f1, f2): y = readFile(f2) return x == y -def installFromDir(srcDir, mod, files=None): +def installFromDir(srcDir, targetDir, mod, files=None): if files is None: files = [p.relative_to(srcDir) for p in Path(srcDir).rglob('*.py')] else: files = [Path(f) for f in files] - userDir = site.USER_SITE - installDir = os.path.join(userDir, mod) + installDir = os.path.join(targetDir, mod) os.makedirs(installDir, exist_ok=True) installedFiles = sorted([p.relative_to(installDir) for p in Path(installDir).rglob('*.py')]) wantedFiles = sorted(files) @@ -144,7 +143,7 @@ def installFromDir(srcDir, mod, files=None): break else: # no break, all files equal - verbose(f'All files from {srcDir} already installed in {userDir}/{mod}') + verbose(f'All files from {srcDir} already installed in {targetDir}/{mod}') return True else: verbose(f'Installed files {installedFiles} and wanted files {wantedFiles} are different') @@ -164,17 +163,17 @@ def installLib(mode): if mode == InstallMode.dontInstall: verbose("No installation of WYPP should be performed") return - userDir = site.USER_SITE + targetDir = os.getenv('WYPP_INSTALL_DIR', site.USER_SITE) try: - allEq1 = installFromDir(LIB_DIR, INSTALLED_MODULE_NAME, FILES_TO_INSTALL) - allEq2 = installFromDir(UNTYPY_DIR, UNTYPY_MODULE_NAME) + allEq1 = installFromDir(LIB_DIR, targetDir, INSTALLED_MODULE_NAME, FILES_TO_INSTALL) + allEq2 = installFromDir(UNTYPY_DIR, targetDir, UNTYPY_MODULE_NAME) if allEq1 and allEq2: - verbose(f'WYPP library in {userDir} already up to date') + verbose(f'WYPP library in {targetDir} already up to date') if mode == InstallMode.installOnly: - printStderr(f'WYPP library in {userDir} already up to date') + printStderr(f'WYPP library in {targetDir} already up to date') return else: - printStderr(f'The WYPP library has been successfully installed in {userDir}.') + printStderr(f'The WYPP library has been successfully installed in {targetDir}.') except Exception as e: printStderr('Installation of the WYPP library failed: ' + str(e)) if mode == InstallMode.assertInstall or mode == InstallMode.installOnly: From 2a2b4846996fef9ba5017e1f97ef828b5c3dc199 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 09:53:22 +0200 Subject: [PATCH 06/40] unskip to tests that now succeed --- python/integration-tests/testIntegration.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py index 51cf445a..bd766bac 100644 --- a/python/integration-tests/testIntegration.py +++ b/python/integration-tests/testIntegration.py @@ -50,7 +50,6 @@ def test_recordOk(self): out2 = runInteractive(rec, 'incAge(Person("stefan", 42))') self.assertEqual(["Person(name='stefan', age=43)"], out2) - @unittest.skip def test_recordFail1(self): rec = 'file-tests/typeRecords.py' out = runInteractive(rec, 'Person("stefan", 42.3)')[0] @@ -68,7 +67,6 @@ def test_recordMutableOk(self): out2 = runInteractive(rec, 'p = MutablePerson("stefan", 42)\nmutableIncAge(p)\np') self.assertEqual(['', '', "MutablePerson(name='stefan', age=43)"], out2) - @unittest.skip def test_mutableRecordFail1(self): rec = 'file-tests/typeRecords.py' out = runInteractive(rec, 'MutablePerson("stefan", 42.3)')[0] @@ -100,7 +98,6 @@ def check(self, file, testFile, ecode, tycheck=True): if not tycheck: flags.append('--no-typechecking') cmd = f"python3 src/runYourProgram.py {' '.join(flags)} --test-file {testFile} {file} {LOG_REDIR}" - print(cmd) res = shell.run(cmd, onError='ignore') self.assertEqual(ecode, res.exitcode) From a23a8d0891725f08ec620ee3d5f6a3cd757ba38a Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 09:53:50 +0200 Subject: [PATCH 07/40] update README --- TODO.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.org b/TODO.org index 1716ec28..9e81e5fe 100644 --- a/TODO.org +++ b/TODO.org @@ -1,11 +1,11 @@ * untypy ** add tests with type annotations *** for records -**** construction **** write access * other ** upload dist to pypi ** update README +*** python version *** pip *** records *** import From bdb0d963ea8fbea5bde1824978289a3c0ab280af Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 14:18:50 +0200 Subject: [PATCH 08/40] remove leftover from implicit wypp import --- python/runTestsForPyVersion.sh | 2 +- python/src/runner.py | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/python/runTestsForPyVersion.sh b/python/runTestsForPyVersion.sh index f27d7677..9862f225 100755 --- a/python/runTestsForPyVersion.sh +++ b/python/runTestsForPyVersion.sh @@ -44,7 +44,7 @@ else fi shift echo "Running $what tests $@ with PYTHONPATH=$p" - if [ -z "$1" ]; then + if [ -z "${1:-}" ]; then PYTHONPATH=$p python3 -m unittest $dir/test*.py ecode=$? else diff --git a/python/src/runner.py b/python/src/runner.py index 9c697b8a..c8f733cc 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -200,7 +200,7 @@ def __init__(self, mod, properlyImported): if name and name[0] != '_': d[name] = getattr(mod, name) -def loadLib(onlyCheckRunnable): +def prepareLib(onlyCheckRunnable): libDefs = None mod = INSTALLED_MODULE_NAME verbose('Attempting to import ' + mod) @@ -258,7 +258,7 @@ def __exit__(self, exc_type, value, traceback): sys.path.remove(self.dir) self.inserted = False -def runCode(fileToRun, globals, args, *, useUntypy=True): +def runCode(fileToRun, globals, args, useUntypy=True): localDir = os.path.dirname(fileToRun) with sysPathPrepended(localDir): with open(fileToRun) as f: @@ -285,11 +285,7 @@ def runCode(fileToRun, globals, args, *, useUntypy=True): finally: sys.argv = oldArgs -def runStudentCode(fileToRun, globals, libDefs, onlyCheckRunnable, args, *, useUntypy=True): - importsWypp = findWyppImport(fileToRun) - if importsWypp: - if not libDefs.properlyImported: - globals[INSTALLED_MODULE_NAME] = libDefs.dict +def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, useUntypy=True): doRun = lambda: runCode(fileToRun, globals, args, useUntypy=useUntypy) if onlyCheckRunnable: try: @@ -414,13 +410,13 @@ def main(globals): if not args.checkRunnable and not args.quiet: printWelcomeString(fileToRun, version, useUntypy=args.checkTypes) - libDefs = loadLib(onlyCheckRunnable=args.checkRunnable) + libDefs = prepareLib(onlyCheckRunnable=args.checkRunnable) globals['__name__'] = '__wypp__' sys.modules['__wypp__'] = sys.modules['__main__'] try: verbose(f'running code in {fileToRun}') - runStudentCode(fileToRun, globals, libDefs, args.checkRunnable, restArgs, + runStudentCode(fileToRun, globals, args.checkRunnable, restArgs, useUntypy=args.checkTypes) except: handleCurrentException() From 55e0df914e14631afb1c069c83e84039999ca9bc Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 14:56:49 +0200 Subject: [PATCH 09/40] improved repl tester and renamed some files --- python/file-tests/testArgs.out | 1 - python/file-tests/testTraceback.err | 7 -- python/integration-tests/testIntegration.py | 64 ++++++------- python/runFileTests.sh | 24 ++--- python/src/replTester.py | 90 +++++++++++++++++++ python/src/runner.py | 13 ++- python/src/testReplSamples.py | 74 --------------- python/src/writeYourProgram.py | 2 +- .../fileWithBothImports.py | 0 .../fileWithImport.py | 0 .../fileWithRecursiveTypes.py | 0 .../fileWithStarImport.py | 0 .../fileWithoutImport.py | 0 python/{file-tests => test-data}/localMod.py | 0 .../printModuleName.err | 0 .../printModuleName.out | 0 .../printModuleName.py | 0 .../printModuleNameImport.err | 0 .../printModuleNameImport.out | 0 .../printModuleNameImport.py | 0 .../scope-bug-peter.py | 0 .../student-submission-bad.py | 0 .../student-submission-tests-tyerror.py | 0 .../student-submission-tests.py | 0 .../student-submission-tyerror.py | 0 .../student-submission.py | 0 python/{file-tests => test-data}/testArgs.err | 0 python/test-data/testArgs.out | 1 + python/{file-tests => test-data}/testArgs.py | 0 python/test-data/testTraceback.err | 7 ++ .../testTraceback.out | 0 .../testTraceback.py | 0 .../testTraceback2.err | 2 +- .../testTraceback2.err-3.9.0 | 2 +- .../testTraceback2.out | 0 .../testTraceback2.py | 0 .../testTraceback3.err | 2 +- .../testTraceback3.out | 0 .../testTraceback3.py | 0 .../{file-tests => test-data}/testTypes1.err | 4 +- .../testTypes1.err-notypes | 4 +- .../{file-tests => test-data}/testTypes1.out | 0 .../{file-tests => test-data}/testTypes1.py | 0 .../{file-tests => test-data}/testTypes2.err | 4 +- .../testTypes2.err-notypes | 0 .../{file-tests => test-data}/testTypes2.out | 0 .../{file-tests => test-data}/testTypes2.py | 0 .../{file-tests => test-data}/testTypes3.py | 0 .../testTypesInteractive.py | 0 python/{file-tests => test-data}/typeEnums.py | 0 .../{file-tests => test-data}/typeRecords.py | 0 python/{file-tests => test-data}/typeUnion.py | 0 52 files changed, 162 insertions(+), 139 deletions(-) delete mode 100644 python/file-tests/testArgs.out delete mode 100644 python/file-tests/testTraceback.err mode change 100755 => 100644 python/runFileTests.sh create mode 100644 python/src/replTester.py delete mode 100644 python/src/testReplSamples.py rename python/{file-tests => test-data}/fileWithBothImports.py (100%) rename python/{file-tests => test-data}/fileWithImport.py (100%) rename python/{file-tests => test-data}/fileWithRecursiveTypes.py (100%) rename python/{file-tests => test-data}/fileWithStarImport.py (100%) rename python/{file-tests => test-data}/fileWithoutImport.py (100%) rename python/{file-tests => test-data}/localMod.py (100%) rename python/{file-tests => test-data}/printModuleName.err (100%) rename python/{file-tests => test-data}/printModuleName.out (100%) rename python/{file-tests => test-data}/printModuleName.py (100%) rename python/{file-tests => test-data}/printModuleNameImport.err (100%) rename python/{file-tests => test-data}/printModuleNameImport.out (100%) rename python/{file-tests => test-data}/printModuleNameImport.py (100%) rename python/{file-tests => test-data}/scope-bug-peter.py (100%) rename python/{file-tests => test-data}/student-submission-bad.py (100%) rename python/{file-tests => test-data}/student-submission-tests-tyerror.py (100%) rename python/{file-tests => test-data}/student-submission-tests.py (100%) rename python/{file-tests => test-data}/student-submission-tyerror.py (100%) rename python/{file-tests => test-data}/student-submission.py (100%) rename python/{file-tests => test-data}/testArgs.err (100%) create mode 100644 python/test-data/testArgs.out rename python/{file-tests => test-data}/testArgs.py (100%) create mode 100644 python/test-data/testTraceback.err rename python/{file-tests => test-data}/testTraceback.out (100%) rename python/{file-tests => test-data}/testTraceback.py (100%) rename python/{file-tests => test-data}/testTraceback2.err (62%) rename python/{file-tests => test-data}/testTraceback2.err-3.9.0 (62%) rename python/{file-tests => test-data}/testTraceback2.out (100%) rename python/{file-tests => test-data}/testTraceback2.py (100%) rename python/{file-tests => test-data}/testTraceback3.err (61%) rename python/{file-tests => test-data}/testTraceback3.out (100%) rename python/{file-tests => test-data}/testTraceback3.py (100%) rename python/{file-tests => test-data}/testTypes1.err (78%) rename python/{file-tests => test-data}/testTypes1.err-notypes (53%) rename python/{file-tests => test-data}/testTypes1.out (100%) rename python/{file-tests => test-data}/testTypes1.py (100%) rename python/{file-tests => test-data}/testTypes2.err (78%) rename python/{file-tests => test-data}/testTypes2.err-notypes (100%) rename python/{file-tests => test-data}/testTypes2.out (100%) rename python/{file-tests => test-data}/testTypes2.py (100%) rename python/{file-tests => test-data}/testTypes3.py (100%) rename python/{file-tests => test-data}/testTypesInteractive.py (100%) rename python/{file-tests => test-data}/typeEnums.py (100%) rename python/{file-tests => test-data}/typeRecords.py (100%) rename python/{file-tests => test-data}/typeUnion.py (100%) diff --git a/python/file-tests/testArgs.out b/python/file-tests/testArgs.out deleted file mode 100644 index 6840595f..00000000 --- a/python/file-tests/testArgs.out +++ /dev/null @@ -1 +0,0 @@ -['file-tests/testArgs.py', 'ARG_1', 'ARG_2'] diff --git a/python/file-tests/testTraceback.err b/python/file-tests/testTraceback.err deleted file mode 100644 index e20739f5..00000000 --- a/python/file-tests/testTraceback.err +++ /dev/null @@ -1,7 +0,0 @@ - -Traceback (most recent call last): - File "file-tests/testTraceback.py", line 9, in - foo(lst) - File "file-tests/testTraceback.py", line 7, in foo - print(lst[10]) -IndexError: list index out of range diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py index bd766bac..fb86d6e0 100644 --- a/python/integration-tests/testIntegration.py +++ b/python/integration-tests/testIntegration.py @@ -36,55 +36,55 @@ def stripTrailingWs(s): class TypeTests(unittest.TestCase): def test_enumOk(self): - out = runInteractive('file-tests/typeEnums.py', 'colorToNumber("red")') + out = runInteractive('test-data/typeEnums.py', 'colorToNumber("red")') self.assertEqual(['0'], out) def test_enumTypeError(self): - out = runInteractive('file-tests/typeEnums.py', 'colorToNumber(1)')[0] + out = runInteractive('test-data/typeEnums.py', 'colorToNumber(1)')[0] self.assertIn("expected: Literal['red', 'yellow', 'green']", out) def test_recordOk(self): - rec = 'file-tests/typeRecords.py' + rec = 'test-data/typeRecords.py' out1 = runInteractive(rec, 'Person("stefan", 42)') self.assertEqual(["Person(name='stefan', age=42)"], out1) out2 = runInteractive(rec, 'incAge(Person("stefan", 42))') self.assertEqual(["Person(name='stefan', age=43)"], out2) def test_recordFail1(self): - rec = 'file-tests/typeRecords.py' + rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'Person("stefan", 42.3)')[0] self.assertIn('expected: int', out) def test_recordFail2(self): - rec = 'file-tests/typeRecords.py' + rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'mutableIncAge(Person("stefan", 42))')[0] self.assertIn('expected: MutablePerson', out) def test_recordMutableOk(self): - rec = 'file-tests/typeRecords.py' + rec = 'test-data/typeRecords.py' out1 = runInteractive(rec, 'MutablePerson("stefan", 42)') self.assertEqual(["MutablePerson(name='stefan', age=42)"], out1) out2 = runInteractive(rec, 'p = MutablePerson("stefan", 42)\nmutableIncAge(p)\np') self.assertEqual(['', '', "MutablePerson(name='stefan', age=43)"], out2) def test_mutableRecordFail1(self): - rec = 'file-tests/typeRecords.py' + rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'MutablePerson("stefan", 42.3)')[0] self.assertIn('expected: int', out) def test_mutableRecordFail2(self): - rec = 'file-tests/typeRecords.py' + rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'incAge(MutablePerson("stefan", 42))')[0] self.assertIn('expected: Person', out) @unittest.skip def test_mutableRecordFail3(self): - rec = 'file-tests/typeRecords.py' + rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'p = MutablePerson("stefan", 42)\np.age = 42.4') self.assertIn('expected: int', out) def test_union(self): - out = runInteractive('file-tests/typeUnion.py', """formatAnimal(myCat) + out = runInteractive('test-data/typeUnion.py', """formatAnimal(myCat) formatAnimal(myParrot) formatAnimal(None) """) @@ -102,38 +102,38 @@ def check(self, file, testFile, ecode, tycheck=True): self.assertEqual(ecode, res.exitcode) def test_goodSubmission(self): - self.check("file-tests/student-submission.py", "file-tests/student-submission-tests.py", 0) - self.check("file-tests/student-submission.py", "file-tests/student-submission-tests.py", 0, + self.check("test-data/student-submission.py", "test-data/student-submission-tests.py", 0) + self.check("test-data/student-submission.py", "test-data/student-submission-tests.py", 0, tycheck=False) def test_badSubmission(self): - self.check("file-tests/student-submission-bad.py", - "file-tests/student-submission-tests.py", 1) - self.check("file-tests/student-submission-bad.py", - "file-tests/student-submission-tests.py", 1, tycheck=False) + self.check("test-data/student-submission-bad.py", + "test-data/student-submission-tests.py", 1) + self.check("test-data/student-submission-bad.py", + "test-data/student-submission-tests.py", 1, tycheck=False) def test_submissionWithTypeErrors(self): - self.check("file-tests/student-submission-tyerror.py", - "file-tests/student-submission-tests.py", 1) - self.check("file-tests/student-submission-tyerror.py", - "file-tests/student-submission-tests.py", 0, tycheck=False) - self.check("file-tests/student-submission.py", - "file-tests/student-submission-tests-tyerror.py", 1) - self.check("file-tests/student-submission.py", - "file-tests/student-submission-tests-tyerror.py", 0, tycheck=False) + self.check("test-data/student-submission-tyerror.py", + "test-data/student-submission-tests.py", 1) + self.check("test-data/student-submission-tyerror.py", + "test-data/student-submission-tests.py", 0, tycheck=False) + self.check("test-data/student-submission.py", + "test-data/student-submission-tests-tyerror.py", 1) + self.check("test-data/student-submission.py", + "test-data/student-submission-tests-tyerror.py", 0, tycheck=False) class InteractiveTests(unittest.TestCase): def test_scopeBugPeter(self): - out = runInteractive('file-tests/scope-bug-peter.py', 'local_test()\nprint(spam)') + out = runInteractive('test-data/scope-bug-peter.py', 'local_test()\nprint(spam)') self.assertIn('IT WORKS', out) def test_types1(self): - out = runInteractive('file-tests/testTypesInteractive.py', 'inc(3)') + out = runInteractive('test-data/testTypesInteractive.py', 'inc(3)') self.assertEqual(['4'], out) def test_types2(self): - out = runInteractive('file-tests/testTypesInteractive.py', 'inc("3")')[0] + out = runInteractive('test-data/testTypesInteractive.py', 'inc("3")')[0] expected = """given: '3' expected: int ^^^ @@ -144,25 +144,25 @@ def test_types2(self): self.assertIn(expected, stripTrailingWs(out)) def test_types3(self): - out = runInteractive('file-tests/testTypesInteractive.py', + out = runInteractive('test-data/testTypesInteractive.py', 'def f(x: int) -> int: return x\n\nf("x")')[1] self.assertIn('expected: int', out) def test_types4(self): - out = runInteractive('file-tests/testTypesInteractive.py', + out = runInteractive('test-data/testTypesInteractive.py', 'def f(x: int) -> int: return x\n\nf(3)') self.assertEqual(['...', '3'], out) def test_types5(self): - out = runInteractive('file-tests/testTypesInteractive.py', + out = runInteractive('test-data/testTypesInteractive.py', 'def f(x: int) -> int: return x\n\nf("x")', tycheck=False) self.assertEqual(['...', "'x'"], out) def test_typesInImportedModule1(self): - out = run('file-tests/testTypes3.py', ecode=1) + out = run('test-data/testTypes3.py', ecode=1) self.assertIn('expected: int', out) def test_typesInImportedModule2(self): - out = run('file-tests/testTypes3.py', tycheck=False) + out = run('test-data/testTypes3.py', tycheck=False) self.assertEqual('END', out) diff --git a/python/runFileTests.sh b/python/runFileTests.sh old mode 100755 new mode 100644 index 567097a9..01abde15 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -23,10 +23,10 @@ function check() python3 $d/src/runYourProgram.py --check --install-mode assertInstall $d/"$1" >> "$t" popd > /dev/null } -check file-tests/fileWithImport.py -check file-tests/fileWithoutImport.py -check file-tests/fileWithBothImports.py -check file-tests/fileWithRecursiveTypes.py +check test-data/fileWithImport.py +check test-data/fileWithoutImport.py +check test-data/fileWithBothImports.py +check test-data/fileWithRecursiveTypes.py # First argument: whether to do type checking or not # Second argument: expected exit code. If given as X:Y, then X is the exit code with active @@ -101,11 +101,11 @@ function checkWithOutput() checkWithOutputAux no "$@" } -checkWithOutput 1 file-tests/testTraceback.py -checkWithOutput 1 file-tests/testTraceback2.py -checkWithOutput 1 file-tests/testTraceback3.py -checkWithOutput 0 file-tests/testArgs.py ARG_1 ARG_2 -checkWithOutput 0 file-tests/printModuleName.py -checkWithOutput 0 file-tests/printModuleNameImport.py -checkWithOutput 1 file-tests/testTypes1.py -checkWithOutput 1:0 file-tests/testTypes2.py +checkWithOutput 1 test-data/testTraceback.py +checkWithOutput 1 test-data/testTraceback2.py +checkWithOutput 1 test-data/testTraceback3.py +checkWithOutput 0 test-data/testArgs.py ARG_1 ARG_2 +checkWithOutput 0 test-data/printModuleName.py +checkWithOutput 0 test-data/printModuleNameImport.py +checkWithOutput 1 test-data/testTypes1.py +checkWithOutput 1:0 test-data/testTypes2.py diff --git a/python/src/replTester.py b/python/src/replTester.py new file mode 100644 index 00000000..2949901f --- /dev/null +++ b/python/src/replTester.py @@ -0,0 +1,90 @@ +import sys +import doctest +import os +import argparse +from dataclasses import dataclass +from runner import runCode, importUntypy, verbose, enableVerbose + +usage = """python3 replTester.py [ ARGUMENTS ] LIB_1 ... LIB_n --repl SAMPLE_1 ... SAMPLE_m + +If no library files should be used to test the REPL samples, omit LIB_1 ... LIB_n +and the --repl flag. +The definitions of LIB_1 ... LIB_n are made available when testing +SAMPLE_1 ... SAMPLE_m, where identifer in LIB_i takes precedence over identifier in +LIB_j if i > j. +""" + +@dataclass +class Options: + verbose: bool + libs: list[str] + repls: list[str] + +def parseCmdlineArgs(): + parser = argparse.ArgumentParser(usage=usage, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('--verbose', dest='verbose', action='store_const', + const=True, default=False, + help='Be verbose') + args, restArgs = parser.parse_known_args() + libs = [] + repls = [] + replFlag = '--repl' + if replFlag in restArgs: + cur = libs + for x in restArgs: + if x == replFlag: + cur = repls + else: + cur.append(x) + else: + repls = restArgs + if len(repls) == 0: + print('No SAMPLE arguments given') + sys.exit(1) + return Options(args.verbose, libs, repls) + +opts = parseCmdlineArgs() + +if opts.verbose: + enableVerbose() + +libDir = os.path.dirname(__file__) +libFile = os.path.join(libDir, 'writeYourProgram.py') +defs = {} +importUntypy() +# runCode(libFile, defs, []) + +for lib in opts.libs: + d = os.path.dirname(lib) + if d not in sys.path: + sys.path.insert(0, d) + +for lib in opts.libs: + verbose(f"Loading lib {lib}") + runCode(lib, defs, []) + +totalFailures = 0 +totalTests = 0 + +for repl in opts.repls: + (failures, tests) = doctest.testfile(repl, globs=defs, module_relative=False) + totalFailures += failures + totalTests += tests + if failures == 0: + if tests == 0: + print(f'No tests in {repl}') + else: + print(f'All {tests} tests in {repl} succeeded') + else: + print(f'ERROR: {failures} out of {tests} in {repl} failed') + +if totalFailures == 0: + if totalTests == 0: + print('ERROR: No tests found at all!') + sys.exit(1) + else: + print(f'All {totalTests} tests succeded. Great!') +else: + print(f'ERROR: {failures} out of {tests} failed') + sys.exit(1) diff --git a/python/src/runner.py b/python/src/runner.py index c8f733cc..9ec5e904 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -24,6 +24,10 @@ def die(ecode=1): VERBOSE = False # set via commandline +def enableVerbose(): + global VERBOSE + VERBOSE = True + LIB_DIR = os.path.dirname(__file__) INSTALLED_MODULE_NAME = 'wypp' FILES_TO_INSTALL = ['writeYourProgram.py', 'drawingLib.py', '__init__.py'] @@ -379,6 +383,11 @@ def getHistoryFilePath(): else: return None +# We cannot import untypy at the top of the file because we might have to install it first. +def importUntypy(): + global untypy + import untypy + def main(globals): v = sys.version_info if v.major < 3 or v.minor < 9: @@ -393,9 +402,7 @@ def main(globals): VERBOSE = True installLib(args.installMode) - - global untypy - import untypy + importUntypy() fileToRun = args.file if args.changeDir: diff --git a/python/src/testReplSamples.py b/python/src/testReplSamples.py deleted file mode 100644 index bbc7d557..00000000 --- a/python/src/testReplSamples.py +++ /dev/null @@ -1,74 +0,0 @@ -import sys -import doctest -import os -from runner import runCode - -def usage(): - print('USAGE: python3 testReplSamples.py LIB_1 ... LIB_n --repl SAMPLE_1 ... SAMPLE_m') - print('If no library files should be used to test the REPL samples, omit LIB_1 ... LIB_n') - print('and the --repl flag.') - print('The definitions of LIB_1 ... LIB_n are made available when testing ') - print('SAMPLE_1 ... SAMPLE_m, where identifer in LIB_i takes precedence over identifier in ') - print('LIB_j if i > j.') - sys.exit(1) - -args = sys.argv[1:] - -if '--help' in args: - usage() - -libs = [] -repls = [] - -replFlag = '--repl' - -if replFlag in args: - cur = libs - for x in args: - if x == replFlag: - cur = repls - else: - cur.append(x) -else: - repls = args - -if len(repls) == 0: - usage() - -libDir = os.path.dirname(__file__) -libFile = os.path.join(libDir, 'writeYourProgram.py') -defs = {} -runCode(libFile, defs, []) - -for lib in libs: - d = os.path.dirname(lib) - if d not in sys.path: - sys.path.insert(0, d) - -for lib in libs: - runCode(lib, defs, []) - -totalFailures = 0 -totalTests = 0 - -for repl in repls: - (failures, tests) = doctest.testfile(repl, globs=defs, module_relative=False) - totalFailures += failures - totalTests += tests - if failures == 0: - if tests == 0: - print(f'No tests in {repl}') - else: - print(f'All {tests} tests in {repl} succeeded') - else: - print(f'ERROR: {failures} out of {tests} in {repl} failed') - -if totalFailures == 0: - if totalTests == 0: - print('ERROR: No tests found at all!') - sys.exit(1) - else: - print(f'All {totalTests} tests succeded. Great!') -else: - print(f'ERROR: {failures} out of {tests} failed') - sys.exit(1) diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index 1f279943..d82f36ef 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -33,7 +33,7 @@ def _debug(s): def _patchDataClass(cls, mutable): fieldNames = [f.name for f in dataclasses.fields(cls)] setattr(cls, EQ_ATTRS_ATTR, fieldNames) - + if hasattr(cls, '__annotations__'): # add annotions for type checked constructor. cls.__init__.__annotations__ = cls.__annotations__ diff --git a/python/file-tests/fileWithBothImports.py b/python/test-data/fileWithBothImports.py similarity index 100% rename from python/file-tests/fileWithBothImports.py rename to python/test-data/fileWithBothImports.py diff --git a/python/file-tests/fileWithImport.py b/python/test-data/fileWithImport.py similarity index 100% rename from python/file-tests/fileWithImport.py rename to python/test-data/fileWithImport.py diff --git a/python/file-tests/fileWithRecursiveTypes.py b/python/test-data/fileWithRecursiveTypes.py similarity index 100% rename from python/file-tests/fileWithRecursiveTypes.py rename to python/test-data/fileWithRecursiveTypes.py diff --git a/python/file-tests/fileWithStarImport.py b/python/test-data/fileWithStarImport.py similarity index 100% rename from python/file-tests/fileWithStarImport.py rename to python/test-data/fileWithStarImport.py diff --git a/python/file-tests/fileWithoutImport.py b/python/test-data/fileWithoutImport.py similarity index 100% rename from python/file-tests/fileWithoutImport.py rename to python/test-data/fileWithoutImport.py diff --git a/python/file-tests/localMod.py b/python/test-data/localMod.py similarity index 100% rename from python/file-tests/localMod.py rename to python/test-data/localMod.py diff --git a/python/file-tests/printModuleName.err b/python/test-data/printModuleName.err similarity index 100% rename from python/file-tests/printModuleName.err rename to python/test-data/printModuleName.err diff --git a/python/file-tests/printModuleName.out b/python/test-data/printModuleName.out similarity index 100% rename from python/file-tests/printModuleName.out rename to python/test-data/printModuleName.out diff --git a/python/file-tests/printModuleName.py b/python/test-data/printModuleName.py similarity index 100% rename from python/file-tests/printModuleName.py rename to python/test-data/printModuleName.py diff --git a/python/file-tests/printModuleNameImport.err b/python/test-data/printModuleNameImport.err similarity index 100% rename from python/file-tests/printModuleNameImport.err rename to python/test-data/printModuleNameImport.err diff --git a/python/file-tests/printModuleNameImport.out b/python/test-data/printModuleNameImport.out similarity index 100% rename from python/file-tests/printModuleNameImport.out rename to python/test-data/printModuleNameImport.out diff --git a/python/file-tests/printModuleNameImport.py b/python/test-data/printModuleNameImport.py similarity index 100% rename from python/file-tests/printModuleNameImport.py rename to python/test-data/printModuleNameImport.py diff --git a/python/file-tests/scope-bug-peter.py b/python/test-data/scope-bug-peter.py similarity index 100% rename from python/file-tests/scope-bug-peter.py rename to python/test-data/scope-bug-peter.py diff --git a/python/file-tests/student-submission-bad.py b/python/test-data/student-submission-bad.py similarity index 100% rename from python/file-tests/student-submission-bad.py rename to python/test-data/student-submission-bad.py diff --git a/python/file-tests/student-submission-tests-tyerror.py b/python/test-data/student-submission-tests-tyerror.py similarity index 100% rename from python/file-tests/student-submission-tests-tyerror.py rename to python/test-data/student-submission-tests-tyerror.py diff --git a/python/file-tests/student-submission-tests.py b/python/test-data/student-submission-tests.py similarity index 100% rename from python/file-tests/student-submission-tests.py rename to python/test-data/student-submission-tests.py diff --git a/python/file-tests/student-submission-tyerror.py b/python/test-data/student-submission-tyerror.py similarity index 100% rename from python/file-tests/student-submission-tyerror.py rename to python/test-data/student-submission-tyerror.py diff --git a/python/file-tests/student-submission.py b/python/test-data/student-submission.py similarity index 100% rename from python/file-tests/student-submission.py rename to python/test-data/student-submission.py diff --git a/python/file-tests/testArgs.err b/python/test-data/testArgs.err similarity index 100% rename from python/file-tests/testArgs.err rename to python/test-data/testArgs.err diff --git a/python/test-data/testArgs.out b/python/test-data/testArgs.out new file mode 100644 index 00000000..982a2823 --- /dev/null +++ b/python/test-data/testArgs.out @@ -0,0 +1 @@ +['test-data/testArgs.py', 'ARG_1', 'ARG_2'] diff --git a/python/file-tests/testArgs.py b/python/test-data/testArgs.py similarity index 100% rename from python/file-tests/testArgs.py rename to python/test-data/testArgs.py diff --git a/python/test-data/testTraceback.err b/python/test-data/testTraceback.err new file mode 100644 index 00000000..d12f2238 --- /dev/null +++ b/python/test-data/testTraceback.err @@ -0,0 +1,7 @@ + +Traceback (most recent call last): + File "test-data/testTraceback.py", line 9, in + foo(lst) + File "test-data/testTraceback.py", line 7, in foo + print(lst[10]) +IndexError: list index out of range diff --git a/python/file-tests/testTraceback.out b/python/test-data/testTraceback.out similarity index 100% rename from python/file-tests/testTraceback.out rename to python/test-data/testTraceback.out diff --git a/python/file-tests/testTraceback.py b/python/test-data/testTraceback.py similarity index 100% rename from python/file-tests/testTraceback.py rename to python/test-data/testTraceback.py diff --git a/python/file-tests/testTraceback2.err b/python/test-data/testTraceback2.err similarity index 62% rename from python/file-tests/testTraceback2.err rename to python/test-data/testTraceback2.err index 55342d7e..98bc37a8 100644 --- a/python/file-tests/testTraceback2.err +++ b/python/test-data/testTraceback2.err @@ -1,5 +1,5 @@ - File "file-tests/testTraceback2.py", line 3 + File "test-data/testTraceback2.py", line 3 lst = [1,2,3 ^ SyntaxError: unexpected EOF while parsing diff --git a/python/file-tests/testTraceback2.err-3.9.0 b/python/test-data/testTraceback2.err-3.9.0 similarity index 62% rename from python/file-tests/testTraceback2.err-3.9.0 rename to python/test-data/testTraceback2.err-3.9.0 index e3f643b9..281b1771 100644 --- a/python/file-tests/testTraceback2.err-3.9.0 +++ b/python/test-data/testTraceback2.err-3.9.0 @@ -1,5 +1,5 @@ - File "file-tests/testTraceback2.py", line 3 + File "test-data/testTraceback2.py", line 3 lst = [1,2,3 ^ SyntaxError: unexpected EOF while parsing diff --git a/python/file-tests/testTraceback2.out b/python/test-data/testTraceback2.out similarity index 100% rename from python/file-tests/testTraceback2.out rename to python/test-data/testTraceback2.out diff --git a/python/file-tests/testTraceback2.py b/python/test-data/testTraceback2.py similarity index 100% rename from python/file-tests/testTraceback2.py rename to python/test-data/testTraceback2.py diff --git a/python/file-tests/testTraceback3.err b/python/test-data/testTraceback3.err similarity index 61% rename from python/file-tests/testTraceback3.err rename to python/test-data/testTraceback3.err index e2de6b58..c59940d1 100644 --- a/python/file-tests/testTraceback3.err +++ b/python/test-data/testTraceback3.err @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "file-tests/testTraceback3.py", line 2, in + File "test-data/testTraceback3.py", line 2, in print([1,2,3][10]) IndexError: list index out of range diff --git a/python/file-tests/testTraceback3.out b/python/test-data/testTraceback3.out similarity index 100% rename from python/file-tests/testTraceback3.out rename to python/test-data/testTraceback3.out diff --git a/python/file-tests/testTraceback3.py b/python/test-data/testTraceback3.py similarity index 100% rename from python/file-tests/testTraceback3.py rename to python/test-data/testTraceback3.py diff --git a/python/file-tests/testTypes1.err b/python/test-data/testTypes1.err similarity index 78% rename from python/file-tests/testTypes1.err rename to python/test-data/testTypes1.err index 86fffb7c..dd25544c 100644 --- a/python/file-tests/testTypes1.err +++ b/python/test-data/testTypes1.err @@ -6,12 +6,12 @@ expected: int inside of inc(x: int) -> int ^^^ declared at: -file-tests/testTypes1.py:1 +test-data/testTypes1.py:1 1 | def inc(x: int) -> int: 2 | return x + 1 caused by: -file-tests/testTypes1.py:4 +test-data/testTypes1.py:4 4 | inc("1") diff --git a/python/file-tests/testTypes1.err-notypes b/python/test-data/testTypes1.err-notypes similarity index 53% rename from python/file-tests/testTypes1.err-notypes rename to python/test-data/testTypes1.err-notypes index ecbea461..c1014f7a 100644 --- a/python/file-tests/testTypes1.err-notypes +++ b/python/test-data/testTypes1.err-notypes @@ -1,7 +1,7 @@ Traceback (most recent call last): - File "file-tests/testTypes1.py", line 4, in + File "test-data/testTypes1.py", line 4, in inc("1") - File "file-tests/testTypes1.py", line 2, in inc + File "test-data/testTypes1.py", line 2, in inc return x + 1 TypeError: can only concatenate str (not "int") to str diff --git a/python/file-tests/testTypes1.out b/python/test-data/testTypes1.out similarity index 100% rename from python/file-tests/testTypes1.out rename to python/test-data/testTypes1.out diff --git a/python/file-tests/testTypes1.py b/python/test-data/testTypes1.py similarity index 100% rename from python/file-tests/testTypes1.py rename to python/test-data/testTypes1.py diff --git a/python/file-tests/testTypes2.err b/python/test-data/testTypes2.err similarity index 78% rename from python/file-tests/testTypes2.err rename to python/test-data/testTypes2.err index d3bc73e6..ffd45cb0 100644 --- a/python/file-tests/testTypes2.err +++ b/python/test-data/testTypes2.err @@ -6,12 +6,12 @@ expected: int inside of inc(x: int) -> int ^^^ declared at: -file-tests/testTypes2.py:1 +test-data/testTypes2.py:1 1 | def inc(x: int) -> int: 2 | return x caused by: -file-tests/testTypes2.py:4 +test-data/testTypes2.py:4 4 | inc("1") diff --git a/python/file-tests/testTypes2.err-notypes b/python/test-data/testTypes2.err-notypes similarity index 100% rename from python/file-tests/testTypes2.err-notypes rename to python/test-data/testTypes2.err-notypes diff --git a/python/file-tests/testTypes2.out b/python/test-data/testTypes2.out similarity index 100% rename from python/file-tests/testTypes2.out rename to python/test-data/testTypes2.out diff --git a/python/file-tests/testTypes2.py b/python/test-data/testTypes2.py similarity index 100% rename from python/file-tests/testTypes2.py rename to python/test-data/testTypes2.py diff --git a/python/file-tests/testTypes3.py b/python/test-data/testTypes3.py similarity index 100% rename from python/file-tests/testTypes3.py rename to python/test-data/testTypes3.py diff --git a/python/file-tests/testTypesInteractive.py b/python/test-data/testTypesInteractive.py similarity index 100% rename from python/file-tests/testTypesInteractive.py rename to python/test-data/testTypesInteractive.py diff --git a/python/file-tests/typeEnums.py b/python/test-data/typeEnums.py similarity index 100% rename from python/file-tests/typeEnums.py rename to python/test-data/typeEnums.py diff --git a/python/file-tests/typeRecords.py b/python/test-data/typeRecords.py similarity index 100% rename from python/file-tests/typeRecords.py rename to python/test-data/typeRecords.py diff --git a/python/file-tests/typeUnion.py b/python/test-data/typeUnion.py similarity index 100% rename from python/file-tests/typeUnion.py rename to python/test-data/typeUnion.py From 6349976546e4a3bb830d144a09dfd2a7b9f49b47 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 15:09:36 +0200 Subject: [PATCH 10/40] symlink for using wypp without installing --- python/site-lib/README | 3 +++ python/site-lib/untypy | 1 + python/site-lib/wypp | 1 + 3 files changed, 5 insertions(+) create mode 100644 python/site-lib/README create mode 120000 python/site-lib/untypy create mode 120000 python/site-lib/wypp diff --git a/python/site-lib/README b/python/site-lib/README new file mode 100644 index 00000000..d2769afc --- /dev/null +++ b/python/site-lib/README @@ -0,0 +1,3 @@ +This directory contains symlinks for wypp and untypy. + +Add this directory to PYTHONPATH if you want to import wypp and untypy in-place. diff --git a/python/site-lib/untypy b/python/site-lib/untypy new file mode 120000 index 00000000..db337ec2 --- /dev/null +++ b/python/site-lib/untypy @@ -0,0 +1 @@ +../deps/untypy/untypy/ \ No newline at end of file diff --git a/python/site-lib/wypp b/python/site-lib/wypp new file mode 120000 index 00000000..e057607e --- /dev/null +++ b/python/site-lib/wypp @@ -0,0 +1 @@ +../src/ \ No newline at end of file From f4bb18ea0d8d31b1bfd31d09cabb13a97c6795df Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 15:10:11 +0200 Subject: [PATCH 11/40] test for replTester (fails!) --- python/integration-tests/testIntegration.py | 8 ++++++++ python/test-data/repl-test-checks.py | 2 ++ python/test-data/repl-test-lib.py | 10 ++++++++++ 3 files changed, 20 insertions(+) create mode 100644 python/test-data/repl-test-checks.py create mode 100644 python/test-data/repl-test-lib.py diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py index fb86d6e0..6ee393c6 100644 --- a/python/integration-tests/testIntegration.py +++ b/python/integration-tests/testIntegration.py @@ -166,3 +166,11 @@ def test_typesInImportedModule1(self): def test_typesInImportedModule2(self): out = run('test-data/testTypes3.py', tycheck=False) self.assertEqual('END', out) + +class ReplTesterTests(unittest.TestCase): + + def test_replTester(self): + d = shell.pwd() + cmd = f'python3 {d}/src/replTester.py {d}/test-data/repl-test-lib.py --repl {d}/test-data/repl-test-checks.py' + res = shell.run(cmd, captureStdout=True, onError='die', cwd='/tmp') + self.assertIn('All 1 tests succeded. Great!', res.stdout) diff --git a/python/test-data/repl-test-checks.py b/python/test-data/repl-test-checks.py new file mode 100644 index 00000000..8f2560c2 --- /dev/null +++ b/python/test-data/repl-test-checks.py @@ -0,0 +1,2 @@ +>>> dora.gewicht +25000 diff --git a/python/test-data/repl-test-lib.py b/python/test-data/repl-test-lib.py new file mode 100644 index 00000000..4b836670 --- /dev/null +++ b/python/test-data/repl-test-lib.py @@ -0,0 +1,10 @@ +from wypp import * + +Status = Literal['tot', 'lebendig'] + +@record +class Gürteltier: + gewicht: int + totOderLebendig: Status + +dora = Gürteltier(25000, 'lebendig') From 150c7a5b209ddb264fd104feae42eba4905c2be7 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 15:10:40 +0200 Subject: [PATCH 12/40] fix for failing replTester test I don't know why this fixes the test --- python/src/replTester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/replTester.py b/python/src/replTester.py index 2949901f..fbadce02 100644 --- a/python/src/replTester.py +++ b/python/src/replTester.py @@ -51,7 +51,7 @@ def parseCmdlineArgs(): libDir = os.path.dirname(__file__) libFile = os.path.join(libDir, 'writeYourProgram.py') -defs = {} +defs = globals() importUntypy() # runCode(libFile, defs, []) From 5da45e97c1a4712fe58632f98bfbb9fadb8b6300 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Tue, 21 Sep 2021 15:16:41 +0200 Subject: [PATCH 13/40] update TODO --- TODO.org | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.org b/TODO.org index 9e81e5fe..8da01239 100644 --- a/TODO.org +++ b/TODO.org @@ -2,7 +2,10 @@ ** add tests with type annotations *** for records **** write access +**** test for correct location +** understand why commit 150c7a5b209ddb264fd104feae42eba4905c2be7 is necessary to fix the replTester test * other +** mode where all functions or methods without type signatures are reported as errors ** upload dist to pypi ** update README *** python version From e9504aba742d533b4d1daa94fd7815e29003f205 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 22 Sep 2021 12:31:52 +0200 Subject: [PATCH 14/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 3c0759d3..8d11cacb 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 3c0759d345e3f5eb88cb8dae691593989a98db7c +Subproject commit 8d11cacbbb1f4a39fc887ea5d9a51b5ca7e1ffea From 8f9ec73ed12d47399a774b5a450c574073d5f62d Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 22 Sep 2021 12:32:42 +0200 Subject: [PATCH 15/40] fix tests --- python/integration-tests/shell.py | 8 +++-- python/integration-tests/testIntegration.py | 38 ++++++++++++--------- python/runFileTests.sh | 2 +- python/runTestsForPyVersion.sh | 2 ++ python/src/replTester.py | 15 ++++++-- python/src/runner.py | 1 + python/test-data/fileWithBothImports.py | 1 - python/test-data/fileWithImport.py | 1 - python/test-data/testTraceback2.err | 2 +- python/test-data/testTraceback2.err-3.9.0 | 5 --- python/test-data/testTypes1.err | 19 ++++------- python/test-data/testTypes2.err | 19 ++++------- 12 files changed, 58 insertions(+), 55 deletions(-) mode change 100644 => 100755 python/runFileTests.sh delete mode 100644 python/test-data/testTraceback2.err-3.9.0 diff --git a/python/integration-tests/shell.py b/python/integration-tests/shell.py index 7e573fc6..0d53673d 100644 --- a/python/integration-tests/shell.py +++ b/python/integration-tests/shell.py @@ -322,10 +322,12 @@ def hook(self): def exit(self, code=0): if code is None: - code = 0 + myCode = 0 elif type(code) != int: - code = 1 - self.exitCode = code + myCode = 1 + else: + myCode = code + self.exitCode = myCode self._origExit(code) def exc_handler(self, exc_type, exc, *args): diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py index 6ee393c6..477d4897 100644 --- a/python/integration-tests/testIntegration.py +++ b/python/integration-tests/testIntegration.py @@ -32,7 +32,7 @@ def stripTrailingWs(s): LOG_FILE = shell.mkTempFile(prefix="wypp-tests", suffix=".log", deleteAtExit='ifSuccess') print(f'Output of integration tests goes to {LOG_FILE}') -LOG_REDIR = f'> {LOG_FILE} 2>&1' +LOG_REDIR = f'>> {LOG_FILE} 2>&1' class TypeTests(unittest.TestCase): def test_enumOk(self): @@ -41,7 +41,7 @@ def test_enumOk(self): def test_enumTypeError(self): out = runInteractive('test-data/typeEnums.py', 'colorToNumber(1)')[0] - self.assertIn("expected: Literal['red', 'yellow', 'green']", out) + self.assertIn("expected: value of type Literal['red', 'yellow', 'green']", out) def test_recordOk(self): rec = 'test-data/typeRecords.py' @@ -53,12 +53,12 @@ def test_recordOk(self): def test_recordFail1(self): rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'Person("stefan", 42.3)')[0] - self.assertIn('expected: int', out) + self.assertIn('expected: value of type int', out) def test_recordFail2(self): rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'mutableIncAge(Person("stefan", 42))')[0] - self.assertIn('expected: MutablePerson', out) + self.assertIn('expected: value of type MutablePerson', out) def test_recordMutableOk(self): rec = 'test-data/typeRecords.py' @@ -70,18 +70,18 @@ def test_recordMutableOk(self): def test_mutableRecordFail1(self): rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'MutablePerson("stefan", 42.3)')[0] - self.assertIn('expected: int', out) + self.assertIn('expected: value of type int', out) def test_mutableRecordFail2(self): rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'incAge(MutablePerson("stefan", 42))')[0] - self.assertIn('expected: Person', out) + self.assertIn('expected: value of type Person', out) @unittest.skip def test_mutableRecordFail3(self): rec = 'test-data/typeRecords.py' out = runInteractive(rec, 'p = MutablePerson("stefan", 42)\np.age = 42.4') - self.assertIn('expected: int', out) + self.assertIn('expected: value of type int', out) def test_union(self): out = runInteractive('test-data/typeUnion.py', """formatAnimal(myCat) @@ -90,7 +90,7 @@ def test_union(self): """) self.assertEqual("'Cat Pumpernickel'", out[0]) self.assertEqual("\"Parrot Mike says: Let's go to the punkrock show\"", out[1]) - self.assertIn('given: None\nexpected: Union[Cat, Parrot]', out[2]) + self.assertIn('given: None\nexpected: value of type Union[Cat, Parrot]', out[2]) class StudentSubmissionTests(unittest.TestCase): def check(self, file, testFile, ecode, tycheck=True): @@ -134,19 +134,23 @@ def test_types1(self): def test_types2(self): out = runInteractive('test-data/testTypesInteractive.py', 'inc("3")')[0] - expected = """given: '3' -expected: int - ^^^ + expected = """untypy.error.UntypyTypeError +given: '3' +expected: value of type int -inside of inc(x: int) -> int - ^^^ -declared at:""" - self.assertIn(expected, stripTrailingWs(out)) +by function inc(x: int) -> int + ^^^ +declared at: /Users/swehr/devel/write-your-python-program/python/test-data/testTypesInteractive.py:1 + 1 | def inc(x: int) -> int: + 2 | return x + 1 + +caused by: :1""" + self.assertEqual(expected, out) def test_types3(self): out = runInteractive('test-data/testTypesInteractive.py', 'def f(x: int) -> int: return x\n\nf("x")')[1] - self.assertIn('expected: int', out) + self.assertIn('expected: value of type int', out) def test_types4(self): out = runInteractive('test-data/testTypesInteractive.py', @@ -161,7 +165,7 @@ def test_types5(self): def test_typesInImportedModule1(self): out = run('test-data/testTypes3.py', ecode=1) - self.assertIn('expected: int', out) + self.assertIn('expected: value of type int', out) def test_typesInImportedModule2(self): out = run('test-data/testTypes3.py', tycheck=False) diff --git a/python/runFileTests.sh b/python/runFileTests.sh old mode 100644 new mode 100755 index 01abde15..38d3f31b --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -10,7 +10,6 @@ d=$(pwd) siteDir=$(python3 -c 'import site; print(site.USER_SITE)') t=$(mktemp) -echo echo "Running file tests, siteDir=$siteDir ..." echo "Writing logs to $t" function check() @@ -37,6 +36,7 @@ function checkWithOutputAux() local tycheck="$1" local expectedEcode=$2 local file="$3" + echo "Checking $file" shift 3 tycheckOpt="" suffixes="${PYENV_VERSION}" diff --git a/python/runTestsForPyVersion.sh b/python/runTestsForPyVersion.sh index 9862f225..0af1708c 100755 --- a/python/runTestsForPyVersion.sh +++ b/python/runTestsForPyVersion.sh @@ -25,10 +25,12 @@ function usage() if [ -z "${1:-}" ]; then echo "Running all unit tests, PYTHONPATH=$unit_test_path" PYTHONPATH=$unit_test_path python3 -m unittest tests/test*.py + echo "Done with unit tests" echo prepare_integration_tests echo "Running all integration tests, PYTHONPATH=$integ_test_path" PYTHONPATH=$integ_test_path python3 -m unittest integration-tests/test*.py + echo "Done with integration tests" else if [ "$1" == "--unit" ]; then what="unit" diff --git a/python/src/replTester.py b/python/src/replTester.py index fbadce02..5f7e4ca7 100644 --- a/python/src/replTester.py +++ b/python/src/replTester.py @@ -17,6 +17,7 @@ @dataclass class Options: verbose: bool + diffOutput: bool libs: list[str] repls: list[str] @@ -26,6 +27,9 @@ def parseCmdlineArgs(): parser.add_argument('--verbose', dest='verbose', action='store_const', const=True, default=False, help='Be verbose') + parser.add_argument('--diffOutput', dest='diffOutput', + action='store_const', const=True, default=False, + help='print diff of expected/given output') args, restArgs = parser.parse_known_args() libs = [] repls = [] @@ -42,7 +46,7 @@ def parseCmdlineArgs(): if len(repls) == 0: print('No SAMPLE arguments given') sys.exit(1) - return Options(args.verbose, libs, repls) + return Options(verbose=args.verbose, diffOutput=args.diffOutput, libs=libs, repls=repls) opts = parseCmdlineArgs() @@ -67,8 +71,15 @@ def parseCmdlineArgs(): totalFailures = 0 totalTests = 0 +doctestOptions = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + +if opts.diffOutput: + doctestOptions = doctestOptions | doctest.REPORT_NDIFF + for repl in opts.repls: - (failures, tests) = doctest.testfile(repl, globs=defs, module_relative=False) + (failures, tests) = doctest.testfile(repl, globs=defs, module_relative=False, + optionflags=doctestOptions) + totalFailures += failures totalTests += tests if failures == 0: diff --git a/python/src/runner.py b/python/src/runner.py index 9ec5e904..19f6dc0c 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -360,6 +360,7 @@ def limitTraceback(fullTb): def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): (etype, val, tb) = sys.exc_info() if isinstance(val, untypy.error.UntypyTypeError) or isinstance(val, untypy.error.UntypyAttributeError): + file.write(etype.__module__ + "." + etype.__qualname__ + '\n') file.write(str(val)) file.write('\n') else: diff --git a/python/test-data/fileWithBothImports.py b/python/test-data/fileWithBothImports.py index 29df392d..489c85e4 100644 --- a/python/test-data/fileWithBothImports.py +++ b/python/test-data/fileWithBothImports.py @@ -11,4 +11,3 @@ def use(x): use(wypp.record) use(drawingLib.Point) use(Point) -use(wypp.list[int]) diff --git a/python/test-data/fileWithImport.py b/python/test-data/fileWithImport.py index e8b11c9b..861fdb88 100644 --- a/python/test-data/fileWithImport.py +++ b/python/test-data/fileWithImport.py @@ -6,4 +6,3 @@ def use(x): use(wypp) use(wypp.record) -use(wypp.list[int]) diff --git a/python/test-data/testTraceback2.err b/python/test-data/testTraceback2.err index 98bc37a8..281b1771 100644 --- a/python/test-data/testTraceback2.err +++ b/python/test-data/testTraceback2.err @@ -1,5 +1,5 @@ File "test-data/testTraceback2.py", line 3 lst = [1,2,3 - ^ + ^ SyntaxError: unexpected EOF while parsing diff --git a/python/test-data/testTraceback2.err-3.9.0 b/python/test-data/testTraceback2.err-3.9.0 deleted file mode 100644 index 281b1771..00000000 --- a/python/test-data/testTraceback2.err-3.9.0 +++ /dev/null @@ -1,5 +0,0 @@ - - File "test-data/testTraceback2.py", line 3 - lst = [1,2,3 - ^ -SyntaxError: unexpected EOF while parsing diff --git a/python/test-data/testTypes1.err b/python/test-data/testTypes1.err index dd25544c..63d0b9c8 100644 --- a/python/test-data/testTypes1.err +++ b/python/test-data/testTypes1.err @@ -1,17 +1,12 @@ +untypy.error.UntypyTypeError +given: '1' +expected: value of type int -given: '1' -expected: int - ^^^ - -inside of inc(x: int) -> int - ^^^ -declared at: -test-data/testTypes1.py:1 +by function inc(x: int) -> int + ^^^ +declared at: test-data/testTypes1.py:1 1 | def inc(x: int) -> int: 2 | return x + 1 - -caused by: -test-data/testTypes1.py:4 +caused by: test-data/testTypes1.py:4 4 | inc("1") - diff --git a/python/test-data/testTypes2.err b/python/test-data/testTypes2.err index ffd45cb0..45d5a9e2 100644 --- a/python/test-data/testTypes2.err +++ b/python/test-data/testTypes2.err @@ -1,17 +1,12 @@ +untypy.error.UntypyTypeError +given: '1' +expected: value of type int -given: '1' -expected: int - ^^^ - -inside of inc(x: int) -> int - ^^^ -declared at: -test-data/testTypes2.py:1 +by function inc(x: int) -> int + ^^^ +declared at: test-data/testTypes2.py:1 1 | def inc(x: int) -> int: 2 | return x - -caused by: -test-data/testTypes2.py:4 +caused by: test-data/testTypes2.py:4 4 | inc("1") - From e8e2b9d6f9afeb7f0040c34a75a6515d7a454389 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 22 Sep 2021 14:05:31 +0200 Subject: [PATCH 16/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 8d11cacb..66c8e6a7 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 8d11cacbbb1f4a39fc887ea5d9a51b5ca7e1ffea +Subproject commit 66c8e6a71c158f272b29ac9758ca72e234552d03 From febdc013e152a1c82ac4de819e485039849be5d2 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 22 Sep 2021 14:06:04 +0200 Subject: [PATCH 17/40] some new tests --- python/integration-tests/testIntegration.py | 4 +-- python/runFileTests.sh | 12 +++++++ python/test-data/testTypes1.err | 4 +-- python/test-data/testTypes2.err | 4 +-- python/test-data/testTypesCollections1.err | 12 +++++++ python/test-data/testTypesCollections1.out | 0 python/test-data/testTypesCollections1.py | 8 +++++ python/test-data/testTypesCollections2.err | 15 ++++++++ python/test-data/testTypesCollections2.out | 1 + python/test-data/testTypesCollections2.py | 10 ++++++ python/test-data/testTypesCollections3.py | 8 +++++ python/test-data/testTypesCollections4.py | 10 ++++++ python/test-data/testTypesHigherOrderFuns.py | 10 ++++++ python/test-data/testTypesProtos1.err | 34 +++++++++++++++++++ python/test-data/testTypesProtos1.out | 0 python/test-data/testTypesProtos1.py | 21 ++++++++++++ python/test-data/testTypesProtos2.py | 19 +++++++++++ python/test-data/testTypesProtos3.err | 14 ++++++++ python/test-data/testTypesProtos3.out | 0 python/test-data/testTypesProtos3.py | 19 +++++++++++ python/test-data/testTypesProtos4.py | 27 +++++++++++++++ .../test-data/testTypesRecordInheritance.py | 14 ++++++++ python/test-data/testTypesSubclassing1.py | 25 ++++++++++++++ 23 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 python/test-data/testTypesCollections1.err create mode 100644 python/test-data/testTypesCollections1.out create mode 100644 python/test-data/testTypesCollections1.py create mode 100644 python/test-data/testTypesCollections2.err create mode 100644 python/test-data/testTypesCollections2.out create mode 100644 python/test-data/testTypesCollections2.py create mode 100644 python/test-data/testTypesCollections3.py create mode 100644 python/test-data/testTypesCollections4.py create mode 100644 python/test-data/testTypesHigherOrderFuns.py create mode 100644 python/test-data/testTypesProtos1.err create mode 100644 python/test-data/testTypesProtos1.out create mode 100644 python/test-data/testTypesProtos1.py create mode 100644 python/test-data/testTypesProtos2.py create mode 100644 python/test-data/testTypesProtos3.err create mode 100644 python/test-data/testTypesProtos3.out create mode 100644 python/test-data/testTypesProtos3.py create mode 100644 python/test-data/testTypesProtos4.py create mode 100644 python/test-data/testTypesRecordInheritance.py create mode 100644 python/test-data/testTypesSubclassing1.py diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py index 477d4897..d59cf9d4 100644 --- a/python/integration-tests/testIntegration.py +++ b/python/integration-tests/testIntegration.py @@ -138,8 +138,8 @@ def test_types2(self): given: '3' expected: value of type int -by function inc(x: int) -> int - ^^^ +context: inc(x: int) -> int + ^^^ declared at: /Users/swehr/devel/write-your-python-program/python/test-data/testTypesInteractive.py:1 1 | def inc(x: int) -> int: 2 | return x + 1 diff --git a/python/runFileTests.sh b/python/runFileTests.sh index 38d3f31b..cca5d378 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -109,3 +109,15 @@ checkWithOutput 0 test-data/printModuleName.py checkWithOutput 0 test-data/printModuleNameImport.py checkWithOutput 1 test-data/testTypes1.py checkWithOutput 1:0 test-data/testTypes2.py +checkWithOutputAux yes 1 test-data/testTypesCollections1.py +checkWithOutputAux yes 1 test-data/testTypesCollections2.py +# checkWithOutputAux yes 1 test-data/testTypesCollections3.py +# checkWithOutputAux yes 1 test-data/testTypesCollections4.py +checkWithOutputAux yes 1 test-data/testTypesProtos1.py +# checkWithOutputAux yes 1 test-data/testTypesProtos2.py +checkWithOutputAux yes 1 test-data/testTypesProtos3.py +# checkWithOutputAux yes 1 test-data/testTypesProtos4.py +# checkWithOutputAux yes 1 test-data/testTypesSubclassing1.py +# checkWithOutputAux yes 1 test-data/testTypesHigherOrderFuns.py +# checkWithOutputAux yes 1 test-data/testTypesRecordInheritance.py + diff --git a/python/test-data/testTypes1.err b/python/test-data/testTypes1.err index 63d0b9c8..d52cba13 100644 --- a/python/test-data/testTypes1.err +++ b/python/test-data/testTypes1.err @@ -2,8 +2,8 @@ untypy.error.UntypyTypeError given: '1' expected: value of type int -by function inc(x: int) -> int - ^^^ +context: inc(x: int) -> int + ^^^ declared at: test-data/testTypes1.py:1 1 | def inc(x: int) -> int: 2 | return x + 1 diff --git a/python/test-data/testTypes2.err b/python/test-data/testTypes2.err index 45d5a9e2..6a5fed85 100644 --- a/python/test-data/testTypes2.err +++ b/python/test-data/testTypes2.err @@ -2,8 +2,8 @@ untypy.error.UntypyTypeError given: '1' expected: value of type int -by function inc(x: int) -> int - ^^^ +context: inc(x: int) -> int + ^^^ declared at: test-data/testTypes2.py:1 1 | def inc(x: int) -> int: 2 | return x diff --git a/python/test-data/testTypesCollections1.err b/python/test-data/testTypesCollections1.err new file mode 100644 index 00000000..2606513a --- /dev/null +++ b/python/test-data/testTypesCollections1.err @@ -0,0 +1,12 @@ +untypy.error.UntypyTypeError +given: 'foo' +expected: value of type int + +context: list[int] + ^^^ +declared at: test-data/testTypesCollections1.py:3 + 3 | def appendSomething(l: list[int]) -> None: + 4 | l.append("foo") + +caused by: test-data/testTypesCollections1.py:4 + 4 | l.append("foo") diff --git a/python/test-data/testTypesCollections1.out b/python/test-data/testTypesCollections1.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testTypesCollections1.py b/python/test-data/testTypesCollections1.py new file mode 100644 index 00000000..be569e10 --- /dev/null +++ b/python/test-data/testTypesCollections1.py @@ -0,0 +1,8 @@ +from wypp import * + +def appendSomething(l: list[int]) -> None: + l.append("foo") + +l = [1,2,3] +appendSomething(l) +print(l) diff --git a/python/test-data/testTypesCollections2.err b/python/test-data/testTypesCollections2.err new file mode 100644 index 00000000..1f7b2b48 --- /dev/null +++ b/python/test-data/testTypesCollections2.err @@ -0,0 +1,15 @@ +untypy.error.UntypyTypeError +given: 42 +expected: value of type str + +context: foo(l: list[Callable[[], str]]) -> list[str] + ^^^ +declared at: test-data/testTypesCollections2.py:3 + 3 | def foo(l: list[Callable[[], str]]) -> list[str]: + 4 | res = [] + 5 | for f in l: + 6 | res.append(f()) + 7 | return res + +caused by: test-data/testTypesCollections2.py:10 + 10 | foo([lambda: "1", lambda: 42]) # error because the 2nd functions returns an int diff --git a/python/test-data/testTypesCollections2.out b/python/test-data/testTypesCollections2.out new file mode 100644 index 00000000..189744a2 --- /dev/null +++ b/python/test-data/testTypesCollections2.out @@ -0,0 +1 @@ +['1', '2'] diff --git a/python/test-data/testTypesCollections2.py b/python/test-data/testTypesCollections2.py new file mode 100644 index 00000000..4e9c4997 --- /dev/null +++ b/python/test-data/testTypesCollections2.py @@ -0,0 +1,10 @@ +from wypp import * + +def foo(l: list[Callable[[], str]]) -> list[str]: + res = [] + for f in l: + res.append(f()) + return res + +print(foo([lambda: "1", lambda: "2"])) +foo([lambda: "1", lambda: 42]) # error because the 2nd functions returns an int diff --git a/python/test-data/testTypesCollections3.py b/python/test-data/testTypesCollections3.py new file mode 100644 index 00000000..0bd390ec --- /dev/null +++ b/python/test-data/testTypesCollections3.py @@ -0,0 +1,8 @@ +from wypp import * + +def appendSomething(l: list[int], l2: list[int]) -> None: + l.append("foo") + +l = [1,2,3] +appendSomething(l, []) +print(l) diff --git a/python/test-data/testTypesCollections4.py b/python/test-data/testTypesCollections4.py new file mode 100644 index 00000000..8ca810bc --- /dev/null +++ b/python/test-data/testTypesCollections4.py @@ -0,0 +1,10 @@ +from wypp import * + +def foo(l: list[Callable[[], str]]) -> list[str]: + l.append(lambda: 42) # error + res = [] + for f in l: + res.append(f()) + return res + +foo([]) diff --git a/python/test-data/testTypesHigherOrderFuns.py b/python/test-data/testTypesHigherOrderFuns.py new file mode 100644 index 00000000..68a4abe5 --- /dev/null +++ b/python/test-data/testTypesHigherOrderFuns.py @@ -0,0 +1,10 @@ +from wypp import * + +def map(container: Iterable[str], fun: Callable[[str], int]) -> list[int]: + res = [] + for x in container: + res.append(fun(x)) + return res + +print(map(["hello", "1"], len)) +map(["hello", "1"], lambda x: x) diff --git a/python/test-data/testTypesProtos1.err b/python/test-data/testTypesProtos1.err new file mode 100644 index 00000000..e38d02ae --- /dev/null +++ b/python/test-data/testTypesProtos1.err @@ -0,0 +1,34 @@ +untypy.error.UntypyTypeError +Type 'Dog' does not implement protocol 'Animal' correctly. + +given: +expected: value of type Animal + +context: doSomething(a: Animal) -> None + ^^^^^^ +declared at: test-data/testTypesProtos1.py:18 + 18 | def doSomething(a: Animal) -> None: + 19 | print(a.makeSound(3.14)) + +caused by: test-data/testTypesProtos1.py:21 + 21 | doSomething(Dog()) + +The argument 'loadness' of method 'makeSound' violates the protocol 'Animal'. +The annotation 'int' is incompatible with the protocol's annotation 'float' +when checking against the following value: + +given: 3.14 +expected: value of type int + +context: makeSound(self: Self, loadness: int) -> str + ^^^ +declared at: test-data/testTypesProtos1.py:13 + 13 | def makeSound(self, loadness: int) -> str: + 14 | return f"{loadness} wuffs" +test-data/testTypesProtos1.py:8 + 8 | def makeSound(self, loadness: float) -> str: + 9 | pass + +caused by: test-data/testTypesProtos1.py:13 + 13 | def makeSound(self, loadness: int) -> str: + 14 | return f"{loadness} wuffs" diff --git a/python/test-data/testTypesProtos1.out b/python/test-data/testTypesProtos1.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testTypesProtos1.py b/python/test-data/testTypesProtos1.py new file mode 100644 index 00000000..167878bb --- /dev/null +++ b/python/test-data/testTypesProtos1.py @@ -0,0 +1,21 @@ +from wypp import * + +from typing import Protocol +import abc + +class Animal(Protocol): + @abc.abstractmethod + def makeSound(self, loadness: float) -> str: + pass + +class Dog: + # incorrect implementation of the Animal protocol + def makeSound(self, loadness: int) -> str: + return f"{loadness} wuffs" + def __repr__(self): + return "" + +def doSomething(a: Animal) -> None: + print(a.makeSound(3.14)) + +doSomething(Dog()) diff --git a/python/test-data/testTypesProtos2.py b/python/test-data/testTypesProtos2.py new file mode 100644 index 00000000..fa2c6e3d --- /dev/null +++ b/python/test-data/testTypesProtos2.py @@ -0,0 +1,19 @@ +from wypp import * + +from typing import Protocol +import abc + +class Animal(Protocol): + @abc.abstractmethod + def makeSound(loadness: float) -> str: # self parameter omitted! + pass + +class Dog: + # incorrect implementation of the Animal protocol + def makeSound(self, loadness: int) -> str: + return f"{loadness} wuffs" + +def doSomething(a: Animal) -> None: + print(a.makeSound(3.14)) + +doSomething(Dog()) diff --git a/python/test-data/testTypesProtos3.err b/python/test-data/testTypesProtos3.err new file mode 100644 index 00000000..4be46a5f --- /dev/null +++ b/python/test-data/testTypesProtos3.err @@ -0,0 +1,14 @@ +untypy.error.UntypyTypeError +Type Dog does not meet the requirements of protocol Animal. The signature of 'makeSound' does not match. Missing required parameter self + +given: 'Dog' +expected: value of type Animal + +context: doSomething(a: Animal) -> None + ^^^^^^ +declared at: test-data/testTypesProtos3.py:16 + 16 | def doSomething(a: Animal) -> None: + 17 | print(a.makeSound(3.14)) + +caused by: test-data/testTypesProtos3.py:19 + 19 | doSomething(Dog()) diff --git a/python/test-data/testTypesProtos3.out b/python/test-data/testTypesProtos3.out new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testTypesProtos3.py b/python/test-data/testTypesProtos3.py new file mode 100644 index 00000000..334c2424 --- /dev/null +++ b/python/test-data/testTypesProtos3.py @@ -0,0 +1,19 @@ +from wypp import * + +from typing import Protocol +import abc + +class Animal(Protocol): + @abc.abstractmethod + def makeSound(self, loadness: float) -> str: + pass + +class Dog: + # incorrect implementation of the Animal protocol + def makeSound(loadness: int) -> str: # self parameter omitted! + return f"{loadness} wuffs" + +def doSomething(a: Animal) -> None: + print(a.makeSound(3.14)) + +doSomething(Dog()) diff --git a/python/test-data/testTypesProtos4.py b/python/test-data/testTypesProtos4.py new file mode 100644 index 00000000..5ce3a127 --- /dev/null +++ b/python/test-data/testTypesProtos4.py @@ -0,0 +1,27 @@ +from wypp import * + +from typing import Protocol +import abc + +class Interface(Protocol): + abc.abstractmethod + def meth(self) -> Callable[[int], int]: + pass + +class ConcreteCorrect: + def meth(self) -> Callable[[int], int]: + return lambda x: x + 1 + +def bar(s: str) -> int: + return len(s) + +class ConcreteWrong: + def meth(self) -> Callable[[int], int]: + return lambda x: bar(x) # invalid call of bar with argument of type int + +def foo(obj: Interface) -> int: + fn = obj.meth() + return fn(2) + +print(foo(ConcreteCorrect())) +print(foo(ConcreteWrong())) diff --git a/python/test-data/testTypesRecordInheritance.py b/python/test-data/testTypesRecordInheritance.py new file mode 100644 index 00000000..6dfd7037 --- /dev/null +++ b/python/test-data/testTypesRecordInheritance.py @@ -0,0 +1,14 @@ +from wypp import * + +@record +class Point2D: + x: int + y: int + +@record +class Point3D(Point2D): + z: int + +print(Point3D(1,2,3)) +Point3D(1,2, "foo") + diff --git a/python/test-data/testTypesSubclassing1.py b/python/test-data/testTypesSubclassing1.py new file mode 100644 index 00000000..3bba042c --- /dev/null +++ b/python/test-data/testTypesSubclassing1.py @@ -0,0 +1,25 @@ +from wypp import * + +class AnimalFood: + def __init__(self, name: str): + self.name = name + +class Animal: + def feed(self, food: AnimalFood) -> None: + print(f'Tasty animal food: {food.name}') + +class DogFood(AnimalFood): + def __init__(self, name: str, weight: float): + super().__init__(name) + self.weight = weight + +class Dog(Animal): + # Dog provides an invalid override for feed + def feed(self, food: DogFood) -> None: + print(f'Tasty dog food: {food.name} ({food.weight}g)') + +def feedAnimal(a: Animal) -> None: + a.feed(AnimalFood('some cat food')) + +dog = Dog() +feedAnimal(dog) From 4ad786ed3a11005544e313aea62921250b8b4f30 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 22 Sep 2021 14:06:14 +0200 Subject: [PATCH 18/40] export type vars from wypp --- python/src/__init__.py | 8 +++++++- python/src/writeYourProgram.py | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/python/src/__init__.py b/python/src/__init__.py index c866c517..6d44652e 100644 --- a/python/src/__init__.py +++ b/python/src/__init__.py @@ -17,7 +17,10 @@ math = w.math nat = w.nat record = w.record +T = w.T +U = w.U unchecked = w.unchecked +V = w.V __all__ = [ 'Any', @@ -36,7 +39,10 @@ 'math', 'nat', 'record', - 'unchecked' + 'T', + 'U', + 'unchecked', + 'V' ] # Exported names not available for star imports (in alphabetic order) diff --git a/python/src/writeYourProgram.py b/python/src/writeYourProgram.py index d82f36ef..c0d66e65 100644 --- a/python/src/writeYourProgram.py +++ b/python/src/writeYourProgram.py @@ -26,10 +26,13 @@ def _debug(s): dataclass = dataclasses.dataclass -# Reexports for Untypy unchecked = untypy.unchecked nat = typing.Annotated[int, lambda i: i >= 0] +T = typing.TypeVar('T') +U = typing.TypeVar('U') +V = typing.TypeVar('V') + def _patchDataClass(cls, mutable): fieldNames = [f.name for f in dataclasses.fields(cls)] setattr(cls, EQ_ATTRS_ATTR, fieldNames) From 6a3854bcfd31e12c6dcb5a4903605834b7abb05f Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 22 Sep 2021 14:30:07 +0200 Subject: [PATCH 19/40] add github issues to failing tests --- python/runFileTests.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python/runFileTests.sh b/python/runFileTests.sh index cca5d378..c9254550 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -111,13 +111,13 @@ checkWithOutput 1 test-data/testTypes1.py checkWithOutput 1:0 test-data/testTypes2.py checkWithOutputAux yes 1 test-data/testTypesCollections1.py checkWithOutputAux yes 1 test-data/testTypesCollections2.py -# checkWithOutputAux yes 1 test-data/testTypesCollections3.py -# checkWithOutputAux yes 1 test-data/testTypesCollections4.py +# checkWithOutputAux yes 1 test-data/testTypesCollections3.py See #5 +# checkWithOutputAux yes 1 test-data/testTypesCollections4.py See #6 checkWithOutputAux yes 1 test-data/testTypesProtos1.py -# checkWithOutputAux yes 1 test-data/testTypesProtos2.py +# checkWithOutputAux yes 1 test-data/testTypesProtos2.py See #8 checkWithOutputAux yes 1 test-data/testTypesProtos3.py -# checkWithOutputAux yes 1 test-data/testTypesProtos4.py -# checkWithOutputAux yes 1 test-data/testTypesSubclassing1.py -# checkWithOutputAux yes 1 test-data/testTypesHigherOrderFuns.py -# checkWithOutputAux yes 1 test-data/testTypesRecordInheritance.py +# checkWithOutputAux yes 1 test-data/testTypesProtos4.py See #9 +# checkWithOutputAux yes 1 test-data/testTypesSubclassing1.py See #10 +# checkWithOutputAux yes 1 test-data/testTypesHigherOrderFuns.py See #7 +# checkWithOutputAux yes 1 test-data/testTypesRecordInheritance.py See #11 From f84f048ef3a966d9c254845462a3a4f6d6eb8587 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Wed, 22 Sep 2021 15:00:22 +0200 Subject: [PATCH 20/40] update untypy --- python/deps/untypy | 2 +- python/src/runner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/deps/untypy b/python/deps/untypy index 66c8e6a7..fca45b4c 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 66c8e6a71c158f272b29ac9758ca72e234552d03 +Subproject commit fca45b4c7aa08ae250155bb4ba84daa18a41090e diff --git a/python/src/runner.py b/python/src/runner.py index 19f6dc0c..5a5417a7 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -360,7 +360,7 @@ def limitTraceback(fullTb): def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): (etype, val, tb) = sys.exc_info() if isinstance(val, untypy.error.UntypyTypeError) or isinstance(val, untypy.error.UntypyAttributeError): - file.write(etype.__module__ + "." + etype.__qualname__ + '\n') + file.write(etype.__module__ + "." + etype.__qualname__) file.write(str(val)) file.write('\n') else: From 7ac53ac5ee6bbed0d51fbe1cbf905e7a2e588d6a Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 11:02:16 +0200 Subject: [PATCH 21/40] tests for forward refs --- python/runFileTests.sh | 4 ++++ python/test-data/testForwardRef1.err | 0 python/test-data/testForwardRef1.out | 1 + python/test-data/testForwardRef1.py | 11 +++++++++++ python/test-data/testForwardRef2.py | 11 +++++++++++ python/test-data/testForwardRef3.err | 0 python/test-data/testForwardRef3.out | 1 + python/test-data/testForwardRef3.py | 12 ++++++++++++ python/test-data/testForwardRef4.py | 12 ++++++++++++ 9 files changed, 52 insertions(+) create mode 100644 python/test-data/testForwardRef1.err create mode 100644 python/test-data/testForwardRef1.out create mode 100644 python/test-data/testForwardRef1.py create mode 100644 python/test-data/testForwardRef2.py create mode 100644 python/test-data/testForwardRef3.err create mode 100644 python/test-data/testForwardRef3.out create mode 100644 python/test-data/testForwardRef3.py create mode 100644 python/test-data/testForwardRef4.py diff --git a/python/runFileTests.sh b/python/runFileTests.sh index c9254550..dfa31981 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -120,4 +120,8 @@ checkWithOutputAux yes 1 test-data/testTypesProtos3.py # checkWithOutputAux yes 1 test-data/testTypesSubclassing1.py See #10 # checkWithOutputAux yes 1 test-data/testTypesHigherOrderFuns.py See #7 # checkWithOutputAux yes 1 test-data/testTypesRecordInheritance.py See #11 +checkWithOutputAux yes 0 test-data/testForwardRef1.py +# checkWithOutputAux yes 1 test-data/testForwardRef2.py +checkWithOutputAux yes 0 test-data/testForwardRef3.py +# checkWithOutputAux yes 1 test-data/testForwardRef4.py diff --git a/python/test-data/testForwardRef1.err b/python/test-data/testForwardRef1.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testForwardRef1.out b/python/test-data/testForwardRef1.out new file mode 100644 index 00000000..345e6aef --- /dev/null +++ b/python/test-data/testForwardRef1.out @@ -0,0 +1 @@ +Test diff --git a/python/test-data/testForwardRef1.py b/python/test-data/testForwardRef1.py new file mode 100644 index 00000000..9aed46b3 --- /dev/null +++ b/python/test-data/testForwardRef1.py @@ -0,0 +1,11 @@ +class Test: + def __init__(self, foo: 'Foo'): + pass + def __repr__(self): + return 'Test' + +class Foo: + pass + +t = Test(Foo()) +print(t) diff --git a/python/test-data/testForwardRef2.py b/python/test-data/testForwardRef2.py new file mode 100644 index 00000000..40523896 --- /dev/null +++ b/python/test-data/testForwardRef2.py @@ -0,0 +1,11 @@ +class Test: + def __init__(self, foo: 'Foo'): + pass + def __repr__(self): + return 'Test' + +class FooX: + pass + +t = Test(FooX()) +print(t) diff --git a/python/test-data/testForwardRef3.err b/python/test-data/testForwardRef3.err new file mode 100644 index 00000000..e69de29b diff --git a/python/test-data/testForwardRef3.out b/python/test-data/testForwardRef3.out new file mode 100644 index 00000000..82cf2102 --- /dev/null +++ b/python/test-data/testForwardRef3.out @@ -0,0 +1 @@ +Test(foo=Foo) diff --git a/python/test-data/testForwardRef3.py b/python/test-data/testForwardRef3.py new file mode 100644 index 00000000..d0e3759f --- /dev/null +++ b/python/test-data/testForwardRef3.py @@ -0,0 +1,12 @@ +from wypp import * + +@record +class Test: + foo: 'Foo' + +class Foo: + def __repr__(self): + return 'Foo' + +t = Test(Foo()) +print(t) diff --git a/python/test-data/testForwardRef4.py b/python/test-data/testForwardRef4.py new file mode 100644 index 00000000..2cb7d5ff --- /dev/null +++ b/python/test-data/testForwardRef4.py @@ -0,0 +1,12 @@ +from wypp import * + +@record +class Test: + foo: 'FooX' + +class Foo: + def __repr__(self): + return 'Foo' + +t = Test(Foo()) +print(t) From ba89fb14c39f50b5bf6a7100526f7550be7d2f7d Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 11:23:40 +0200 Subject: [PATCH 22/40] refer to github issue --- python/runFileTests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/runFileTests.sh b/python/runFileTests.sh index dfa31981..b6add8e9 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -121,7 +121,7 @@ checkWithOutputAux yes 1 test-data/testTypesProtos3.py # checkWithOutputAux yes 1 test-data/testTypesHigherOrderFuns.py See #7 # checkWithOutputAux yes 1 test-data/testTypesRecordInheritance.py See #11 checkWithOutputAux yes 0 test-data/testForwardRef1.py -# checkWithOutputAux yes 1 test-data/testForwardRef2.py +# checkWithOutputAux yes 1 test-data/testForwardRef2.py See #14 checkWithOutputAux yes 0 test-data/testForwardRef3.py -# checkWithOutputAux yes 1 test-data/testForwardRef4.py +# checkWithOutputAux yes 1 test-data/testForwardRef4.py See #14 From 96b5fa4bdc22fa8b0ab02ddb2714e9a7e2e7805f Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 11:23:54 +0200 Subject: [PATCH 23/40] do not re-export internal ForwardRef type --- python/src/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/src/__init__.py b/python/src/__init__.py index 6d44652e..887dfe6d 100644 --- a/python/src/__init__.py +++ b/python/src/__init__.py @@ -3,7 +3,6 @@ # Exported names that are available for star imports (in alphabetic order) Any = w.Any Callable = w.Callable -ForwardRef = w.ForwardRef Generator = w.Generator Iterable = w.Iterable Iterator = w.Iterator @@ -25,7 +24,6 @@ __all__ = [ 'Any', 'Callable', - 'ForwardRef', 'Generator', 'Iterable', 'Iterator', From 1ba6f54186e7a3e9f0e4999782cd1e47a4263b41 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 11:24:25 +0200 Subject: [PATCH 24/40] improve formatting of exceptions --- python/src/runner.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/src/runner.py b/python/src/runner.py index 5a5417a7..86f04310 100644 --- a/python/src/runner.py +++ b/python/src/runner.py @@ -361,7 +361,10 @@ def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr): (etype, val, tb) = sys.exc_info() if isinstance(val, untypy.error.UntypyTypeError) or isinstance(val, untypy.error.UntypyAttributeError): file.write(etype.__module__ + "." + etype.__qualname__) - file.write(str(val)) + s = str(val) + if s and s[0] != '\n': + file.write(': ') + file.write(s) file.write('\n') else: if tb and removeFirstTb: From 6da4d2588c6de8d33695e4499443c4a8bef8ea77 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 12:56:21 +0200 Subject: [PATCH 25/40] test for return type --- python/runFileTests.sh | 2 ++ python/test-data/testTypesReturn.py | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 python/test-data/testTypesReturn.py diff --git a/python/runFileTests.sh b/python/runFileTests.sh index b6add8e9..2cf0dc0b 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -124,4 +124,6 @@ checkWithOutputAux yes 0 test-data/testForwardRef1.py # checkWithOutputAux yes 1 test-data/testForwardRef2.py See #14 checkWithOutputAux yes 0 test-data/testForwardRef3.py # checkWithOutputAux yes 1 test-data/testForwardRef4.py See #14 +# checkWithOutputAux yes 1 test-data/testTypesReturn.py See #15 + diff --git a/python/test-data/testTypesReturn.py b/python/test-data/testTypesReturn.py new file mode 100644 index 00000000..f1aaa969 --- /dev/null +++ b/python/test-data/testTypesReturn.py @@ -0,0 +1,8 @@ +def foo(flag: bool) -> int: + print('Hello World') + if flag: + return 1 + else: + return 'you stupid' + +foo(False) From 5b66956fc884db327dae91031def8d50ce3b263f Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 12:57:39 +0200 Subject: [PATCH 26/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index fca45b4c..edd6b784 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit fca45b4c7aa08ae250155bb4ba84daa18a41090e +Subproject commit edd6b784775a273f019809a1ba76b8330c2f7256 From a47f8b492a827069e4fc1de77a5fe4464dafc7a2 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 12:57:52 +0200 Subject: [PATCH 27/40] script for invoking runYourProgram.py --- python/run | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 python/run diff --git a/python/run b/python/run new file mode 100755 index 00000000..0d92f2cb --- /dev/null +++ b/python/run @@ -0,0 +1,3 @@ +#!/bin/bash + +PYTHONPATH=site-lib/ python3 src/runYourProgram.py "$@" From 9f311a1683adee7e60a3c09baa4c03941b313473 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 12:58:12 +0200 Subject: [PATCH 28/40] add .ignore file for vscode --- python/.ignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 python/.ignore diff --git a/python/.ignore b/python/.ignore new file mode 100644 index 00000000..eed76414 --- /dev/null +++ b/python/.ignore @@ -0,0 +1,2 @@ +site-lib/wypp +site-lib/untypy From d72343b132a81671dbcc16fe296c43df3b18dc7e Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 14:40:16 +0200 Subject: [PATCH 29/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index edd6b784..8b05ed2a 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit edd6b784775a273f019809a1ba76b8330c2f7256 +Subproject commit 8b05ed2ae8284e7f86ca6370170cec65b0cb878a From 55570ef85b9b81223c224ba41b3d970a9a404fd0 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 14:40:23 +0200 Subject: [PATCH 30/40] make run script more powerful --- python/run | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/run b/python/run index 0d92f2cb..20dcb450 100755 --- a/python/run +++ b/python/run @@ -1,3 +1,5 @@ #!/bin/bash -PYTHONPATH=site-lib/ python3 src/runYourProgram.py "$@" +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +PYTHONPATH="$SCRIPT_DIR"/site-lib/ python3 "$SCRIPT_DIR"/src/runYourProgram.py "$@" From fbdfd0140156a1adb8a47e44ef2ee5c163b6ead6 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 14:42:55 +0200 Subject: [PATCH 31/40] test for sequences --- python/runFileTests.sh | 1 + python/test-data/testTypesSequence.py | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 python/test-data/testTypesSequence.py diff --git a/python/runFileTests.sh b/python/runFileTests.sh index 2cf0dc0b..f97a6b84 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -125,5 +125,6 @@ checkWithOutputAux yes 0 test-data/testForwardRef1.py checkWithOutputAux yes 0 test-data/testForwardRef3.py # checkWithOutputAux yes 1 test-data/testForwardRef4.py See #14 # checkWithOutputAux yes 1 test-data/testTypesReturn.py See #15 +# checkWithOutputAux yes 1 test-data/testTypesSequence.py See #16 diff --git a/python/test-data/testTypesSequence.py b/python/test-data/testTypesSequence.py new file mode 100644 index 00000000..eaee72a4 --- /dev/null +++ b/python/test-data/testTypesSequence.py @@ -0,0 +1,9 @@ +from wypp import * + +def foo(seq: Sequence): + pass + +foo([1,2,3]) +foo( ("bar", "baz") ) +foo("Hello!") +foo(1) # should fail From 976237011deec791a0e134aec9d288fdf7cb0e0f Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 15:13:55 +0200 Subject: [PATCH 32/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 8b05ed2a..30956605 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 8b05ed2ae8284e7f86ca6370170cec65b0cb878a +Subproject commit 30956605f1b5bfadab8796b7c0440a7fb59b4bf3 From 5d63a48825115a934ec3d542d5e7be9cdc495a0b Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 15:14:09 +0200 Subject: [PATCH 33/40] test for variadic tuples --- python/runFileTests.sh | 2 ++ python/test-data/testTypesTuple1.err | 12 ++++++++++++ python/test-data/testTypesTuple1.out | 1 + python/test-data/testTypesTuple1.py | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 python/test-data/testTypesTuple1.err create mode 100644 python/test-data/testTypesTuple1.out create mode 100644 python/test-data/testTypesTuple1.py diff --git a/python/runFileTests.sh b/python/runFileTests.sh index f97a6b84..cb7cabb1 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -126,5 +126,7 @@ checkWithOutputAux yes 0 test-data/testForwardRef3.py # checkWithOutputAux yes 1 test-data/testForwardRef4.py See #14 # checkWithOutputAux yes 1 test-data/testTypesReturn.py See #15 # checkWithOutputAux yes 1 test-data/testTypesSequence.py See #16 +# checkWithOutputAux yes 1 test-data/testTypesSequence2.py See #16 +checkWithOutputAux yes 1 test-data/testTypesTuple1.py diff --git a/python/test-data/testTypesTuple1.err b/python/test-data/testTypesTuple1.err new file mode 100644 index 00000000..c9ae74fb --- /dev/null +++ b/python/test-data/testTypesTuple1.err @@ -0,0 +1,12 @@ +untypy.error.UntypyTypeError +given: 1 +expected: value of type tuple[int, ...] + +context: foo(l: tuple[int, ...]) -> int + ^^^^^^^^^^^^^^^ +declared at: test-data/testTypesTuple1.py:1 + 1 | def foo(l: tuple[int, ...]) -> int: + 2 | return len(l) + +caused by: test-data/testTypesTuple1.py:5 + 5 | foo(1) diff --git a/python/test-data/testTypesTuple1.out b/python/test-data/testTypesTuple1.out new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/python/test-data/testTypesTuple1.out @@ -0,0 +1 @@ +3 diff --git a/python/test-data/testTypesTuple1.py b/python/test-data/testTypesTuple1.py new file mode 100644 index 00000000..29ea2c3c --- /dev/null +++ b/python/test-data/testTypesTuple1.py @@ -0,0 +1,5 @@ +def foo(l: tuple[int, ...]) -> int: + return len(l) + +print(foo((1,2,3))) +foo(1) From 14f55702d69ea40feafbcae7c05c4fadfc9f4439 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 15:14:28 +0200 Subject: [PATCH 34/40] failing test for sequence --- python/test-data/testTypesSequence2.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 python/test-data/testTypesSequence2.py diff --git a/python/test-data/testTypesSequence2.py b/python/test-data/testTypesSequence2.py new file mode 100644 index 00000000..e1a48c82 --- /dev/null +++ b/python/test-data/testTypesSequence2.py @@ -0,0 +1,8 @@ +from wypp import * + +def foo(seq: Sequence[int]): + pass + +foo([1,2,3]) +foo( (4,5) ) +foo("Hello!") # should fail From 3091c20e48a948f8e597757fa38580f629535f2f Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 15:38:55 +0200 Subject: [PATCH 35/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 30956605..61d5d1b4 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 30956605f1b5bfadab8796b7c0440a7fb59b4bf3 +Subproject commit 61d5d1b4ff24a63e030ba43f2c65e150d7821860 From 50340acf081c3046794c5232e00ce8afb42158b8 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 15:39:20 +0200 Subject: [PATCH 36/40] test for Sequence --- python/runFileTests.sh | 4 ++-- python/test-data/testTypesSequence1.err | 13 +++++++++++++ python/test-data/testTypesSequence1.out | 3 +++ .../{testTypesSequence.py => testTypesSequence1.py} | 3 ++- python/test-data/testTypesSequence2.err | 13 +++++++++++++ python/test-data/testTypesSequence2.out | 2 ++ python/test-data/testTypesSequence2.py | 3 ++- 7 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 python/test-data/testTypesSequence1.err create mode 100644 python/test-data/testTypesSequence1.out rename python/test-data/{testTypesSequence.py => testTypesSequence1.py} (68%) create mode 100644 python/test-data/testTypesSequence2.err create mode 100644 python/test-data/testTypesSequence2.out diff --git a/python/runFileTests.sh b/python/runFileTests.sh index cb7cabb1..a1652c03 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -125,8 +125,8 @@ checkWithOutputAux yes 0 test-data/testForwardRef1.py checkWithOutputAux yes 0 test-data/testForwardRef3.py # checkWithOutputAux yes 1 test-data/testForwardRef4.py See #14 # checkWithOutputAux yes 1 test-data/testTypesReturn.py See #15 -# checkWithOutputAux yes 1 test-data/testTypesSequence.py See #16 -# checkWithOutputAux yes 1 test-data/testTypesSequence2.py See #16 +checkWithOutputAux yes 1 test-data/testTypesSequence1.py +checkWithOutputAux yes 1 test-data/testTypesSequence2.py checkWithOutputAux yes 1 test-data/testTypesTuple1.py diff --git a/python/test-data/testTypesSequence1.err b/python/test-data/testTypesSequence1.err new file mode 100644 index 00000000..eb475241 --- /dev/null +++ b/python/test-data/testTypesSequence1.err @@ -0,0 +1,13 @@ +untypy.error.UntypyTypeError +given: 1 +expected: value of type Sequence + +context: foo(seq: Sequence) -> None + ^^^^^^^^ +declared at: test-data/testTypesSequence1.py:3 + 3 | def foo(seq: Sequence) -> None: + 4 | print(seq) + 5 | pass + +caused by: test-data/testTypesSequence1.py:10 + 10 | foo(1) # should fail diff --git a/python/test-data/testTypesSequence1.out b/python/test-data/testTypesSequence1.out new file mode 100644 index 00000000..f4ce9837 --- /dev/null +++ b/python/test-data/testTypesSequence1.out @@ -0,0 +1,3 @@ +[1, 2, 3] +('bar', 'baz') +Hello! diff --git a/python/test-data/testTypesSequence.py b/python/test-data/testTypesSequence1.py similarity index 68% rename from python/test-data/testTypesSequence.py rename to python/test-data/testTypesSequence1.py index eaee72a4..8a34e873 100644 --- a/python/test-data/testTypesSequence.py +++ b/python/test-data/testTypesSequence1.py @@ -1,6 +1,7 @@ from wypp import * -def foo(seq: Sequence): +def foo(seq: Sequence) -> None: + print(seq) pass foo([1,2,3]) diff --git a/python/test-data/testTypesSequence2.err b/python/test-data/testTypesSequence2.err new file mode 100644 index 00000000..abccc73a --- /dev/null +++ b/python/test-data/testTypesSequence2.err @@ -0,0 +1,13 @@ +untypy.error.UntypyTypeError +given: 'Hello!' +expected: value of type Sequence[int] + +context: foo(seq: Sequence[int]) -> None + ^^^^^^^^^^^^^ +declared at: test-data/testTypesSequence2.py:3 + 3 | def foo(seq: Sequence[int]) -> None: + 4 | print(seq) + 5 | pass + +caused by: test-data/testTypesSequence2.py:9 + 9 | foo("Hello!") # should fail diff --git a/python/test-data/testTypesSequence2.out b/python/test-data/testTypesSequence2.out new file mode 100644 index 00000000..ec306cac --- /dev/null +++ b/python/test-data/testTypesSequence2.out @@ -0,0 +1,2 @@ +[1, 2, 3] +(4, 5) diff --git a/python/test-data/testTypesSequence2.py b/python/test-data/testTypesSequence2.py index e1a48c82..290dee38 100644 --- a/python/test-data/testTypesSequence2.py +++ b/python/test-data/testTypesSequence2.py @@ -1,6 +1,7 @@ from wypp import * -def foo(seq: Sequence[int]): +def foo(seq: Sequence[int]) -> None: + print(seq) pass foo([1,2,3]) From 4b3963e59f9464a9c44739c69a0b29ecdb605fa0 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 15:40:23 +0200 Subject: [PATCH 37/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 61d5d1b4..60c86213 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 61d5d1b4ff24a63e030ba43f2c65e150d7821860 +Subproject commit 60c86213386b4bd85c28393efbdcc9043a3bfd3d From 5a78f7913a74be6b94f18ad2f5725a0baefb34e6 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 16:10:48 +0200 Subject: [PATCH 38/40] new failing test --- python/runFileTests.sh | 1 + python/test-data/wrong-caused-by.py | 30 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 python/test-data/wrong-caused-by.py diff --git a/python/runFileTests.sh b/python/runFileTests.sh index a1652c03..baf4416e 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -128,5 +128,6 @@ checkWithOutputAux yes 0 test-data/testForwardRef3.py checkWithOutputAux yes 1 test-data/testTypesSequence1.py checkWithOutputAux yes 1 test-data/testTypesSequence2.py checkWithOutputAux yes 1 test-data/testTypesTuple1.py +# checkWithOutputAux yes 1 test-data/wrong-caused-by.py See #17 diff --git a/python/test-data/wrong-caused-by.py b/python/test-data/wrong-caused-by.py new file mode 100644 index 00000000..d7ca90e2 --- /dev/null +++ b/python/test-data/wrong-caused-by.py @@ -0,0 +1,30 @@ +from wypp import * + +@record(mutable=True) +class StreetM: + name: str + cars: list[CarM] + # Einbiegen eines Auto auf eine Straße + # SEITENEFFEKT: Liste der Autos wird geändert. + def turnIntoStreet(self: StreetM, car: Car) -> None: + if car not in self.cars: + self.cars.append(car) + # Verlassen einer Straße + # SEITENEFFEKT: Liste der Autos wird geändert. + def leaveStreet(self: StreetM, car: Car) -> None: + if car in self.cars: + self.cars.remove(car) + +@record(mutable=True) +class CarM: + licensePlate: str + color: str + +@record +class Car: + licensePlate: str + color: str + +redCarM = CarM('OG PY 123', 'rot') +mainStreetM = StreetM('Hauptstraße', [redCarM]) +mainStreetM.turnIntoStreet(redCarM) From f488566fc874c57aa178983ca88269f8afdc36bc Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 16:18:11 +0200 Subject: [PATCH 39/40] another failing test --- python/runFileTests.sh | 1 + python/test-data/declared-at-missing.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 python/test-data/declared-at-missing.py diff --git a/python/runFileTests.sh b/python/runFileTests.sh index baf4416e..e0368194 100755 --- a/python/runFileTests.sh +++ b/python/runFileTests.sh @@ -129,5 +129,6 @@ checkWithOutputAux yes 1 test-data/testTypesSequence1.py checkWithOutputAux yes 1 test-data/testTypesSequence2.py checkWithOutputAux yes 1 test-data/testTypesTuple1.py # checkWithOutputAux yes 1 test-data/wrong-caused-by.py See #17 +# checkWithOutputAux yes 1 test-data/declared-aty.py See #18 diff --git a/python/test-data/declared-at-missing.py b/python/test-data/declared-at-missing.py new file mode 100644 index 00000000..979ff573 --- /dev/null +++ b/python/test-data/declared-at-missing.py @@ -0,0 +1,22 @@ +from wypp import * + +@record +class Course: + name: str + teacher: str + students: tuple[str, ...] + +@record(mutable=True) +class CourseM: + name: str + teacher: str + students: tuple[str, ...] + +@record +class Semester: + degreeProgram: str + semester: str + courses: tuple[CourseM, ...] + +prog1 = Course('Programmierung 1', 'Wehr', ()) +semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, )) From 939f11e77f56087434a3b75fe0a70380974d8090 Mon Sep 17 00:00:00 2001 From: Stefan Wehr Date: Thu, 23 Sep 2021 16:18:28 +0200 Subject: [PATCH 40/40] update untypy --- python/deps/untypy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/deps/untypy b/python/deps/untypy index 60c86213..16ce8157 160000 --- a/python/deps/untypy +++ b/python/deps/untypy @@ -1 +1 @@ -Subproject commit 60c86213386b4bd85c28393efbdcc9043a3bfd3d +Subproject commit 16ce81576c36bcd15b0db3d3147416921823f450