diff options
Diffstat (limited to 'tests')
84 files changed, 2594 insertions, 252 deletions
diff --git a/tests/basics/array_add.py b/tests/basics/array_add.py index 76ce59f761..e78615541c 100644 --- a/tests/basics/array_add.py +++ b/tests/basics/array_add.py @@ -14,3 +14,9 @@ print(a1) a1.extend(array.array('I', [5])) print(a1) + +a1.extend([6, 7]) +print(a1) + +a1.extend(i for i in (8, 9)) +print(a1) diff --git a/tests/basics/io_buffered_writer.py b/tests/basics/io_buffered_writer.py index 5c065f158a..60cf2c837d 100644 --- a/tests/basics/io_buffered_writer.py +++ b/tests/basics/io_buffered_writer.py @@ -28,3 +28,27 @@ print(bts.getvalue()) # hashing a BufferedWriter print(type(hash(buf))) + +# Test failing flush() +class MyIO(io.IOBase): + def __init__(self): + self.count = 0 + + def write(self, buf): + self.count += 1 + if self.count < 3: + return None + print("writing", buf) + return len(buf) + + +buf = io.BufferedWriter(MyIO(), 8) + +buf.write(b"foobar") + +for _ in range(4): + try: + buf.flush() + print("flushed") + except OSError: + print("OSError") diff --git a/tests/basics/io_buffered_writer.py.exp b/tests/basics/io_buffered_writer.py.exp index 2209348f5a..d61eb148b4 100644 --- a/tests/basics/io_buffered_writer.py.exp +++ b/tests/basics/io_buffered_writer.py.exp @@ -4,3 +4,8 @@ b'foobarfoobar' b'foobarfoobar' b'foo' <class 'int'> +OSError +OSError +writing bytearray(b'foobar') +flushed +flushed diff --git a/tests/basics/string_format.py b/tests/basics/string_format.py index e8600f5836..11e7836a73 100644 --- a/tests/basics/string_format.py +++ b/tests/basics/string_format.py @@ -22,7 +22,17 @@ test("{:4o}", 123) test("{:4x}", 123) test("{:4X}", 123) +test("{:4,d}", 1) +test("{:4_d}", 1) +test("{:4_o}", 1) +test("{:4_b}", 1) +test("{:4_x}", 1) + test("{:4,d}", 12345678) +test("{:4_d}", 12345678) +test("{:4_o}", 12345678) +test("{:4_b}", 12345678) +test("{:4_x}", 12345678) test("{:#4b}", 10) test("{:#4o}", 123) diff --git a/tests/cmdline/repl_autocomplete_underscore.py b/tests/cmdline/repl_autocomplete_underscore.py new file mode 100644 index 0000000000..98bbb69920 --- /dev/null +++ b/tests/cmdline/repl_autocomplete_underscore.py @@ -0,0 +1,33 @@ +# Test REPL autocompletion filtering of underscore attributes + +# Start paste mode +{\x05} +class TestClass: + def __init__(self): + self.public_attr = 1 + self._private_attr = 2 + self.__very_private = 3 + + def public_method(self): + pass + + def _private_method(self): + pass + + @property + def public_property(self): + return 42 + + @property + def _private_property(self): + return 99 + +{\x04} +# Paste executed + +# Create an instance +obj = TestClass() + +# Test tab completion on the instance +# The tab character after `obj.` and 'a' below triggers the completions +obj.{\x09}{\x09}a{\x09} diff --git a/tests/cmdline/repl_autocomplete_underscore.py.exp b/tests/cmdline/repl_autocomplete_underscore.py.exp new file mode 100644 index 0000000000..35617554f5 --- /dev/null +++ b/tests/cmdline/repl_autocomplete_underscore.py.exp @@ -0,0 +1,41 @@ +MicroPython \.\+ version +Use Ctrl-D to exit, Ctrl-E for paste mode +>>> # Test REPL autocompletion filtering of underscore attributes +>>> +>>> # Start paste mode +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== class TestClass: +=== def __init__(self): +=== self.public_attr = 1 +=== self._private_attr = 2 +=== self.__very_private = 3 +=== +=== def public_method(self): +=== pass +=== +=== def _private_method(self): +=== pass +=== +=== @property +=== def public_property(self): +=== return 42 +=== +=== @property +=== def _private_property(self): +=== return 99 +=== +=== +>>> # Paste executed +>>> +>>> # Create an instance +>>> obj = TestClass() +>>> +>>> # Test tab completion on the instance +>>> # The tab character after `obj.` and 'a' below triggers the completions +>>> obj.public_ +public_attr public_method public_property +>>> obj.public_attr +1 +>>> diff --git a/tests/cmdline/repl_paste.py b/tests/cmdline/repl_paste.py new file mode 100644 index 0000000000..7cec450fce --- /dev/null +++ b/tests/cmdline/repl_paste.py @@ -0,0 +1,90 @@ +# Test REPL paste mode functionality + +# Basic paste mode with a simple function +{\x05} +def hello(): + print('Hello from paste mode!') +hello() +{\x04} + +# Paste mode with multiple indentation levels +{\x05} +def calculate(n): + if n > 0: + for i in range(n): + if i % 2 == 0: + print(f'Even: {i}') + else: + print(f'Odd: {i}') + else: + print('n must be positive') + +calculate(5) +{\x04} + +# Paste mode with blank lines +{\x05} +def function_with_blanks(): + print('First line') + + print('After blank line') + + + print('After two blank lines') + +function_with_blanks() +{\x04} + +# Paste mode with class definition and multiple methods +{\x05} +class TestClass: + def __init__(self, value): + self.value = value + + def display(self): + print(f'Value is: {self.value}') + + def double(self): + self.value *= 2 + return self.value + +obj = TestClass(21) +obj.display() +print(f'Doubled: {obj.double()}') +obj.display() +{\x04} + +# Paste mode with exception handling +{\x05} +try: + x = 1 / 0 +except ZeroDivisionError: + print('Caught division by zero') +finally: + print('Finally block executed') +{\x04} + +# Cancel paste mode with Ctrl-C +{\x05} +print('This should not execute') +{\x03} + +# Normal REPL still works after cancelled paste +print('Back to normal REPL') + +# Paste mode with syntax error +{\x05} +def bad_syntax(: + print('Missing parameter') +{\x04} + +# Paste mode with runtime error +{\x05} +def will_error(): + undefined_variable + +will_error() +{\x04} + +# Final test to show REPL is still functioning +1 + 2 + 3 diff --git a/tests/cmdline/repl_paste.py.exp b/tests/cmdline/repl_paste.py.exp new file mode 100644 index 0000000000..22d9bd5740 --- /dev/null +++ b/tests/cmdline/repl_paste.py.exp @@ -0,0 +1,133 @@ +MicroPython \.\+ version +Use Ctrl-D to exit, Ctrl-E for paste mode +>>> # Test REPL paste mode functionality +>>> +>>> # Basic paste mode with a simple function +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def hello(): +=== print('Hello from paste mode!') +=== hello() +=== +Hello from paste mode! +>>> +>>> # Paste mode with multiple indentation levels +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def calculate(n): +=== if n > 0: +=== for i in range(n): +=== if i % 2 == 0: +=== print(f'Even: {i}') +=== else: +=== print(f'Odd: {i}') +=== else: +=== print('n must be positive') +=== +=== calculate(5) +=== +Even: 0 +Odd: 1 +Even: 2 +Odd: 3 +Even: 4 +>>> +>>> # Paste mode with blank lines +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def function_with_blanks(): +=== print('First line') +=== +=== print('After blank line') +=== +=== +=== print('After two blank lines') +=== +=== function_with_blanks() +=== +First line +After blank line +After two blank lines +>>> +>>> # Paste mode with class definition and multiple methods +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== class TestClass: +=== def __init__(self, value): +=== self.value = value +=== +=== def display(self): +=== print(f'Value is: {self.value}') +=== +=== def double(self): +=== self.value *= 2 +=== return self.value +=== +=== obj = TestClass(21) +=== obj.display() +=== print(f'Doubled: {obj.double()}') +=== obj.display() +=== +Value is: 21 +Doubled: 42 +Value is: 42 +>>> +>>> # Paste mode with exception handling +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== try: +=== x = 1 / 0 +=== except ZeroDivisionError: +=== print('Caught division by zero') +=== finally: +=== print('Finally block executed') +=== +Caught division by zero +Finally block executed +>>> +>>> # Cancel paste mode with Ctrl-C +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== print('This should not execute') +=== +>>> +>>> +>>> # Normal REPL still works after cancelled paste +>>> print('Back to normal REPL') +Back to normal REPL +>>> +>>> # Paste mode with syntax error +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def bad_syntax(: +=== print('Missing parameter') +=== +Traceback (most recent call last): + File "<stdin>", line 2 +SyntaxError: invalid syntax +>>> +>>> # Paste mode with runtime error +>>> +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== +=== def will_error(): +=== undefined_variable +=== +=== will_error() +=== +Traceback (most recent call last): + File "<stdin>", line 5, in <module> + File "<stdin>", line 3, in will_error +NameError: name 'undefined_variable' isn't defined +>>> +>>> # Final test to show REPL is still functioning +>>> 1 + 2 + 3 +6 +>>> diff --git a/tests/cpydiff/core_fstring_concat.py b/tests/cpydiff/core_fstring_concat.py index 3daa13d753..2fbe1b961a 100644 --- a/tests/cpydiff/core_fstring_concat.py +++ b/tests/cpydiff/core_fstring_concat.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings don't support concatenation with adjacent literals if the adjacent literals contain braces cause: MicroPython is optimised for code space. workaround: Use the + operator between literal strings when they are not both f-strings diff --git a/tests/cpydiff/core_fstring_parser.py b/tests/cpydiff/core_fstring_parser.py index 22bbc5866e..570b92434a 100644 --- a/tests/cpydiff/core_fstring_parser.py +++ b/tests/cpydiff/core_fstring_parser.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings cannot support expressions that require parsing to resolve unbalanced nested braces and brackets cause: MicroPython is optimised for code space. workaround: Always use balanced braces and brackets in expressions inside f-strings diff --git a/tests/cpydiff/core_fstring_repr.py b/tests/cpydiff/core_fstring_repr.py index d37fb48db7..2589a34b7e 100644 --- a/tests/cpydiff/core_fstring_repr.py +++ b/tests/cpydiff/core_fstring_repr.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings don't support !a conversions cause: MicropPython does not implement ascii() workaround: None diff --git a/tests/cpydiff/core_import_all.py b/tests/cpydiff/core_import_all.py deleted file mode 100644 index 0fbe9d4d4e..0000000000 --- a/tests/cpydiff/core_import_all.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -categories: Core,import -description: __all__ is unsupported in __init__.py in MicroPython. -cause: Not implemented. -workaround: Manually import the sub-modules directly in __init__.py using ``from . import foo, bar``. -""" - -from modules3 import * - -foo.hello() diff --git a/tests/cpydiff/modules3/__init__.py b/tests/cpydiff/modules3/__init__.py deleted file mode 100644 index 27a2bf2ad9..0000000000 --- a/tests/cpydiff/modules3/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__all__ = ["foo"] diff --git a/tests/cpydiff/modules3/foo.py b/tests/cpydiff/modules3/foo.py deleted file mode 100644 index dd9b9d4ddd..0000000000 --- a/tests/cpydiff/modules3/foo.py +++ /dev/null @@ -1,2 +0,0 @@ -def hello(): - print("hello") diff --git a/tests/cpydiff/syntax_arg_unpacking.py b/tests/cpydiff/syntax_arg_unpacking.py index e54832ddb9..7133a8a282 100644 --- a/tests/cpydiff/syntax_arg_unpacking.py +++ b/tests/cpydiff/syntax_arg_unpacking.py @@ -1,5 +1,5 @@ """ -categories: Syntax +categories: Syntax,Unpacking description: Argument unpacking does not work if the argument being unpacked is the nth or greater argument where n is the number of bits in an MP_SMALL_INT. cause: The implementation uses an MP_SMALL_INT to flag args that need to be unpacked. workaround: Use fewer arguments. diff --git a/tests/cpydiff/syntax_literal_underscore.py b/tests/cpydiff/syntax_literal_underscore.py new file mode 100644 index 0000000000..4b1406e9f3 --- /dev/null +++ b/tests/cpydiff/syntax_literal_underscore.py @@ -0,0 +1,19 @@ +""" +categories: Syntax,Literals +description: MicroPython accepts underscores in numeric literals where CPython doesn't +cause: Different parser implementation + +MicroPython's tokenizer ignores underscores in numeric literals, while CPython +rejects multiple consecutive underscores and underscores after the last digit. + +workaround: Remove the underscores not accepted by CPython. +""" + +try: + print(eval("1__1")) +except SyntaxError: + print("Should not work") +try: + print(eval("1_")) +except SyntaxError: + print("Should not work") diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index 03d25d5619..670cefdeac 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -1,8 +1,15 @@ """ -categories: Syntax,Spaces -description: uPy requires spaces between literal numbers and keywords, CPy doesn't -cause: Unknown -workaround: Unknown +categories: Syntax,Literals +description: MicroPython requires spaces between literal numbers and keywords or ".", CPython doesn't +cause: Different parser implementation + +MicroPython's tokenizer treats a sequence like ``1and`` as a single token, while CPython treats it as two tokens. + +Since CPython 3.11, when the literal number is followed by a token, this syntax causes a ``SyntaxWarning`` for an "invalid literal". When a literal number is followed by a "." denoting attribute access, CPython does not warn. + +workaround: Add a space between the integer literal and the intended next token. + +This also fixes the ``SyntaxWarning`` in CPython. """ try: @@ -17,3 +24,7 @@ try: print(eval("1if 1else 0")) except SyntaxError: print("Should have worked") +try: + print(eval("0x1.to_bytes(1)")) +except SyntaxError: + print("Should have worked") diff --git a/tests/cpydiff/types_complex_parser.py b/tests/cpydiff/types_complex_parser.py new file mode 100644 index 0000000000..4a012987d9 --- /dev/null +++ b/tests/cpydiff/types_complex_parser.py @@ -0,0 +1,14 @@ +""" +categories: Types,complex +description: MicroPython's complex() accepts certain incorrect values that CPython rejects +cause: MicroPython is highly optimized for memory usage. +workaround: Do not use non-standard complex literals as argument to complex() + +MicroPython's ``complex()`` function accepts literals that contain a space and +no sign between the real and imaginary parts, and interprets it as a plus. +""" + +try: + print(complex("1 1j")) +except ValueError: + print("ValueError") diff --git a/tests/cpydiff/types_str_formatsep.py b/tests/cpydiff/types_str_formatsep.py new file mode 100644 index 0000000000..05d0b8d3d2 --- /dev/null +++ b/tests/cpydiff/types_str_formatsep.py @@ -0,0 +1,19 @@ +""" +categories: Types,str +description: MicroPython accepts the "," grouping option with any radix, unlike CPython +cause: To reduce code size, MicroPython does not issue an error for this combination +workaround: Do not use a format string like ``{:,b}`` if CPython compatibility is required. +""" + +try: + print("{:,b}".format(99)) +except ValueError: + print("ValueError") +try: + print("{:,x}".format(99)) +except ValueError: + print("ValueError") +try: + print("{:,o}".format(99)) +except ValueError: + print("ValueError") diff --git a/tests/extmod/framebuf_blit.py b/tests/extmod/framebuf_blit.py new file mode 100644 index 0000000000..b1d98b330a --- /dev/null +++ b/tests/extmod/framebuf_blit.py @@ -0,0 +1,68 @@ +# Test FrameBuffer.blit method. + +try: + import framebuf +except ImportError: + print("SKIP") + raise SystemExit + + +def printbuf(): + print("--8<--") + for y in range(h): + for x in range(w): + print("%02x" % buf[(x + y * w)], end="") + print() + print("-->8--") + + +w = 5 +h = 4 +buf = bytearray(w * h) +fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.GS8) + +fbuf2 = framebuf.FrameBuffer(bytearray(4), 2, 2, framebuf.GS8) +fbuf2.fill(0xFF) + +# Blit another FrameBuffer, at various locations. +for x, y in ((-1, -1), (0, 0), (1, 1), (4, 3)): + fbuf.fill(0) + fbuf.blit(fbuf2, x, y) + printbuf() + +# Blit a bytes object. +fbuf.fill(0) +image = (b"\x10\x11\x12\x13", 2, 2, framebuf.GS8) +fbuf.blit(image, 1, 1) +printbuf() + +# Blit a bytes object that has a stride. +fbuf.fill(0) +image = (b"\x20\x21\xff\x22\x23\xff", 2, 2, framebuf.GS8, 3) +fbuf.blit(image, 1, 1) +printbuf() + +# Blit a bytes object with a bytes palette. +fbuf.fill(0) +image = (b"\x00\x01\x01\x00", 2, 2, framebuf.GS8) +palette = (b"\xa1\xa2", 2, 1, framebuf.GS8) +fbuf.blit(image, 1, 1, -1, palette) +printbuf() + +# Not enough elements in the tuple. +try: + fbuf.blit((0, 0, 0), 0, 0) +except ValueError: + print("ValueError") + +# Too many elements in the tuple. +try: + fbuf.blit((0, 0, 0, 0, 0, 0), 0, 0) +except ValueError: + print("ValueError") + +# Bytes too small. +try: + fbuf.blit((b"", 1, 1, framebuf.GS8), 0, 0) +except ValueError: + print("ValueError") diff --git a/tests/extmod/framebuf_blit.py.exp b/tests/extmod/framebuf_blit.py.exp new file mode 100644 index 0000000000..e340f1990c --- /dev/null +++ b/tests/extmod/framebuf_blit.py.exp @@ -0,0 +1,45 @@ +--8<-- +ff00000000 +0000000000 +0000000000 +0000000000 +-->8-- +--8<-- +ffff000000 +ffff000000 +0000000000 +0000000000 +-->8-- +--8<-- +0000000000 +00ffff0000 +00ffff0000 +0000000000 +-->8-- +--8<-- +0000000000 +0000000000 +0000000000 +00000000ff +-->8-- +--8<-- +0000000000 +0010110000 +0012130000 +0000000000 +-->8-- +--8<-- +0000000000 +0020210000 +0022230000 +0000000000 +-->8-- +--8<-- +0000000000 +00a1a20000 +00a2a10000 +0000000000 +-->8-- +ValueError +ValueError +ValueError diff --git a/tests/extmod/json_loads.py b/tests/extmod/json_loads.py index f9073c121e..095e67d740 100644 --- a/tests/extmod/json_loads.py +++ b/tests/extmod/json_loads.py @@ -71,3 +71,27 @@ try: my_print(json.loads("[null] a")) except ValueError: print("ValueError") + +# incomplete object declaration +try: + my_print(json.loads('{"a":0,')) +except ValueError: + print("ValueError") + +# incomplete nested array declaration +try: + my_print(json.loads('{"a":0, [')) +except ValueError: + print("ValueError") + +# incomplete array declaration +try: + my_print(json.loads('[0,')) +except ValueError: + print("ValueError") + +# incomplete nested object declaration +try: + my_print(json.loads('[0, {"a":0, ')) +except ValueError: + print("ValueError") diff --git a/tests/extmod/platform_basic.py b/tests/extmod/platform_basic.py new file mode 100644 index 0000000000..eb6f2be13c --- /dev/null +++ b/tests/extmod/platform_basic.py @@ -0,0 +1,8 @@ +try: + import platform +except ImportError: + print("SKIP") + raise SystemExit + +print(type(platform.python_compiler())) +print(type(platform.libc_ver())) diff --git a/tests/extmod/random_extra_float.py b/tests/extmod/random_extra_float.py index 3b37ed8dce..03973c5834 100644 --- a/tests/extmod/random_extra_float.py +++ b/tests/extmod/random_extra_float.py @@ -1,12 +1,8 @@ try: import random -except ImportError: - print("SKIP") - raise SystemExit -try: - random.randint -except AttributeError: + random.random +except (ImportError, AttributeError): print("SKIP") raise SystemExit diff --git a/tests/extmod/vfs_lfs_error.py b/tests/extmod/vfs_lfs_error.py index 2ac7629bfa..73cdf34373 100644 --- a/tests/extmod/vfs_lfs_error.py +++ b/tests/extmod/vfs_lfs_error.py @@ -1,7 +1,7 @@ # Test for VfsLittle using a RAM device, testing error handling try: - import vfs + import errno, vfs vfs.VfsLfs1 vfs.VfsLfs2 @@ -41,14 +41,14 @@ def test(bdev, vfs_class): # mkfs with too-small block device try: vfs_class.mkfs(RAMBlockDevice(1)) - except OSError: - print("mkfs OSError") + except OSError as er: + print("mkfs OSError", er.errno > 0) # mount with invalid filesystem try: vfs_class(bdev) - except OSError: - print("mount OSError") + except OSError as er: + print("mount OSError", er.errno > 0) # set up for following tests vfs_class.mkfs(bdev) @@ -60,60 +60,60 @@ def test(bdev, vfs_class): # ilistdir try: fs.ilistdir("noexist") - except OSError: - print("ilistdir OSError") + except OSError as er: + print("ilistdir OSError", er) # remove try: fs.remove("noexist") - except OSError: - print("remove OSError") + except OSError as er: + print("remove OSError", er) # rmdir try: fs.rmdir("noexist") - except OSError: - print("rmdir OSError") + except OSError as er: + print("rmdir OSError", er) # rename try: fs.rename("noexist", "somethingelse") - except OSError: - print("rename OSError") + except OSError as er: + print("rename OSError", er) # mkdir try: fs.mkdir("testdir") - except OSError: - print("mkdir OSError") + except OSError as er: + print("mkdir OSError", er) # chdir to nonexistent try: fs.chdir("noexist") - except OSError: - print("chdir OSError") + except OSError as er: + print("chdir OSError", er) print(fs.getcwd()) # check still at root # chdir to file try: fs.chdir("testfile") - except OSError: - print("chdir OSError") + except OSError as er: + print("chdir OSError", er) print(fs.getcwd()) # check still at root # stat try: fs.stat("noexist") - except OSError: - print("stat OSError") + except OSError as er: + print("stat OSError", er) # error during seek with fs.open("testfile", "r") as f: f.seek(1 << 30) # SEEK_SET try: f.seek(1 << 30, 1) # SEEK_CUR - except OSError: - print("seek OSError") + except OSError as er: + print("seek OSError", er) bdev = RAMBlockDevice(30) diff --git a/tests/extmod/vfs_lfs_error.py.exp b/tests/extmod/vfs_lfs_error.py.exp index f4327f6962..440607ed84 100644 --- a/tests/extmod/vfs_lfs_error.py.exp +++ b/tests/extmod/vfs_lfs_error.py.exp @@ -1,28 +1,28 @@ test <class 'VfsLfs1'> -mkfs OSError -mount OSError -ilistdir OSError -remove OSError -rmdir OSError -rename OSError -mkdir OSError -chdir OSError +mkfs OSError True +mount OSError True +ilistdir OSError [Errno 2] ENOENT +remove OSError [Errno 2] ENOENT +rmdir OSError [Errno 2] ENOENT +rename OSError [Errno 2] ENOENT +mkdir OSError [Errno 17] EEXIST +chdir OSError [Errno 2] ENOENT / -chdir OSError +chdir OSError [Errno 2] ENOENT / -stat OSError -seek OSError +stat OSError [Errno 2] ENOENT +seek OSError [Errno 22] EINVAL test <class 'VfsLfs2'> -mkfs OSError -mount OSError -ilistdir OSError -remove OSError -rmdir OSError -rename OSError -mkdir OSError -chdir OSError +mkfs OSError True +mount OSError True +ilistdir OSError [Errno 2] ENOENT +remove OSError [Errno 2] ENOENT +rmdir OSError [Errno 2] ENOENT +rename OSError [Errno 2] ENOENT +mkdir OSError [Errno 17] EEXIST +chdir OSError [Errno 2] ENOENT / -chdir OSError +chdir OSError [Errno 2] ENOENT / -stat OSError -seek OSError +stat OSError [Errno 2] ENOENT +seek OSError [Errno 22] EINVAL diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index 770b6863b9..cd14542ea6 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -394,6 +394,7 @@ class TestMounted(TestBase): def setUp(self): self.orig_sys_path = list(sys.path) self.orig_cwd = os.getcwd() + sys.path = [] vfs.mount(vfs.VfsRom(self.romfs), "/test_rom") def tearDown(self): diff --git a/tests/float/complex1.py b/tests/float/complex1.py index f4107a1390..0a1d98b9af 100644 --- a/tests/float/complex1.py +++ b/tests/float/complex1.py @@ -12,9 +12,11 @@ print(complex("1.2j")) print(complex("1+j")) print(complex("1+2j")) print(complex("-1-2j")) +print(complex("-1+2j")) print(complex("+1-2j")) print(complex(" -1-2j ")) print(complex(" +1-2j ")) +print(complex(" -1+2j ")) print(complex("nanj")) print(complex("nan-infj")) print(complex(1, 2)) diff --git a/tests/float/float_array.py b/tests/float/float_array.py index 3d128da838..cfff3b220c 100644 --- a/tests/float/float_array.py +++ b/tests/float/float_array.py @@ -19,4 +19,10 @@ def test(a): test(array("f")) test(array("d")) -print("{:.4f}".format(array("f", bytes(array("I", [0x3DCCCCCC])))[0])) +# hand-crafted floats, including non-standard nan +for float_hex in (0x3DCCCCCC, 0x7F800024, 0x7FC00004): + f = array("f", bytes(array("I", [float_hex])))[0] + if type(f) is float: + print("{:.4e}".format(f)) + else: + print(f) diff --git a/tests/float/math_constants.py b/tests/float/math_constants.py index 2e4c321052..21d822a01e 100644 --- a/tests/float/math_constants.py +++ b/tests/float/math_constants.py @@ -1,11 +1,30 @@ # Tests various constants of the math module. + +import sys + try: - import math - from math import exp, cos + from array import array + from math import e, pi except ImportError: print("SKIP") raise SystemExit -print(math.e == exp(1.0)) +# Hexadecimal representations of e and pi constants. +e_truth_single = 0x402DF854 +pi_truth_single = 0x40490FDB +e_truth_double = 0x4005BF0A8B145769 +pi_truth_double = 0x400921FB54442D18 + +# Detect the floating-point precision of the system, to determine the exact values of +# the constants (parsing the float from a decimal string can lead to inaccuracies). +if float("1e300") == float("inf"): + # Single precision floats. + e_truth = array("f", e_truth_single.to_bytes(4, sys.byteorder))[0] + pi_truth = array("f", pi_truth_single.to_bytes(4, sys.byteorder))[0] +else: + # Double precision floats. + e_truth = array("d", e_truth_double.to_bytes(8, sys.byteorder))[0] + pi_truth = array("d", pi_truth_double.to_bytes(8, sys.byteorder))[0] -print(cos(math.pi)) +print("e:", e == e_truth or (e, e_truth, e - e_truth)) +print("pi:", pi == pi_truth or (pi, pi_truth, pi - pi_truth)) diff --git a/tests/float/math_constants_extra.py b/tests/float/math_constants_extra.py index dea49aef5a..756cb45803 100644 --- a/tests/float/math_constants_extra.py +++ b/tests/float/math_constants_extra.py @@ -9,9 +9,12 @@ except (ImportError, AttributeError): raise SystemExit print(math.tau == 2.0 * math.pi) +print(math.copysign(1.0, math.tau)) print(math.inf == float("inf")) print(-math.inf == -float("inf")) +print(math.copysign(1.0, math.inf)) print(isnan(math.nan)) print(isnan(-math.nan)) +print(math.copysign(1.0, math.nan)) diff --git a/tests/import/import_star.py b/tests/import/import_star.py new file mode 100644 index 0000000000..0947f6a835 --- /dev/null +++ b/tests/import/import_star.py @@ -0,0 +1,59 @@ +# test `from package import *` conventions, including __all__ support +# +# This test requires MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES + +try: + next(iter([]), 42) +except TypeError: + # 2-argument version of next() not supported + # we are probably not at MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES + print('SKIP') + raise SystemExit + +# 1. test default visibility +from pkgstar_default import * + +print('visibleFun' in globals()) +print('VisibleClass' in globals()) +print('_hiddenFun' in globals()) +print('_HiddenClass' in globals()) +print(visibleFun()) + +# 2. test explicit visibility as defined by __all__ (as an array) +from pkgstar_all_array import * + +print('publicFun' in globals()) +print('PublicClass' in globals()) +print('unlistedFun' in globals()) +print('UnlistedClass' in globals()) +print('_privateFun' in globals()) +print('_PrivateClass' in globals()) +print(publicFun()) +# test dynamic import as used in asyncio +print('dynamicFun' in globals()) +print(dynamicFun()) + +# 3. test explicit visibility as defined by __all__ (as an tuple) +from pkgstar_all_tuple import * + +print('publicFun2' in globals()) +print('PublicClass2' in globals()) +print('unlistedFun2' in globals()) +print('UnlistedClass2' in globals()) +print(publicFun2()) + +# 4. test reporting of missing entries in __all__ +try: + from pkgstar_all_miss import * + + print("missed detection of incorrect __all__ definition") +except AttributeError as er: + print("AttributeError triggered for bad __all__ definition") + +# 5. test reporting of invalid __all__ definition +try: + from pkgstar_all_inval import * + + print("missed detection of incorrect __all__ definition") +except TypeError as er: + print("TypeError triggered for bad __all__ definition") diff --git a/tests/import/pkgstar_all_array/__init__.py b/tests/import/pkgstar_all_array/__init__.py new file mode 100644 index 0000000000..4499a94d59 --- /dev/null +++ b/tests/import/pkgstar_all_array/__init__.py @@ -0,0 +1,49 @@ +__all__ = ['publicFun', 'PublicClass', 'dynamicFun'] + + +# Definitions below should always be imported by a star import +def publicFun(): + return 1 + + +class PublicClass: + def __init__(self): + self._val = 1 + + +# If __all__ support is enabled, definitions below +# should not be imported by a star import +def unlistedFun(): + return 0 + + +class UnlistedClass: + def __init__(self): + self._val = 0 + + +# Definitions below should be not be imported by a star import +# (they start with an underscore, and are not listed in __all__) +def _privateFun(): + return -1 + + +class _PrivateClass: + def __init__(self): + self._val = -1 + + +# Test lazy loaded function, as used by extmod/asyncio: +# Works with a star import only if __all__ support is enabled +_attrs = { + "dynamicFun": "funcs", +} + + +def __getattr__(attr): + mod = _attrs.get(attr, None) + if mod is None: + raise AttributeError(attr) + value = getattr(__import__(mod, globals(), locals(), True, 1), attr) + globals()[attr] = value + return value diff --git a/tests/import/pkgstar_all_array/funcs.py b/tests/import/pkgstar_all_array/funcs.py new file mode 100644 index 0000000000..7540d70f66 --- /dev/null +++ b/tests/import/pkgstar_all_array/funcs.py @@ -0,0 +1,2 @@ +def dynamicFun(): + return 777 diff --git a/tests/import/pkgstar_all_inval/__init__.py b/tests/import/pkgstar_all_inval/__init__.py new file mode 100644 index 0000000000..7022476c19 --- /dev/null +++ b/tests/import/pkgstar_all_inval/__init__.py @@ -0,0 +1 @@ +__all__ = 42 diff --git a/tests/import/pkgstar_all_miss/__init__.py b/tests/import/pkgstar_all_miss/__init__.py new file mode 100644 index 0000000000..d960c7d0e2 --- /dev/null +++ b/tests/import/pkgstar_all_miss/__init__.py @@ -0,0 +1,8 @@ +__all__ = ('existingFun', 'missingFun') + + +def existingFun(): + return None + + +# missingFun is not defined, should raise an error on import diff --git a/tests/import/pkgstar_all_tuple/__init__.py b/tests/import/pkgstar_all_tuple/__init__.py new file mode 100644 index 0000000000..a97715ed39 --- /dev/null +++ b/tests/import/pkgstar_all_tuple/__init__.py @@ -0,0 +1,22 @@ +__all__ = ('publicFun2', 'PublicClass2') + + +# Definitions below should always be imported by a star import +def publicFun2(): + return 2 + + +class PublicClass2: + def __init__(self): + self._val = 2 + + +# If __all__ support is enabled, definitions below +# should not be imported by a star import +def unlistedFun2(): + return 0 + + +class UnlistedClass2: + def __init__(self): + self._val = 0 diff --git a/tests/import/pkgstar_default/__init__.py b/tests/import/pkgstar_default/__init__.py new file mode 100644 index 0000000000..4947e4ce7f --- /dev/null +++ b/tests/import/pkgstar_default/__init__.py @@ -0,0 +1,20 @@ +# When __all__ is undefined, star import should only +# show objects that do not start with an underscore + + +def visibleFun(): + return 42 + + +class VisibleClass: + def __init__(self): + self._val = 42 + + +def _hiddenFun(): + return -1 + + +class _HiddenClass: + def __init__(self): + self._val = -1 diff --git a/tests/inlineasm/xtensa/asmargs.py b/tests/inlineasm/xtensa/asmargs.py new file mode 100644 index 0000000000..2bfccfcc69 --- /dev/null +++ b/tests/inlineasm/xtensa/asmargs.py @@ -0,0 +1,44 @@ +# test passing arguments + + +@micropython.asm_xtensa +def arg0(): + movi(a2, 1) + + +print(arg0()) + + +@micropython.asm_xtensa +def arg1(a2): + addi(a2, a2, 1) + + +print(arg1(1)) + + +@micropython.asm_xtensa +def arg2(a2, a3): + add(a2, a2, a3) + + +print(arg2(1, 2)) + + +@micropython.asm_xtensa +def arg3(a2, a3, a4): + add(a2, a2, a3) + add(a2, a2, a4) + + +print(arg3(1, 2, 3)) + + +@micropython.asm_xtensa +def arg4(a2, a3, a4, a5): + add(a2, a2, a3) + add(a2, a2, a4) + add(a2, a2, a5) + + +print(arg4(1, 2, 3, 4)) diff --git a/tests/inlineasm/xtensa/asmargs.py.exp b/tests/inlineasm/xtensa/asmargs.py.exp new file mode 100644 index 0000000000..e33a6964f4 --- /dev/null +++ b/tests/inlineasm/xtensa/asmargs.py.exp @@ -0,0 +1,5 @@ +1 +2 +3 +6 +10 diff --git a/tests/inlineasm/xtensa/asmarith.py b/tests/inlineasm/xtensa/asmarith.py new file mode 100644 index 0000000000..1c0934eb7a --- /dev/null +++ b/tests/inlineasm/xtensa/asmarith.py @@ -0,0 +1,119 @@ +@micropython.asm_xtensa +def f1(a2): + abs_(a2, a2) + + +for value in (10, -10, 0): + print(f1(value)) + + +ADDMI_TEMPLATE = """ +@micropython.asm_xtensa +def f1(a2) -> int: + addmi(a2, a2, {}) +print(f1(0)) +""" + +for value in (-32768, -32767, 32512, 32513, 0): + try: + exec(ADDMI_TEMPLATE.format(value)) + except SyntaxError as error: + print(error) + + +@micropython.asm_xtensa +def a2(a2, a3) -> int: + addx2(a2, a2, a3) + + +@micropython.asm_xtensa +def a4(a2, a3) -> int: + addx4(a2, a2, a3) + + +@micropython.asm_xtensa +def a8(a2, a3) -> int: + addx8(a2, a2, a3) + + +@micropython.asm_xtensa +def s2(a2, a3) -> int: + subx2(a2, a2, a3) + + +@micropython.asm_xtensa +def s4(a2, a3) -> int: + subx4(a2, a2, a3) + + +@micropython.asm_xtensa +def s8(a2, a3) -> int: + subx8(a2, a2, a3) + + +for first, second in ((100, 100), (-100, 100), (-100, -100), (100, -100)): + print("a2", a2(first, second)) + print("a4", a4(first, second)) + print("a8", a8(first, second)) + print("s2", s2(first, second)) + print("s4", s4(first, second)) + print("s8", s8(first, second)) + + +@micropython.asm_xtensa +def f5(a2) -> int: + neg(a2, a2) + + +for value in (0, -100, 100): + print(f5(value)) + + +@micropython.asm_xtensa +def f6(): + movi(a2, 0x100) + movi(a3, 1) + add(a2, a2, a3) + addi(a2, a2, 1) + addi(a2, a2, -2) + sub(a2, a2, a3) + + +print(hex(f6())) + + +@micropython.asm_xtensa +def f7(): + movi(a2, 0x10FF) + movi(a3, 1) + and_(a4, a2, a3) + or_(a4, a4, a3) + movi(a3, 0x200) + xor(a2, a4, a3) + + +print(hex(f7())) + + +@micropython.asm_xtensa +def f8(a2, a3): + add_n(a2, a2, a3) + + +print(f8(100, 200)) + + +@micropython.asm_xtensa +def f9(a2): + addi_n(a2, a2, 1) + + +print(f9(100)) + + +@micropython.asm_xtensa +def f10(a2, a3) -> uint: + mull(a2, a2, a3) + + +print(hex(f10(0xC0000000, 2))) diff --git a/tests/inlineasm/xtensa/asmarith.py.exp b/tests/inlineasm/xtensa/asmarith.py.exp new file mode 100644 index 0000000000..7aba46a27d --- /dev/null +++ b/tests/inlineasm/xtensa/asmarith.py.exp @@ -0,0 +1,40 @@ +10 +10 +0 +-32768 +-32767 is not a multiple of 256 +32512 +'addmi' integer 32513 isn't within range -32768..32512 +0 +a2 300 +a4 500 +a8 900 +s2 100 +s4 300 +s8 700 +a2 -100 +a4 -300 +a8 -700 +s2 -300 +s4 -500 +s8 -900 +a2 -300 +a4 -500 +a8 -900 +s2 -100 +s4 -300 +s8 -700 +a2 100 +a4 300 +a8 700 +s2 300 +s4 500 +s8 900 +0 +100 +-100 +0xff +0x201 +300 +101 +0x80000000 diff --git a/tests/inlineasm/xtensa/asmbranch.py b/tests/inlineasm/xtensa/asmbranch.py new file mode 100644 index 0000000000..22bcd5a7c7 --- /dev/null +++ b/tests/inlineasm/xtensa/asmbranch.py @@ -0,0 +1,299 @@ +# test branch instructions + + +@micropython.asm_xtensa +def tball(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + ball(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tball(0xFFFFFFFF, 0xFFFFFFFF)) +print(tball(0xFFFEFFFF, 0xFFFFFFFF)) +print(tball(0x00000000, 0xFFFFFFFF)) +print(tball(0xFFFFFFFF, 0x01010101)) + + +@micropython.asm_xtensa +def tbany(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bany(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbany(0xFFFFFFFF, 0xFFFFFFFF)) +print(tbany(0xFFFEFFFF, 0xFFFFFFFF)) +print(tbany(0x00000000, 0xFFFFFFFF)) +print(tbany(0xFFFFFFFF, 0x01010101)) + + +@micropython.asm_xtensa +def tbbc(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bbc(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbbc(0xFFFFFFFF, 4)) +print(tbbc(0xFFFEFFFF, 16)) +print(tbbc(0x00000000, 1)) + + +BBCI_TEMPLATE = """ +@micropython.asm_xtensa +def tbbci(a2) -> int: + mov(a3, a2) + movi(a2, 0) + bbci(a3, {}, end) + movi(a2, -1) + label(end) + +print(tbbci({})) +""" + + +for value, bit in ((0xFFFFFFFF, 4), (0xFFFEFFFF, 16), (0x00000000, 1)): + try: + exec(BBCI_TEMPLATE.format(bit, value)) + except SyntaxError as error: + print(error) + + +@micropython.asm_xtensa +def tbbs(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bbs(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbbs(0x00000000, 4)) +print(tbbs(0x00010000, 16)) +print(tbbs(0xFFFFFFFF, 1)) + + +BBSI_TEMPLATE = """ +@micropython.asm_xtensa +def tbbsi(a2) -> int: + mov(a3, a2) + movi(a2, 0) + bbsi(a3, {}, end) + movi(a2, -1) + label(end) + +print(tbbsi({})) +""" + + +for value, bit in ((0x00000000, 4), (0x00010000, 16), (0xFFFFFFFF, 1)): + try: + exec(BBSI_TEMPLATE.format(bit, value)) + except SyntaxError as error: + print(error) + + +@micropython.asm_xtensa +def tbeq(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + beq(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbeq(0x00000000, 0x00000000)) +print(tbeq(0x00010000, 0x00000000)) +print(tbeq(0xFFFFFFFF, 0xFFFFFFFF)) + + +@micropython.asm_xtensa +def tbeqz(a2) -> int: + mov(a3, a2) + movi(a2, 0) + beqz(a3, end) + movi(a2, -1) + label(end) + + +print(tbeqz(0)) +print(tbeqz(0x12345678)) +print(tbeqz(-1)) + + +@micropython.asm_xtensa +def tbge(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bge(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbge(0x00000000, 0x00000000)) +print(tbge(0x00010000, 0x00000000)) +print(tbge(0xF0000000, 0xFFFFFFFF)) + + +@micropython.asm_xtensa +def tbgeu(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bgeu(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbgeu(0x00000000, 0x00000000)) +print(tbgeu(0x00010000, 0x00000000)) +print(tbgeu(0xF0000000, 0xFFFFFFFF)) +print(tbgeu(0xFFFFFFFF, 0xF0000000)) + + +@micropython.asm_xtensa +def tbgez(a2) -> int: + mov(a3, a2) + movi(a2, 0) + bgez(a3, end) + movi(a2, -1) + label(end) + + +print(tbgez(0)) +print(tbgez(0x12345678)) +print(tbgez(-1)) + + +@micropython.asm_xtensa +def tblt(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + blt(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tblt(0x00000000, 0x00000000)) +print(tblt(0x00010000, 0x00000000)) +print(tblt(0xF0000000, 0xFFFFFFFF)) + + +@micropython.asm_xtensa +def tbltu(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bltu(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbltu(0x00000000, 0x00000000)) +print(tbltu(0x00010000, 0x00000000)) +print(tbltu(0xF0000000, 0xFFFFFFFF)) +print(tbltu(0xFFFFFFFF, 0xF0000000)) + + +@micropython.asm_xtensa +def tbltz(a2) -> int: + mov(a3, a2) + movi(a2, 0) + bltz(a3, end) + movi(a2, -1) + label(end) + + +print(tbltz(0)) +print(tbltz(0x12345678)) +print(tbltz(-1)) + + +@micropython.asm_xtensa +def tbnall(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bnall(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbnall(0xFFFFFFFF, 0xFFFFFFFF)) +print(tbnall(0xFFFEFFFF, 0xFFFFFFFF)) +print(tbnall(0x00000000, 0xFFFFFFFF)) +print(tbnall(0xFFFFFFFF, 0x01010101)) + + +@micropython.asm_xtensa +def tbne(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bne(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbne(0x00000000, 0x00000000)) +print(tbne(0x00010000, 0x00000000)) +print(tbne(0xFFFFFFFF, 0xFFFFFFFF)) + + +@micropython.asm_xtensa +def tbnez(a2) -> int: + mov(a3, a2) + movi(a2, 0) + bnez(a3, end) + movi(a2, -1) + label(end) + + +print(tbnez(0)) +print(tbnez(0x12345678)) +print(tbnez(-1)) + + +@micropython.asm_xtensa +def tbnone(a2, a3) -> int: + mov(a4, a2) + movi(a2, 0) + bnone(a4, a3, end) + movi(a2, -1) + label(end) + + +print(tbnone(0xFFFFFFFF, 0xFFFFFFFF)) +print(tbnone(0xFFFEFFFF, 0xFFFFFFFF)) +print(tbnone(0x00000000, 0xFFFFFFFF)) +print(tbnone(0x10101010, 0x01010101)) + + +@micropython.asm_xtensa +def tbeqz_n(a2) -> int: + mov(a3, a2) + movi(a2, 0) + beqz_n(a3, end) + movi(a2, -1) + label(end) + + +print(tbeqz_n(0)) +print(tbeqz_n(0x12345678)) +print(tbeqz_n(-1)) + + +@micropython.asm_xtensa +def tbnez_n(a2) -> int: + mov(a3, a2) + movi(a2, 0) + bnez(a3, end) + movi(a2, -1) + label(end) + + +print(tbnez_n(0)) +print(tbnez_n(0x12345678)) +print(tbnez_n(-1)) diff --git a/tests/inlineasm/xtensa/asmbranch.py.exp b/tests/inlineasm/xtensa/asmbranch.py.exp new file mode 100644 index 0000000000..319a4b435e --- /dev/null +++ b/tests/inlineasm/xtensa/asmbranch.py.exp @@ -0,0 +1,66 @@ +0 +-1 +-1 +0 +0 +0 +-1 +0 +-1 +0 +0 +-1 +0 +0 +-1 +0 +0 +-1 +0 +0 +0 +-1 +0 +0 +-1 +-1 +0 +0 +-1 +0 +0 +-1 +0 +0 +0 +-1 +-1 +-1 +0 +-1 +-1 +0 +-1 +-1 +-1 +0 +-1 +0 +0 +-1 +-1 +0 +-1 +-1 +0 +0 +-1 +-1 +0 +0 +0 +-1 +-1 +-1 +0 +0 diff --git a/tests/inlineasm/xtensa/asmjump.py b/tests/inlineasm/xtensa/asmjump.py new file mode 100644 index 0000000000..f41c948231 --- /dev/null +++ b/tests/inlineasm/xtensa/asmjump.py @@ -0,0 +1,26 @@ +@micropython.asm_xtensa +def jump() -> int: + movi(a2, 0) + j(NEXT) + addi(a2, a2, 1) + j(DONE) + label(NEXT) + addi(a2, a2, 2) + label(DONE) + + +print(jump()) + + +@micropython.asm_xtensa +def jumpx() -> int: + call0(ENTRY) + label(ENTRY) + movi(a2, 0) + addi(a3, a0, 12) + jx(a3) + movi(a2, 1) + movi(a2, 2) + + +print(jumpx()) diff --git a/tests/inlineasm/xtensa/asmjump.py.exp b/tests/inlineasm/xtensa/asmjump.py.exp new file mode 100644 index 0000000000..51993f072d --- /dev/null +++ b/tests/inlineasm/xtensa/asmjump.py.exp @@ -0,0 +1,2 @@ +2 +2 diff --git a/tests/inlineasm/xtensa/asmloadstore.py b/tests/inlineasm/xtensa/asmloadstore.py new file mode 100644 index 0000000000..b185e30520 --- /dev/null +++ b/tests/inlineasm/xtensa/asmloadstore.py @@ -0,0 +1,98 @@ +import array + +# On the 8266 the generated code gets put into the IRAM segment, which is only +# word-addressable. Therefore, to test byte and halfword load/store opcodes +# some memory must be reserved in the DRAM segment. + +BYTE_DATA = array.array("B", (0x11, 0x22, 0x33, 0x44)) +WORD_DATA = array.array("h", (100, 200, -100, -200)) +DWORD_DATA = array.array("i", (100_000, -200_000, 300_000, -400_000)) + + +@micropython.asm_xtensa +def tl32r() -> int: + nop() + j(CODE) + align(4) + label(DATA) + data(1, 1, 2, 3, 4, 5, 6, 7) + align(4) + label(CODE) + nop_n() + nop_n() + l32r(a2, DATA) + + +print(hex(tl32r())) + + +@micropython.asm_xtensa +def tl32i() -> uint: + call0(ENTRY) + label(ENTRY) + l32i(a2, a0, 0) + + +print(hex(tl32i())) + + +@micropython.asm_xtensa +def tl8ui(a2) -> uint: + mov(a3, a2) + l8ui(a2, a3, 1) + + +print(hex(tl8ui(BYTE_DATA))) + + +@micropython.asm_xtensa +def tl16ui(a2) -> uint: + mov(a3, a2) + l16ui(a2, a3, 2) + + +print(tl16ui(WORD_DATA)) + + +@micropython.asm_xtensa +def tl16si(a2) -> int: + mov(a3, a2) + l16si(a2, a3, 6) + + +print(tl16si(WORD_DATA)) + + +@micropython.asm_xtensa +def ts8i(a2, a3): + s8i(a3, a2, 1) + + +ts8i(BYTE_DATA, 0xFF) +print(BYTE_DATA) + + +@micropython.asm_xtensa +def ts16i(a2, a3): + s16i(a3, a2, 2) + + +ts16i(WORD_DATA, -123) +print(WORD_DATA) + + +@micropython.asm_xtensa +def ts32i(a2, a3) -> uint: + s32i(a3, a2, 4) + + +ts32i(DWORD_DATA, -123456) +print(DWORD_DATA) + + +@micropython.asm_xtensa +def tl32i_n(a2) -> uint: + l32i_n(a2, a2, 8) + + +print(tl32i_n(DWORD_DATA)) diff --git a/tests/inlineasm/xtensa/asmloadstore.py.exp b/tests/inlineasm/xtensa/asmloadstore.py.exp new file mode 100644 index 0000000000..e6672df6f8 --- /dev/null +++ b/tests/inlineasm/xtensa/asmloadstore.py.exp @@ -0,0 +1,9 @@ +0x4030201 +0xf8002022 +0x22 +200 +-200 +array('B', [17, 255, 51, 68]) +array('h', [100, -123, -100, -200]) +array('i', [100000, -123456, 300000, -400000]) +300000 diff --git a/tests/inlineasm/xtensa/asmmisc.py b/tests/inlineasm/xtensa/asmmisc.py new file mode 100644 index 0000000000..271ab83662 --- /dev/null +++ b/tests/inlineasm/xtensa/asmmisc.py @@ -0,0 +1,25 @@ +@micropython.asm_xtensa +def tnop(a2, a3, a4, a5): + nop() + + +out2 = tnop(0x100, 0x200, 0x300, 0x400) +print(out2 == 0x100) + + +@micropython.asm_xtensa +def tnop_n(a2, a3, a4, a5): + nop_n() + + +out2 = tnop_n(0x100, 0x200, 0x300, 0x400) +print(out2 == 0x100) + + +@micropython.asm_xtensa +def tmov_n(a2, a3): + mov_n(a4, a3) + add(a2, a4, a3) + + +print(tmov_n(0, 1)) diff --git a/tests/inlineasm/xtensa/asmmisc.py.exp b/tests/inlineasm/xtensa/asmmisc.py.exp new file mode 100644 index 0000000000..eefaa35daf --- /dev/null +++ b/tests/inlineasm/xtensa/asmmisc.py.exp @@ -0,0 +1,3 @@ +True +True +2 diff --git a/tests/inlineasm/xtensa/asmshift.py b/tests/inlineasm/xtensa/asmshift.py new file mode 100644 index 0000000000..271ca1ccd4 --- /dev/null +++ b/tests/inlineasm/xtensa/asmshift.py @@ -0,0 +1,137 @@ +@micropython.asm_xtensa +def lsl1(a2): + slli(a2, a2, 1) + + +print(hex(lsl1(0x123))) + + +@micropython.asm_xtensa +def lsl23(a2): + slli(a2, a2, 23) + + +print(hex(lsl23(1))) + + +@micropython.asm_xtensa +def lsr1(a2): + srli(a2, a2, 1) + + +print(hex(lsr1(0x123))) + + +@micropython.asm_xtensa +def lsr15(a2): + srli(a2, a2, 15) + + +print(hex(lsr15(0x80000000))) + + +@micropython.asm_xtensa +def asr1(a2): + srai(a2, a2, 1) + + +print(hex(asr1(0x123))) + + +@micropython.asm_xtensa +def asr31(a2): + srai(a2, a2, 31) + + +print(hex(asr31(0x80000000))) + + +@micropython.asm_xtensa +def lsl1r(a2): + movi(a3, 1) + ssl(a3) + sll(a2, a2) + + +print(hex(lsl1r(0x123))) + + +@micropython.asm_xtensa +def lsr1r(a2): + movi(a3, 1) + ssr(a3) + srl(a2, a2) + + +print(hex(lsr1r(0x123))) + + +@micropython.asm_xtensa +def asr1r(a2): + movi(a3, 1) + ssr(a3) + sra(a2, a2) + + +print(hex(asr1r(0x123))) + + +@micropython.asm_xtensa +def sll9(a2): + ssai(9) + sll(a2, a2) + + +print(hex(sll9(1))) + + +@micropython.asm_xtensa +def srlr(a2, a3): + ssa8l(a3) + srl(a2, a2) + + +print(hex(srlr(0x12340000, 2))) + + +@micropython.asm_xtensa +def sllr(a2, a3): + ssa8b(a3) + sll(a2, a2) + + +print(hex(sllr(0x1234, 2))) + + +@micropython.asm_xtensa +def srcr(a2, a3, a4): + ssr(a4) + src(a2, a2, a3) + + +print(hex(srcr(0x00000001, 0x80000000, 2))) + + +@micropython.asm_xtensa +def srai24(a2): + srai(a2, a2, 24) + + +print(hex(srai24(0x12345678))) + + +@micropython.asm_xtensa +def nsar(a2, a3): + nsa(a2, a3) + + +print(nsar(0x12345678, 0)) +print(nsar(0x12345678, -1)) + + +@micropython.asm_xtensa +def nsaur(a2, a3): + nsau(a2, a3) + + +print(nsaur(0x12345678, 0)) diff --git a/tests/inlineasm/xtensa/asmshift.py.exp b/tests/inlineasm/xtensa/asmshift.py.exp new file mode 100644 index 0000000000..3e2bb3b4ae --- /dev/null +++ b/tests/inlineasm/xtensa/asmshift.py.exp @@ -0,0 +1,17 @@ +0x246 +0x800000 +0x91 +0x10000 +0x91 +-0x1 +0x246 +0x91 +0x91 +0x800000 +0x1234 +0x12340000 +0x60000000 +0x12 +31 +31 +32 diff --git a/tests/micropython/viper_ptr16_load_boundary.py b/tests/micropython/viper_ptr16_load_boundary.py new file mode 100644 index 0000000000..ccaaa0909a --- /dev/null +++ b/tests/micropython/viper_ptr16_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr16) -> int: + return src[{off}] +print(b[{off} * 2:({off} + 1) * 2]) +""" + + +@micropython.viper +def get_index(src: ptr16, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[28:38] = b"0123456789" +b[252:262] = b"ABCDEFGHIJ" +b[4092:4102] = b"KLMNOPQRST" + +for pre, idx, post in (15, 16, 17), (127, 128, 129), (2047, 2048, 2049): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr16_load_boundary.py.exp b/tests/micropython/viper_ptr16_load_boundary.py.exp new file mode 100644 index 0000000000..4b8c184c13 --- /dev/null +++ b/tests/micropython/viper_ptr16_load_boundary.py.exp @@ -0,0 +1,12 @@ +13106 13620 14134 +bytearray(b'23') +bytearray(b'45') +bytearray(b'67') +17475 17989 18503 +bytearray(b'CD') +bytearray(b'EF') +bytearray(b'GH') +20045 20559 21073 +bytearray(b'MN') +bytearray(b'OP') +bytearray(b'QR') diff --git a/tests/micropython/viper_ptr16_store_boundary.py b/tests/micropython/viper_ptr16_store_boundary.py new file mode 100644 index 0000000000..e0a4f84557 --- /dev/null +++ b/tests/micropython/viper_ptr16_store_boundary.py @@ -0,0 +1,41 @@ +# Test boundary conditions for various architectures + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr16): + dest[{off}] = {val} +set{off}(b) +print(b[{off} * 2:({off} + 1) * 2]) +""" + +TEST_DATA = ( + (15, (0x4241, 0x4443, 0x4645)), + (127, (0x4847, 0x4A49, 0x4C4B)), + (2047, (0x4E4D, 0x504F, 0x5251)), +) + + +@micropython.viper +def set_index(dest: ptr16, i: int, val: int): + dest[i] = val + + +@micropython.viper +def set_index(dest: ptr16, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + set_index(b, start + i, v) + print(b[(start + i) * 2 : (start + i + 1) * 2]) + + +for i in range(len(b)): + b[i] = 0 + + +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + exec(SET_TEMPLATE.format(off=start + i, val=v + 0x0101)) diff --git a/tests/micropython/viper_ptr16_store_boundary.py.exp b/tests/micropython/viper_ptr16_store_boundary.py.exp new file mode 100644 index 0000000000..b56fe6695f --- /dev/null +++ b/tests/micropython/viper_ptr16_store_boundary.py.exp @@ -0,0 +1,18 @@ +bytearray(b'AB') +bytearray(b'CD') +bytearray(b'EF') +bytearray(b'GH') +bytearray(b'IJ') +bytearray(b'KL') +bytearray(b'MN') +bytearray(b'OP') +bytearray(b'QR') +bytearray(b'BC') +bytearray(b'DE') +bytearray(b'FG') +bytearray(b'HI') +bytearray(b'JK') +bytearray(b'LM') +bytearray(b'NO') +bytearray(b'PQ') +bytearray(b'RS') diff --git a/tests/micropython/viper_ptr32_load_boundary.py b/tests/micropython/viper_ptr32_load_boundary.py new file mode 100644 index 0000000000..6954bd46b2 --- /dev/null +++ b/tests/micropython/viper_ptr32_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr32) -> int: + return src[{off}] +print(b[{off} * 4:({off} + 1) * 4]) +""" + + +@micropython.viper +def get_index(src: ptr32, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[24:43] = b"0123456789ABCDEFGHIJ" +b[248:268] = b"KLMNOPQRSTUVWXYZabcd" +b[4088:4108] = b"efghijklmnopqrstuvwx" + +for pre, idx, post in (7, 8, 9), (63, 64, 65), (1023, 1024, 1025): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr32_load_boundary.py.exp b/tests/micropython/viper_ptr32_load_boundary.py.exp new file mode 100644 index 0000000000..a58e703f91 --- /dev/null +++ b/tests/micropython/viper_ptr32_load_boundary.py.exp @@ -0,0 +1,12 @@ +926299444 1111570744 1178944579 +bytearray(b'4567') +bytearray(b'89AB') +bytearray(b'CDEF') +1381060687 1448432723 1515804759 +bytearray(b'OPQR') +bytearray(b'STUV') +bytearray(b'WXYZ') +1818978921 1886350957 1953722993 +bytearray(b'ijkl') +bytearray(b'mnop') +bytearray(b'qrst') diff --git a/tests/micropython/viper_ptr32_store_boundary.py b/tests/micropython/viper_ptr32_store_boundary.py new file mode 100644 index 0000000000..243ff5cd9c --- /dev/null +++ b/tests/micropython/viper_ptr32_store_boundary.py @@ -0,0 +1,35 @@ +# Test boundary conditions for various architectures + +TEST_DATA = ( + (3, (0x04030201, 0x08070605, 0x0C0B0A09)), + (63, (0x100F0E0D, 0x14131211, 0x18171615)), + (1023, (0x1C1B1A19, 0x201F1E1D, 0x24232221)), +) + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr32): + dest[{off}] = {val} & 0x3FFFFFFF +set{off}(b) +print(b[{off} * 4:({off} + 1) * 4]) +""" + + +@micropython.viper +def set_index(dest: ptr32, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + set_index(b, start + i, v) + print(b[(start + i) * 4 : (start + i + 1) * 4]) + +for i in range(len(b)): + b[i] = 0 + + +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + exec(SET_TEMPLATE.format(off=start + i, val=v + 0x01010101)) diff --git a/tests/micropython/viper_ptr32_store_boundary.py.exp b/tests/micropython/viper_ptr32_store_boundary.py.exp new file mode 100644 index 0000000000..89f09fbc7a --- /dev/null +++ b/tests/micropython/viper_ptr32_store_boundary.py.exp @@ -0,0 +1,18 @@ +bytearray(b'\x01\x02\x03\x04') +bytearray(b'\x05\x06\x07\x08') +bytearray(b'\t\n\x0b\x0c') +bytearray(b'\r\x0e\x0f\x10') +bytearray(b'\x11\x12\x13\x14') +bytearray(b'\x15\x16\x17\x18') +bytearray(b'\x19\x1a\x1b\x1c') +bytearray(b'\x1d\x1e\x1f ') +bytearray(b'!"#$') +bytearray(b'\x02\x03\x04\x05') +bytearray(b'\x06\x07\x08\t') +bytearray(b'\n\x0b\x0c\r') +bytearray(b'\x0e\x0f\x10\x11') +bytearray(b'\x12\x13\x14\x15') +bytearray(b'\x16\x17\x18\x19') +bytearray(b'\x1a\x1b\x1c\x1d') +bytearray(b'\x1e\x1f !') +bytearray(b'"#$%') diff --git a/tests/micropython/viper_ptr8_load_boundary.py b/tests/micropython/viper_ptr8_load_boundary.py new file mode 100644 index 0000000000..bcb17a1e1f --- /dev/null +++ b/tests/micropython/viper_ptr8_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr8) -> int: + return src[{off}] +print(get{off}(b)) +""" + + +@micropython.viper +def get_index(src: ptr8, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[30:32] = b"123" +b[254:256] = b"456" +b[4094:4096] = b"789" + +for pre, idx, post in (30, 31, 32), (254, 255, 256), (4094, 4095, 4096): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr8_load_boundary.py.exp b/tests/micropython/viper_ptr8_load_boundary.py.exp new file mode 100644 index 0000000000..7cbd1ac78c --- /dev/null +++ b/tests/micropython/viper_ptr8_load_boundary.py.exp @@ -0,0 +1,12 @@ +49 50 51 +49 +50 +51 +52 53 54 +52 +53 +54 +55 56 57 +55 +56 +57 diff --git a/tests/micropython/viper_ptr8_store_boundary.py b/tests/micropython/viper_ptr8_store_boundary.py new file mode 100644 index 0000000000..ad51268454 --- /dev/null +++ b/tests/micropython/viper_ptr8_store_boundary.py @@ -0,0 +1,30 @@ +# Test boundary conditions for various architectures + +TEST_DATA = ((49, 30, 3), (52, 254, 3), (55, 4094, 3)) + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr8): + dest[{off}] = {val} +set{off}(b) +print(b[{off}]) +""" + + +@micropython.viper +def set_index(dest: ptr8, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for val, start, count in TEST_DATA: + for i in range(count): + set_index(b, start + i, val + i) + print(b[start : start + count]) + +for i in range(len(b)): + b[i] = 0 + +for val, start, count in TEST_DATA: + for i in range(count): + exec(SET_TEMPLATE.format(off=start + i, val=val + i + 16)) diff --git a/tests/micropython/viper_ptr8_store_boundary.py.exp b/tests/micropython/viper_ptr8_store_boundary.py.exp new file mode 100644 index 0000000000..a35cb3ac9e --- /dev/null +++ b/tests/micropython/viper_ptr8_store_boundary.py.exp @@ -0,0 +1,12 @@ +bytearray(b'123') +bytearray(b'456') +bytearray(b'789') +65 +66 +67 +68 +69 +70 +71 +72 +73 diff --git a/tests/misc/print_exception.py b/tests/misc/print_exception.py index 1d196d6ab1..92754733b5 100644 --- a/tests/misc/print_exception.py +++ b/tests/misc/print_exception.py @@ -1,3 +1,5 @@ +# Test sys.print_exception (MicroPython) / traceback.print_exception (CPython). + try: import io import sys diff --git a/tests/multi_net/tcp_accept_recv.py b/tests/multi_net/tcp_accept_recv.py index dee14e3b97..4108a6f8a3 100644 --- a/tests/multi_net/tcp_accept_recv.py +++ b/tests/multi_net/tcp_accept_recv.py @@ -1,30 +1,73 @@ -# Test recv on socket that just accepted a connection +# Test recv on listening socket after accept(), with various listen() arguments import socket PORT = 8000 +# Test cases for listen() function +LISTEN_ARGS = [None, 0, 1, 2] # None means no argument + # Server def instance0(): multitest.globals(IP=multitest.get_network_ip()) - s = socket.socket() - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) - s.listen(1) multitest.next() - s.accept() - try: - print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN - except OSError as er: - print(er.errno in (107, 128)) - s.close() + + test_num = 0 + for blocking_mode in [True, False]: + for listen_arg in LISTEN_ARGS: + test_num += 1 + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + + # Call listen with or without argument based on test case + if listen_arg is None: + print(f"Test case {test_num}/8: listen() blocking={blocking_mode}") + s.listen() + else: + print(f"Test case {test_num}/8: listen({listen_arg}) blocking={blocking_mode}") + s.listen(listen_arg) + + # Signal client that server is ready + multitest.broadcast(f"server_ready_{test_num}") + + # Wait for client connection + c, _ = s.accept() + + # Set blocking mode after accept + s.setblocking(blocking_mode) + + try: + print("recv", s.recv(10)) # should raise Errno 107 ENOTCONN + except OSError as er: + # Verify the error code is either 107 (ENOTCONN) or 128 (ENOTCONN on Windows) + print(er.errno in (107, 128)) + + # Cleanup + c.close() + s.close() + + # Signal client we're done with this test case + multitest.broadcast(f"server_done_{test_num}") # Client def instance1(): multitest.next() - s = socket.socket() - s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) - s.send(b"GET / HTTP/1.0\r\n\r\n") - s.close() + + test_num = 0 + for blocking_mode in [True, False]: + for _ in LISTEN_ARGS: + test_num += 1 + # Wait for server to be ready + multitest.wait(f"server_ready_{test_num}") + + # Connect to server + s = socket.socket() + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + s.send(b"GET / HTTP/1.0\r\n\r\n") + s.close() + + # Wait for server to finish this test case + multitest.wait(f"server_done_{test_num}") diff --git a/tests/multi_net/tcp_recv_peek.py b/tests/multi_net/tcp_recv_peek.py new file mode 100644 index 0000000000..ff540dd3c3 --- /dev/null +++ b/tests/multi_net/tcp_recv_peek.py @@ -0,0 +1,46 @@ +# Test TCP recv with MSG_PEEK +# +# Note that bare metal LWIP only returns at most one packet's worth of TCP data +# in any recv() call - including when peeking - so can't be too clever with +# different recv() combinations +import socket +import random + + +# Server +def instance0(): + PORT = random.randrange(10000, 50000) + multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + s.listen() + multitest.next() + s2, _ = s.accept() + print(s2.recv(8, socket.MSG_PEEK)) + print(s2.recv(8)) + s2.send(b"1234567890") + multitest.broadcast("0-sent") + multitest.wait("1-sent") + print(s2.recv(5, socket.MSG_PEEK)) + print(s2.recv(5, socket.MSG_PEEK)) + multitest.broadcast("0-recved") + multitest.wait("1-recved") # sync here necessary as MP sends RST if closing TCP early + s2.close() + s.close() + + +# Client +def instance1(): + multitest.next() + s = socket.socket() + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + s.send(b"abcdefgh") + multitest.broadcast("1-sent") + multitest.wait("0-sent") + s.send(b"klmnopqr") + print(s.recv(5, socket.MSG_PEEK)) + print(s.recv(10)) + multitest.broadcast("1-recved") + multitest.wait("0-recved") + s.close() diff --git a/tests/multi_net/udp_data_multi.py b/tests/multi_net/udp_data_multi.py new file mode 100644 index 0000000000..5d7b13e518 --- /dev/null +++ b/tests/multi_net/udp_data_multi.py @@ -0,0 +1,69 @@ +# Test UDP reception when there are multiple incoming UDP packets that need to be +# queued internally in the TCP/IP stack. + +import socket + +NUM_NEW_SOCKETS = 4 +NUM_PACKET_BURSTS = 6 +NUM_PACKET_GROUPS = 5 +TOTAL_PACKET_BURSTS = NUM_NEW_SOCKETS * NUM_PACKET_BURSTS +# The tast passes if more than 75% of packets are received in each group. +PACKET_RECV_THRESH = 0.75 * TOTAL_PACKET_BURSTS +PORT = 8000 + + +# Server +def instance0(): + recv_count = {i: 0 for i in range(NUM_PACKET_GROUPS)} + multitest.globals(IP=multitest.get_network_ip()) + multitest.next() + for i in range(NUM_NEW_SOCKETS): + print("test socket", i) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT + i)[0][-1]) + s.settimeout(0.250) + multitest.broadcast("server ready") + for burst in range(NUM_PACKET_BURSTS): + # Wait for all packets to be sent, without receiving any yet. + multitest.wait("data sent burst={}".format(burst)) + # Try to receive all packets (they should be waiting in the queue). + for group in range(NUM_PACKET_GROUPS): + try: + data, addr = s.recvfrom(1000) + except: + continue + recv_burst, recv_group = data.split(b":") + recv_burst = int(recv_burst) + recv_group = int(recv_group) + if recv_burst == burst: + recv_count[recv_group] += 1 + # Inform the client that all data was received. + multitest.broadcast("data received burst={}".format(burst)) + s.close() + + # Check how many packets were received. + for group, count in recv_count.items(): + if count >= PACKET_RECV_THRESH: + print("pass group={}".format(group)) + else: + print("fail group={} received={}%".format(group, 100 * count // TOTAL_PACKET_BURSTS)) + + +# Client +def instance1(): + multitest.next() + for i in range(NUM_NEW_SOCKETS): + print("test socket", i) + ai = socket.getaddrinfo(IP, PORT + i)[0][-1] + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + multitest.wait("server ready") + for burst in range(NUM_PACKET_BURSTS): + # Send a bunch of packets all in a row. + for group in range(NUM_PACKET_GROUPS): + s.sendto(b"%d:%d" % (burst, group), ai) + # Inform the server that the data has been sent. + multitest.broadcast("data sent burst={}".format(burst)) + # Wait for the server to finish receiving. + multitest.wait("data received burst={}".format(burst)) + s.close() diff --git a/tests/multi_net/udp_data_multi.py.exp b/tests/multi_net/udp_data_multi.py.exp new file mode 100644 index 0000000000..bc67c6ab0c --- /dev/null +++ b/tests/multi_net/udp_data_multi.py.exp @@ -0,0 +1,15 @@ +--- instance0 --- +test socket 0 +test socket 1 +test socket 2 +test socket 3 +pass group=0 +pass group=1 +pass group=2 +pass group=3 +pass group=4 +--- instance1 --- +test socket 0 +test socket 1 +test socket 2 +test socket 3 diff --git a/tests/multi_net/udp_recv_dontwait.py b/tests/multi_net/udp_recv_dontwait.py new file mode 100644 index 0000000000..640f3f060e --- /dev/null +++ b/tests/multi_net/udp_recv_dontwait.py @@ -0,0 +1,59 @@ +# Test UDP recv and recvfrom with MSG_DONTWAIT +import random +import socket + +try: + import errno, time +except ImportError: + print("SKIP") + raise SystemExit + + +# Server +def instance0(): + PORT = random.randrange(10000, 50000) + multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + multitest.next() + begin = time.ticks_ms() + + # do some recvs before instance1 starts, when we know no packet is waiting + try: + print(s.recvfrom(8, socket.MSG_DONTWAIT)) + except OSError as e: + print(e.errno == errno.EAGAIN) + try: + print(s.recv(8, socket.MSG_DONTWAIT)) + except OSError as e: + print(e.errno == errno.EAGAIN) + + # the above steps should not have taken any substantial time + elapsed = time.ticks_diff(time.ticks_ms(), begin) + print(True if elapsed < 50 else elapsed) + + # Now instance1 will send us a UDP packet + multitest.broadcast("0-ready") + multitest.wait("1-sent") + + for _ in range(10): # retry if necessary, to allow for network delay + time.sleep_ms(100) + try: + print(s.recv(8, socket.MSG_DONTWAIT)) + break + except OSError as er: + if er.errno != errno.EAGAIN: + raise er + s.close() + + +# Client +def instance1(): + multitest.next() + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + multitest.wait("0-ready") + print(s.send(b"abcdefgh")) + multitest.broadcast("1-sent") + s.close() diff --git a/tests/multi_net/udp_recv_dontwait.py.exp b/tests/multi_net/udp_recv_dontwait.py.exp new file mode 100644 index 0000000000..f61fd4bbe2 --- /dev/null +++ b/tests/multi_net/udp_recv_dontwait.py.exp @@ -0,0 +1,7 @@ +--- instance0 --- +True +True +True +b'abcdefgh' +--- instance1 --- +8 diff --git a/tests/multi_net/udp_recv_peek.py b/tests/multi_net/udp_recv_peek.py new file mode 100644 index 0000000000..47897ce553 --- /dev/null +++ b/tests/multi_net/udp_recv_peek.py @@ -0,0 +1,36 @@ +# Test UDP recv and recvfrom with MSG_PEEK +import random +import socket +import time + + +# Server +def instance0(): + PORT = random.randrange(10000, 50000) + multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + multitest.next() + peek_bytes, peek_addr = s.recvfrom(8, socket.MSG_PEEK) + print(peek_bytes) + real_bytes, real_addr = s.recvfrom(8) + print(real_bytes) + print(peek_addr == real_addr) # source addr should be the same for each + res = s.sendto(b"1234567890", peek_addr) + print(res) + print(s.recv(5, socket.MSG_PEEK)) + print(s.recv(5, socket.MSG_PEEK)) + s.close() + + +# Client +def instance1(): + multitest.next() + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + s.send(b"abcdefgh") + s.send(b"klmnopqr") + print(s.recv(5, socket.MSG_PEEK)) + print(s.recv(10)) + s.close() diff --git a/tests/net_inet/mpycert.der b/tests/net_inet/mpycert.der Binary files differindex 0b0eabc9bc..ac22dcf9e8 100644 --- a/tests/net_inet/mpycert.der +++ b/tests/net_inet/mpycert.der diff --git a/tests/net_inet/ssl_cert.py b/tests/net_inet/ssl_cert.py index 3597b7855d..83d83e3f8e 100644 --- a/tests/net_inet/ssl_cert.py +++ b/tests/net_inet/ssl_cert.py @@ -1,4 +1,3 @@ -import binascii import socket import ssl @@ -6,48 +5,48 @@ import ssl # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 </dev/null 2>/dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R11 +# 1 s:C=US, O=Let's Encrypt, CN=R10 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 -# a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 +# a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT -# Copy PEM content to a file (certmpy.pem) and convert to DER e.g. -# $ openssl x509 -in certmpy.pem -out certmpy.der -outform DER -# Then convert to hex format, eg using binascii.hexlify(data). +# Copy PEM content to a file (mpycert.pem) and convert to DER e.g. +# $ openssl x509 -in mpycert.pem -out mpycert.der -outform DER +# Then convert to hex format using: for i in range(0,len(data),40):print(data[i:i+40].hex()) -ca_cert_chain = binascii.unhexlify( - b"30820506308202eea0030201020211008a7d3e13d62f30ef2386bd29076b34f8300d06092a864886" - b"f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65" - b"742053656375726974792052657365617263682047726f7570311530130603550403130c49535247" - b"20526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a" - b"3033310b300906035504061302555331163014060355040a130d4c6574277320456e637279707431" - b"0c300a0603550403130352313130820122300d06092a864886f70d01010105000382010f00308201" - b"0a0282010100ba87bc5c1b0039cbca0acdd46710f9013ca54ea561cb26ca52fb1501b7b928f5281e" - b"ed27b324183967090c08ece03ab03b770ebdf3e53954410c4eae41d69974de51dbef7bff58bda8b7" - b"13f6de31d5f272c9726a0b8374959c4600641499f3b1d922d9cda892aa1c267a3ffeef58057b0895" - b"81db710f8efbe33109bb09be504d5f8f91763d5a9d9e83f2e9c466b3e106664348188065a037189a" - b"9b843297b1b2bdc4f815009d2788fbe26317966c9b27674bc4db285e69c279f0495ce02450e1c4bc" - b"a105ac7b406d00b4c2413fa758b82fc55c9ba5bb099ef1feebb08539fda80aef45c478eb652ac2cf" - b"5f3cdee35c4d1bf70b272baa0b4277534f796a1d87d90203010001a381f83081f5300e0603551d0f" - b"0101ff040403020186301d0603551d250416301406082b0601050507030206082b06010505070301" - b"30120603551d130101ff040830060101ff020100301d0603551d0e04160414c5cf46a4eaf4c3c07a" - b"6c95c42db05e922f26e3b9301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58" - b"f6e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f" - b"2f78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c010201302706" - b"03551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d" - b"06092a864886f70d01010b050003820201004ee2895d0a031c9038d0f51ff9715cf8c38fb237887a" - b"6fb0251fedbeb7d886068ee90984cd72bf81f3fccacf5348edbdf66942d4a5113e35c813b2921d05" - b"5fea2ed4d8f849c3adf599969cef26d8e1b4240b48204dfcd354b4a9c621c8e1361bff77642917b9" - b"f04bef5deacd79d0bf90bfbe23b290da4aa9483174a9440be1e2f62d8371a4757bd294c10519461c" - b"b98ff3c47448252a0de5f5db43e2db939bb919b41f2fdf6a0e8f31d3630fbb29dcdd662c3fb01b67" - b"51f8413ce44db9acb8a49c6663f5ab85231dcc53b6ab71aedcc50171da36ee0a182a32fd09317c8f" - b"f673e79c9cb54a156a77825acfda8d45fe1f2a6405303e73c2c60cb9d63b634aab4603fe99c04640" - b"276063df503a0747d8154a9fea471f995a08620cb66c33084dd738ed482d2e0568ae805def4cdcd8" - b"20415f68f1bb5acde30eb00c31879b43de4943e1c8043fd13c1b87453069a8a9720e79121c31d83e" - b"2357dda74fa0f01c81d1771f6fd6d2b9a8b3031681394b9f55aed26ae4b3bfeaa5d59f4ba3c9d63b" - b"72f34af654ab0cfc38f76080df6e35ca75a154e42fbc6e17c91aa537b5a29abaecf4c075464f77a8" - b"e8595691662d6ede2981d6a697055e6445be2cceea644244b0c34fadf0b4dc03ca999b098295820d" - b"638a66f91972f8d5b98910e289980935f9a21cbe92732374e99d1fd73b4a9a845810c2f3a7e235ec" - b"7e3b45ce3046526bc0c0" +ca_cert_chain = bytes.fromhex( + "30820505308202eda00302010202104ba85293f79a2fa273064ba8048d75d0300d06092a864886f7" + "0d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e6574" + "2053656375726974792052657365617263682047726f7570311530130603550403130c4953524720" + "526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a30" + "33310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074310c" + "300a0603550403130352313030820122300d06092a864886f70d01010105000382010f003082010a" + "0282010100cf57e5e6c45412edb447fec92758764650288c1d3e88df059dd5b51829bdddb55abffa" + "f6cea3beaf00214b625a5a3c012fc55803f689ff8e1143ebc1b5e01407968f6f1fd7e7ba81390975" + "65b7c2af185b372628e7a3f4072b6d1affab58bc95ae40ffe9cb57c4b55b7f780d1861bc17e754c6" + "bb4991cd6e18d18085eea66536bc74eabc504ceafc21f338169394bab0d36b3806cd16127aca5275" + "c8ad76b2c29c5d98455c6f617bc62dee3c13528601d957e6381cdf8db51f92919ae74a1ccc45a872" + "55f0b0e6a307ecfda71b669e3f488b71847158c93afaef5ef25b442b3c74e78fb247c1076acd9ab7" + "0d96f712812651540aec61f6f7f5e2f28ac8950d8d0203010001a381f83081f5300e0603551d0f01" + "01ff040403020186301d0603551d250416301406082b0601050507030206082b0601050507030130" + "120603551d130101ff040830060101ff020100301d0603551d0e04160414bbbcc347a5e4bca9c6c3" + "a4720c108da235e1c8e8301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58f6" + "e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f2f" + "78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c01020130270603" + "551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d06" + "092a864886f70d01010b0500038202010092b1e74137eb799d81e6cde225e13a20e9904495a3815c" + "cfc35dfdbda070d5b19628220bd2f228cf0ce7d4e6438c24221dc14292d109af9f4bf4c8704f2016" + "b15add01f61ff81f616b1427b0728d63aeeee2ce4bcf37ddbba3d4cde7ad50adbdbfe3ec3e623670" + "9931a7e88dddea62e212aef59cd43d2c0caad09c79beea3d5c446e9631635a7dd67e4f24a04b057f" + "5e6fd2d4ea5f334b13d657b6cade51b85da3098274fdc7789eb3b9ac16da4a2b96c3b68b628ff974" + "19a29e03dee96f9bb00fd2a05af6855cc204b7c8d54e32c4bf045dbc29f6f7818f0c5d3c53c94090" + "8bfbb60865b9a421d509e51384843782ce1028fc76c206257a46524dda5372a4273f6270acbe6948" + "00fb670fdb5ba1e8d703212dd7c9f69942398343df770a1208f125d6ba9419541888a5c58ee11a99" + "93796bec1cf93140b0cc3200df9f5ee7b492ab9082918d0de01e95ba593b2e4b5fc2b74635523906" + "c0bdaaac52c122a0449799f70ca021a7a16c714716170168c0caa62665047cb3aec9e79455c26f9b" + "3c1ca9f92ec5201af076e0beec18d64fd825fb7611e8bfe6210fe8e8ccb5b6a7d5b8f79f41cf6122" + "466a83b668972e7cea4e95db23eb2ec82b2884a460e949f4442e3bf9ca625701e25d9016f9c9fc7a" + "23488ea6d58172f128fa5dcefbed4e738f942ed241949899dba7af705ff5befb0220bf66276cb4ad" + "fa75120b2b3ece039e" ) diff --git a/tests/net_inet/test_sslcontext_client.py b/tests/net_inet/test_sslcontext_client.py index 30ec0ac7c8..119a42721f 100644 --- a/tests/net_inet/test_sslcontext_client.py +++ b/tests/net_inet/test_sslcontext_client.py @@ -5,14 +5,12 @@ import ssl # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 </dev/null 2>/dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R11 +# 1 s:C=US, O=Let's Encrypt, CN=R10 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 -# a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 +# a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT -# Copy PEM content to a file (certmpy.pem) and convert to DER e.g. -# $ openssl x509 -in certmpy.pem -out certmpy.der -outform DER -# Then convert to hex format, eg using binascii.hexlify(data). - +# Copy PEM content to a file (mpycert.pem) and convert to DER e.g. +# $ openssl x509 -in mpycert.pem -out mpycert.der -outform DER ca_cert_chain = "mpycert.der" try: diff --git a/tests/ports/rp2/rp2_lightsleep_thread.py b/tests/ports/rp2/rp2_lightsleep_thread.py new file mode 100644 index 0000000000..494ead4223 --- /dev/null +++ b/tests/ports/rp2/rp2_lightsleep_thread.py @@ -0,0 +1,67 @@ +# Verify that a thread running on CPU1 can go to lightsleep +# and wake up in the expected timeframe +import _thread +import time +import unittest +from machine import lightsleep + +N_SLEEPS = 5 +SLEEP_MS = 250 + +IDEAL_RUNTIME = N_SLEEPS * SLEEP_MS +MAX_RUNTIME = (N_SLEEPS + 1) * SLEEP_MS +MAX_DELTA = 20 + + +class LightSleepInThread(unittest.TestCase): + def thread_entry(self, is_thread=True): + for _ in range(N_SLEEPS): + lightsleep(SLEEP_MS) + if is_thread: + self.thread_done = True + + def elapsed_ms(self): + return time.ticks_diff(time.ticks_ms(), self.t0) + + def setUp(self): + self.thread_done = False + self.t0 = time.ticks_ms() + + def test_cpu0_busy(self): + _thread.start_new_thread(self.thread_entry, ()) + # CPU0 is busy-waiting not asleep itself + while not self.thread_done: + self.assertLessEqual(self.elapsed_ms(), MAX_RUNTIME) + self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=MAX_DELTA) + + def test_cpu0_sleeping(self): + _thread.start_new_thread(self.thread_entry, ()) + time.sleep_ms(MAX_RUNTIME) + self.assertTrue(self.thread_done) + self.assertAlmostEqual(self.elapsed_ms(), MAX_RUNTIME, delta=MAX_DELTA) + + def test_cpu0_also_lightsleep(self): + _thread.start_new_thread(self.thread_entry, ()) + time.sleep_ms(50) # account for any delay in starting the thread + self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag + while not self.thread_done: + time.sleep_ms(10) + # + # Only one thread can actually be in lightsleep at a time to avoid + # races, but otherwise the behaviour when both threads call lightsleep() + # is unspecified. + # + # Currently, the other thread will return immediately if one is already + # in lightsleep. Therefore, runtime can be between IDEAL_RUNTIME and + # IDEAL_RUNTIME * 2 depending on how many times the calls to lightsleep() race + # each other. + # + # Note this test case is really only here to ensure that the rp2 hasn't + # hung or failed to sleep at all - not to verify any correct behaviour + # when there's a race to call lightsleep(). + self.assertGreaterEqual(self.elapsed_ms(), IDEAL_RUNTIME - MAX_DELTA) + self.assertLessEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2 + MAX_DELTA) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ports/rp2/rp2_machine_idle.py b/tests/ports/rp2/rp2_machine_idle.py index 3135110b82..f9c2828478 100644 --- a/tests/ports/rp2/rp2_machine_idle.py +++ b/tests/ports/rp2/rp2_machine_idle.py @@ -1,4 +1,3 @@ -import sys import machine import time @@ -18,11 +17,6 @@ import time # Verification uses the average idle time, as individual iterations will always # have outliers due to interrupts, scheduler, etc. -# RP2350 currently fails this test because machine.idle() resumes immediately. -if "RP2350" in sys.implementation._machine: - print("SKIP") - raise SystemExit - ITERATIONS = 500 total = 0 diff --git a/tests/ports/rp2/rp2_machine_timer.py b/tests/ports/rp2/rp2_machine_timer.py new file mode 100644 index 0000000000..ac4efcf7f3 --- /dev/null +++ b/tests/ports/rp2/rp2_machine_timer.py @@ -0,0 +1,20 @@ +from machine import Timer +from time import sleep_ms + +# Test the rp2-specific adjustable tick_hz and hard/soft IRQ handlers +# for both one-shot and periodic timers. + +modes = {Timer.ONE_SHOT: "one-shot", Timer.PERIODIC: "periodic"} +kinds = {False: "soft", True: "hard"} + +for mode in modes: + for hard in kinds: + for period in 2, 4: + timer = Timer( + mode=mode, + period=period, + hard=hard, + callback=lambda t: print("callback", modes[mode], kinds[hard], period), + ) + sleep_ms(9) + timer.deinit() diff --git a/tests/ports/rp2/rp2_machine_timer.py.exp b/tests/ports/rp2/rp2_machine_timer.py.exp new file mode 100644 index 0000000000..b3dd93dfab --- /dev/null +++ b/tests/ports/rp2/rp2_machine_timer.py.exp @@ -0,0 +1,16 @@ +callback one-shot soft 2 +callback one-shot soft 4 +callback one-shot hard 2 +callback one-shot hard 4 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 4 +callback periodic soft 4 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 4 +callback periodic hard 4 diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index 5ff947e883..ac64edde69 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -14,6 +14,7 @@ false true 80000000 abc % +.a . # GC 0 0 @@ -50,16 +51,16 @@ RuntimeError: ame__ port -builtins micropython _asyncio _thread -array binascii btree cexample -cmath collections cppexample cryptolib -deflate errno example_package -ffi framebuf gc hashlib -heapq io json machine -marshal math os platform -random re select socket -struct sys termios time -tls uctypes vfs websocket +builtins micropython array binascii +btree cexample cmath collections +cppexample cryptolib deflate errno +example_package ffi framebuf +gc hashlib heapq io +json machine marshal math +os platform random re +select socket struct sys +termios time tls uctypes +vfs websocket me micropython machine marshal math @@ -89,6 +90,10 @@ data 12345 6 -1 +0 +1 +0 +0.000000 # runtime utils TypeError: unsupported type for __abs__: 'str' TypeError: unsupported types for __divmod__: 'str', 'str' @@ -122,6 +127,13 @@ unlocked KeyboardInterrupt: KeyboardInterrupt: 10 +loop +scheduled function +loop +scheduled function +loop +scheduled function +scheduled function # ringbuf 99 0 98 1 diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 387eec7018..92bd64193d 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -15,6 +15,8 @@ import itertools import subprocess import tempfile +run_tests_module = __import__("run-tests") + test_dir = os.path.abspath(os.path.dirname(__file__)) if os.path.abspath(sys.path[0]) == test_dir: @@ -488,9 +490,7 @@ def print_diff(a, b): def run_tests(test_files, instances_truth, instances_test): - skipped_tests = [] - passed_tests = [] - failed_tests = [] + test_results = [] for test_file, num_instances in test_files: instances_str = "|".join(str(instances_test[i]) for i in range(num_instances)) @@ -526,13 +526,13 @@ def run_tests(test_files, instances_truth, instances_test): # Print result of test if skip: print("skip") - skipped_tests.append(test_file) + test_results.append((test_file, "skip", "")) elif output_test == output_truth: print("pass") - passed_tests.append(test_file) + test_results.append((test_file, "pass", "")) else: print("FAIL") - failed_tests.append(test_file) + test_results.append((test_file, "fail", "")) if not cmd_args.show_output: print("### TEST ###") print(output_test, end="") @@ -549,15 +549,7 @@ def run_tests(test_files, instances_truth, instances_test): if cmd_args.show_output: print() - print("{} tests performed".format(len(skipped_tests) + len(passed_tests) + len(failed_tests))) - print("{} tests passed".format(len(passed_tests))) - - if skipped_tests: - print("{} tests skipped: {}".format(len(skipped_tests), " ".join(skipped_tests))) - if failed_tests: - print("{} tests failed: {}".format(len(failed_tests), " ".join(failed_tests))) - - return not failed_tests + return test_results def main(): @@ -583,6 +575,12 @@ def main(): default=1, help="repeat the test with this many permutations of the instance order", ) + cmd_parser.add_argument( + "-r", + "--result-dir", + default=run_tests_module.base_path("results"), + help="directory for test results", + ) cmd_parser.epilog = ( "Supported instance types:\r\n" " -i pyb:<port> physical device (eg. pyboard) on provided repl port.\n" @@ -623,13 +621,15 @@ def main(): for _ in range(max_instances - len(instances_test)): instances_test.append(PyInstanceSubProcess([MICROPYTHON])) + os.makedirs(cmd_args.result_dir, exist_ok=True) all_pass = True try: for i, instances_test_permutation in enumerate(itertools.permutations(instances_test)): if i >= cmd_args.permutations: break - all_pass &= run_tests(test_files, instances_truth, instances_test_permutation) + test_results = run_tests(test_files, instances_truth, instances_test_permutation) + all_pass &= run_tests_module.create_test_report(cmd_args, test_results) finally: for i in instances_truth: diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index b858989daa..f9d2074f6f 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -9,6 +9,8 @@ import subprocess import sys import argparse +run_tests_module = __import__("run-tests") + sys.path.append("../tools") import pyboard @@ -73,6 +75,7 @@ class __FS: return __File() vfs.mount(__FS(), '/__remote') sys.path.insert(0, '/__remote') +{import_prelude} sys.modules['{}'] = __import__('__injected') """ @@ -132,7 +135,15 @@ def detect_architecture(target): return platform, arch, None -def run_tests(target_truth, target, args, stats, resolved_arch): +def run_tests(target_truth, target, args, resolved_arch): + global injected_import_hook_code + + prelude = "" + if args.begin: + prelude = args.begin.read() + injected_import_hook_code = injected_import_hook_code.replace("{import_prelude}", prelude) + + test_results = [] for test_file in args.files: # Find supported test test_file_basename = os.path.basename(test_file) @@ -155,7 +166,8 @@ def run_tests(target_truth, target, args, stats, resolved_arch): with open(NATMOD_EXAMPLE_DIR + test_mpy, "rb") as f: test_script += b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n" except OSError: - print("---- {} - mpy file not compiled".format(test_file)) + test_results.append((test_file, "skip", "mpy file not compiled")) + print("skip {} - mpy file not compiled".format(test_file)) continue test_script += bytes(injected_import_hook_code.format(test_module), "ascii") test_script += test_file_data @@ -187,17 +199,18 @@ def run_tests(target_truth, target, args, stats, resolved_arch): result = "pass" # Accumulate statistics - stats["total"] += 1 if result == "pass": - stats["pass"] += 1 + test_results.append((test_file, "pass", "")) elif result == "SKIP": - stats["skip"] += 1 + test_results.append((test_file, "skip", "")) else: - stats["fail"] += 1 + test_results.append((test_file, "fail", "")) # Print result print("{:4} {}{}".format(result, test_file, extra)) + return test_results + def main(): cmd_parser = argparse.ArgumentParser( @@ -212,6 +225,19 @@ def main(): cmd_parser.add_argument( "-a", "--arch", choices=AVAILABLE_ARCHS, help="override native architecture of the target" ) + cmd_parser.add_argument( + "-b", + "--begin", + type=argparse.FileType("rt"), + default=None, + help="prologue python file to execute before module import", + ) + cmd_parser.add_argument( + "-r", + "--result-dir", + default=run_tests_module.base_path("results"), + help="directory for test results", + ) cmd_parser.add_argument("files", nargs="*", help="input test files") args = cmd_parser.parse_args() @@ -236,20 +262,14 @@ def main(): print("platform={} ".format(target_platform), end="") print("arch={}".format(target_arch)) - stats = {"total": 0, "pass": 0, "fail": 0, "skip": 0} - run_tests(target_truth, target, args, stats, target_arch) + os.makedirs(args.result_dir, exist_ok=True) + test_results = run_tests(target_truth, target, args, target_arch) + res = run_tests_module.create_test_report(args, test_results) target.close() target_truth.close() - print("{} tests performed".format(stats["total"])) - print("{} tests passed".format(stats["pass"])) - if stats["fail"]: - print("{} tests failed".format(stats["fail"])) - if stats["skip"]: - print("{} tests skipped".format(stats["skip"])) - - if stats["fail"]: + if not res: sys.exit(1) diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 81d873c459..cac2fee58f 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -10,10 +10,12 @@ import sys import argparse from glob import glob +run_tests_module = __import__("run-tests") + sys.path.append("../tools") import pyboard -prepare_script_for_target = __import__("run-tests").prepare_script_for_target +prepare_script_for_target = run_tests_module.prepare_script_for_target # Paths for host executables if os.name == "nt": @@ -90,9 +92,9 @@ def run_benchmark_on_target(target, script): def run_benchmarks(args, target, param_n, param_m, n_average, test_list): + test_results = [] skip_complex = run_feature_test(target, "complex") != "complex" skip_native = run_feature_test(target, "native_check") != "native" - target_had_error = False for test_file in sorted(test_list): print(test_file + ": ", end="") @@ -105,6 +107,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): and test_file.find("viper_") != -1 ) if skip: + test_results.append((test_file, "skip", "")) print("SKIP") continue @@ -125,6 +128,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): if isinstance(target, pyboard.Pyboard) or args.via_mpy: crash, test_script_target = prepare_script_for_target(args, script_text=test_script) if crash: + test_results.append((test_file, "fail", "preparation")) print("CRASH:", test_script_target) continue else: @@ -162,10 +166,13 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): error = "FAIL truth" if error is not None: - if not error.startswith("SKIP"): - target_had_error = True + if error.startswith("SKIP"): + test_results.append((test_file, "skip", error)) + else: + test_results.append((test_file, "fail", error)) print(error) else: + test_results.append((test_file, "pass", "")) t_avg, t_sd = compute_stats(times) s_avg, s_sd = compute_stats(scores) print( @@ -179,7 +186,7 @@ def run_benchmarks(args, target, param_n, param_m, n_average, test_list): sys.stdout.flush() - return target_had_error + return test_results def parse_output(filename): @@ -265,6 +272,12 @@ def main(): cmd_parser.add_argument("--via-mpy", action="store_true", help="compile code to .mpy first") cmd_parser.add_argument("--mpy-cross-flags", default="", help="flags to pass to mpy-cross") cmd_parser.add_argument( + "-r", + "--result-dir", + default=run_tests_module.base_path("results"), + help="directory for test results", + ) + cmd_parser.add_argument( "N", nargs=1, help="N parameter (approximate target CPU frequency in MHz)" ) cmd_parser.add_argument("M", nargs=1, help="M parameter (approximate target heap in kbytes)") @@ -307,13 +320,15 @@ def main(): print("N={} M={} n_average={}".format(N, M, n_average)) - target_had_error = run_benchmarks(args, target, N, M, n_average, tests) + os.makedirs(args.result_dir, exist_ok=True) + test_results = run_benchmarks(args, target, N, M, n_average, tests) + res = run_tests_module.create_test_report(args, test_results) if isinstance(target, pyboard.Pyboard): target.exit_raw_repl() target.close() - if target_had_error: + if not res: sys.exit(1) diff --git a/tests/run-tests.py b/tests/run-tests.py index ac411a0be6..628fde9d30 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -105,14 +105,11 @@ PC_PLATFORMS = ("darwin", "linux", "win32") # These are tests that are difficult to detect that they should not be run on the given target. platform_tests_to_skip = { "esp8266": ( - "micropython/viper_args.py", # too large - "micropython/viper_binop_arith.py", # too large - "misc/rge_sm.py", # too large + "misc/rge_sm.py", # incorrect values due to object representation C ), "minimal": ( "basics/class_inplace_op.py", # all special methods not supported "basics/subclass_native_init.py", # native subclassing corner cases not support - "misc/rge_sm.py", # too large "micropython/opt_level.py", # don't assume line numbers are stored ), "nrf": ( @@ -272,22 +269,17 @@ def detect_test_platform(pyb, args): print() -def prepare_script_for_target(args, *, script_filename=None, script_text=None, force_plain=False): +def prepare_script_for_target(args, *, script_text=None, force_plain=False): if force_plain or (not args.via_mpy and args.emit == "bytecode"): - if script_filename is not None: - with open(script_filename, "rb") as f: - script_text = f.read() + # A plain test to run as-is, no processing needed. + pass elif args.via_mpy: tempname = tempfile.mktemp(dir="") mpy_filename = tempname + ".mpy" - if script_filename is None: - script_filename = tempname + ".py" - cleanup_script_filename = True - with open(script_filename, "wb") as f: - f.write(script_text) - else: - cleanup_script_filename = False + script_filename = tempname + ".py" + with open(script_filename, "wb") as f: + f.write(script_text) try: subprocess.check_output( @@ -303,8 +295,7 @@ def prepare_script_for_target(args, *, script_filename=None, script_text=None, f script_text = b"__buf=" + bytes(repr(f.read()), "ascii") + b"\n" rm_f(mpy_filename) - if cleanup_script_filename: - rm_f(script_filename) + rm_f(script_filename) script_text += bytes(injected_import_hook_code, "ascii") else: @@ -315,9 +306,21 @@ def prepare_script_for_target(args, *, script_filename=None, script_text=None, f def run_script_on_remote_target(pyb, args, test_file, is_special): - had_crash, script = prepare_script_for_target( - args, script_filename=test_file, force_plain=is_special - ) + with open(test_file, "rb") as f: + script = f.read() + + # If the test is not a special test, prepend it with a print to indicate that it started. + # If the print does not execute this means that the test did not even start, eg it was + # too large for the target. + prepend_start_test = not is_special + if prepend_start_test: + if script.startswith(b"#"): + script = b"print('START TEST')" + script + else: + script = b"print('START TEST')\n" + script + + had_crash, script = prepare_script_for_target(args, script_text=script, force_plain=is_special) + if had_crash: return True, script @@ -328,9 +331,19 @@ def run_script_on_remote_target(pyb, args, test_file, is_special): except pyboard.PyboardError as e: had_crash = True if not is_special and e.args[0] == "exception": - output_mupy = e.args[1] + e.args[2] + b"CRASH" + if prepend_start_test and e.args[1] == b"" and b"MemoryError" in e.args[2]: + output_mupy = b"SKIP-TOO-LARGE\n" + else: + output_mupy = e.args[1] + e.args[2] + b"CRASH" else: output_mupy = bytes(e.args[0], "ascii") + b"\nCRASH" + + if prepend_start_test: + if output_mupy.startswith(b"START TEST\r\n"): + output_mupy = output_mupy.removeprefix(b"START TEST\r\n") + else: + had_crash = True + return had_crash, output_mupy @@ -392,6 +405,10 @@ def run_micropython(pyb, args, test_file, test_file_abspath, is_special=False): return rv def send_get(what): + # Detect {\x00} pattern and convert to ctrl-key codes. + ctrl_code = lambda m: bytes([int(m.group(1))]) + what = re.sub(rb'{\\x(\d\d)}', ctrl_code, what) + os.write(master, what) return get() @@ -474,7 +491,7 @@ def run_micropython(pyb, args, test_file, test_file_abspath, is_special=False): output_mupy = output_mupy.replace(b"\r\n", b"\n") # don't try to convert the output if we should skip this test - if had_crash or output_mupy in (b"SKIP\n", b"CRASH"): + if had_crash or output_mupy in (b"SKIP\n", b"SKIP-TOO-LARGE\n", b"CRASH"): return output_mupy # skipped special tests will output "SKIP" surrounded by other interpreter debug output @@ -603,11 +620,8 @@ class PyboardNodeRunner: def run_tests(pyb, tests, args, result_dir, num_threads=1): - test_count = ThreadSafeCounter() testcase_count = ThreadSafeCounter() - passed_count = ThreadSafeCounter() - failed_tests = ThreadSafeCounter([]) - skipped_tests = ThreadSafeCounter([]) + test_results = ThreadSafeCounter([]) skip_tests = set() skip_native = False @@ -864,11 +878,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): test_basename = test_file.replace("..", "_").replace("./", "").replace("/", "_") test_name = os.path.splitext(os.path.basename(test_file))[0] - is_native = ( - test_name.startswith("native_") - or test_name.startswith("viper_") - or args.emit == "native" - ) + is_native = test_name.startswith("native_") or test_name.startswith("viper_") is_endian = test_name.endswith("_endian") is_int_big = test_name.startswith("int_big") or test_name.endswith("_intbig") is_bytearray = test_name.startswith("bytearray") or test_name.endswith("_bytearray") @@ -896,7 +906,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): if skip_it: print("skip ", test_file) - skipped_tests.append(test_name) + test_results.append((test_file, "skip", "")) return # Run the test on the MicroPython target. @@ -911,7 +921,11 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # start-up code (eg boot.py) when preparing to run the next test. pyb.read_until(1, b"raw REPL; CTRL-B to exit\r\n") print("skip ", test_file) - skipped_tests.append(test_name) + test_results.append((test_file, "skip", "")) + return + elif output_mupy == b"SKIP-TOO-LARGE\n": + print("lrge ", test_file) + test_results.append((test_file, "skip", "too large")) return # Look at the output of the test to see if unittest was used. @@ -994,7 +1008,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): # Print test summary, update counters, and save .exp/.out files if needed. if test_passed: print("pass ", test_file, extra_info) - passed_count.increment() + test_results.append((test_file, "pass", "")) rm_f(filename_expected) rm_f(filename_mupy) else: @@ -1006,9 +1020,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): rm_f(filename_expected) # in case left over from previous failed run with open(filename_mupy, "wb") as f: f.write(output_mupy) - failed_tests.append((test_name, test_file)) - - test_count.increment() + test_results.append((test_file, "fail", "")) # Print a note if this looks like it might have been a misfired unittest if not uses_unittest and not test_passed: @@ -1035,17 +1047,49 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): print(line) sys.exit(1) - print( - "{} tests performed ({} individual testcases)".format( - test_count.value, testcase_count.value - ) + # Return test results. + return test_results.value, testcase_count.value + + +# Print a summary of the results and save them to a JSON file. +# Returns True if everything succeeded, False otherwise. +def create_test_report(args, test_results, testcase_count=None): + passed_tests = list(r for r in test_results if r[1] == "pass") + skipped_tests = list(r for r in test_results if r[1] == "skip" and r[2] != "too large") + skipped_tests_too_large = list( + r for r in test_results if r[1] == "skip" and r[2] == "too large" ) - print("{} tests passed".format(passed_count.value)) + failed_tests = list(r for r in test_results if r[1] == "fail") + + num_tests_performed = len(passed_tests) + len(failed_tests) + + testcase_count_info = "" + if testcase_count is not None: + testcase_count_info = " ({} individual testcases)".format(testcase_count) + print("{} tests performed{}".format(num_tests_performed, testcase_count_info)) + + print("{} tests passed".format(len(passed_tests))) - skipped_tests = sorted(skipped_tests.value) if len(skipped_tests) > 0: - print("{} tests skipped: {}".format(len(skipped_tests), " ".join(skipped_tests))) - failed_tests = sorted(failed_tests.value) + print( + "{} tests skipped: {}".format( + len(skipped_tests), " ".join(test[0] for test in skipped_tests) + ) + ) + + if len(skipped_tests_too_large) > 0: + print( + "{} tests skipped because they are too large: {}".format( + len(skipped_tests_too_large), " ".join(test[0] for test in skipped_tests_too_large) + ) + ) + + if len(failed_tests) > 0: + print( + "{} tests failed: {}".format( + len(failed_tests), " ".join(test[0] for test in failed_tests) + ) + ) # Serialize regex added by append_filter. def to_json(obj): @@ -1053,23 +1097,22 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): return obj.pattern return obj - with open(os.path.join(result_dir, RESULTS_FILE), "w") as f: + with open(os.path.join(args.result_dir, RESULTS_FILE), "w") as f: json.dump( - {"args": vars(args), "failed_tests": [test[1] for test in failed_tests]}, + { + # The arguments passed on the command-line. + "args": vars(args), + # A list of all results of the form [(test, result, reason), ...]. + "results": list(test for test in test_results), + # A list of failed tests. This is deprecated, use the "results" above instead. + "failed_tests": [test[0] for test in failed_tests], + }, f, default=to_json, ) - if len(failed_tests) > 0: - print( - "{} tests failed: {}".format( - len(failed_tests), " ".join(test[0] for test in failed_tests) - ) - ) - return False - - # all tests succeeded - return True + # Return True only if all tests succeeded. + return len(failed_tests) == 0 class append_filter(argparse.Action): @@ -1238,7 +1281,7 @@ the last matching regex is used: results_file = os.path.join(args.result_dir, RESULTS_FILE) if os.path.exists(results_file): with open(results_file, "r") as f: - tests = json.load(f)["failed_tests"] + tests = list(test[0] for test in json.load(f)["results"] if test[1] == "fail") else: tests = [] elif len(args.files) == 0: @@ -1316,7 +1359,8 @@ the last matching regex is used: try: os.makedirs(args.result_dir, exist_ok=True) - res = run_tests(pyb, tests, args, args.result_dir, args.jobs) + test_results, testcase_count = run_tests(pyb, tests, args, args.result_dir, args.jobs) + res = create_test_report(args, test_results, testcase_count) finally: if pyb: pyb.close() |