MicroPythonのテストスイートを使ってみた

RPiベアメタル版MicroPythonもだいぶ機能追加が進んできたので、そろそろ自動テストが欲しくなってきました。そこで、手始めにMicroPython付属のテストスイートを調べてみました。

MicroPythonのテストスクリプトは、tests/の下にあります。
大量のスクリプトがありますが、それぞれテストを行うPythonスクリプト(.py)と、その実行結果として期待される(expected)出力のファイル(.py.exp)のセットになっています。
expファイルでは正規表現を使うことができ、「整数値の出力」といった要件を表現することもできます。(例えばtests/micropython/meminfo.py.exp)
expファイルが無いテストスクリプトでは、正解はCPythonでの実行結果になります。なお、正解を

./run-tests --write-exp

でexpファイルを書き出すことができます。

実際のテストは、MicroPythonが動作しているデバイスへシリアルポート等でスクリプトを転送し、その結果を読み出してexpファイルと比較することで行われます。
ちなみにこの操作はRAW REPLを使っています。RAW REPLは通常のREPLと異なり、入力した文字のエコーバックなどの(人間にとって)親切な仕組みはなく、MicroPythonの処理系と直接接続します。

沢山あるテストスクリプトを黙々と転送しては結果を正解と比較する仕事は、tests/run-testsスクリプトが行います。
例えば

./run-tests --target unix -d basics

でports/unix/micropythonに対してtests/basicsに格納されているテストを実行します。

MicroPythonの実装内容はデバイスごとに異なりますが、現時点でテストスイートのターゲットデバイスとしてサポートされているのは「’unix’, ‘pyboard’, ‘wipy’, ‘esp8266’, ‘esp32’, ‘minimal’」の各portです。
‘unix’以外のプラットフォームでは、デバイスとのやりとりの部分はtests/pyboard.pyに実装されています。

デフォルトで実行されるテストもプラットフォームごとに異なっており、esp8266/esp32/minimalでは「’basics’, ‘micropython’, ‘float’, ‘misc’, ‘extmod’」となっています。

ベアメタルRaspberry Pi版MicroPythonでは、以下のようにターゲットをminimalに指定してみたところ、とりあえず実行することができました。(Raspberry Piのシリアルポートは/dev/ttyUSB0に接続されている想定です。)

./run-tests --target minimal --device /dev/ttyUSB0 -b 115200  -d basics
pass  basics/0prelim.py
pass  basics/andor.py
pass  basics/array1.py
pass  basics/array_add.py
pass  basics/array_construct.py
pass  basics/array_construct2.py
skip  basics/array_construct_endian.py
pass  basics/array_intbig.py
pass  basics/array_micropython.py
pass  basics/assign1.py
(以下略)

動かしてみたら、basicsだけでもいろいろと動かないものが見つかりました。
失敗したテストの出力結果は「スクリプト名.out」でexpファイルと共にカレントディレクトリに出力されます。

単にmpconfigport.hの中で有効にしていないオプションの影響もありましたが、

・enumerateを正常に動作させるにはMICROPY_CPYTHON_COMPATを有効にする
・MICROPY_BEGIN_ATOMIC_SECTION()/MICROPY_END_ATOMIC_SECTION(state) をARMv6のIRQのアーキテクチャに合わせて定義する

など、今回始めて見つかったバグもありました。

basics/の中のテストは現在455個ありますが、pass/fail以前に、まだ実行するとクラッシュしてしまうテストもありますので、地道にバグをつぶして行きたいと思います。

411 tests performed (12343 individual testcases)
404 tests passed
44 tests skipped: async_await async_await2 async_def async_for async_for2 async_with async_with2 async_with_break async_with_return builtin_compile builtin_override builtin_pow3 builtin_pow3_intbig builtin_range_binop builtin_round_int builtin_round_intbig bytearray_slice_assign bytes_partition class_delattr_setattr class_descriptor class_inplace_op class_notimpl class_reverse_op deque1 deque2 dict_fixed errno1 exception_chain fun_name generator_name io_buffered_writer namedtuple_asdict ordereddict1 ordereddict_eq parser slice_attrs special_methods2 string_center string_partition string_rpartition string_splitlines subclass_native_call subclass_native_init sys_getsizeof
7 tests failed: gc1 io_bytesio_ext2 io_iobase io_write_ext memoryerror memoryview1 python36

コメント