Skip to content

mpremote/tests: Rewrite test runner to run correctly under Windows. #17089

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

AJMansfield
Copy link
Contributor

@AJMansfield AJMansfield commented Apr 7, 2025

Summary

This PR rewrites the test runner script used for testing mpremote to run correctly under Windows, and adds some functionality present in the main test runner that it was previously missing.

This PR adds inheritance behavior for the TESTS_DIR and MPREMOTE variables in the run-mpremote-tests.sh test script. It adds a new RESULT_DIR variable that can be specified to direct the runner for a path to place output .exp and .out files. It expands the use of the input arguments to allow specifying multiple test scripts.

Among other things, this rewrite also enables multiple concurrent instances of the test runner to be run against multiple simultaneously-connected target boards, via:

SERIAL_PORT="/dev/serial/by-id/usb-MY_BOARD"
MPREMOTE="./mpremote.py connect ${SERIAL_PORT}" RESULT_DIR="tests/results/MY_BOARD" tests/run-mpremote-tests.sh

Previously, the test runner had no way to override its automatic serial port selection, and concurrent instances had no way to specify different output paths to avoid clobbering each other's .out files.

Testing

I've tested these changes manually and as part of the local automations I've been using to test #16994. I can confirm they address the configurability and concurrency issues described above.

For the moment I'm relying on @Josverl to report on whether this rewrite runs appropriately under Windows implementations of bash.

Trade-Offs and Alternatives

Some attention should probably be paid to adapting this to also run against locally-runnable ports, so that the mpremote tests can be added to automated CI/CD.

Copy link

codecov bot commented Apr 7, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.38%. Comparing base (e993f53) to head (018194d).
⚠️ Report is 62 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #17089      +/-   ##
==========================================
- Coverage   98.41%   98.38%   -0.03%     
==========================================
  Files         171      171              
  Lines       22210    22240      +30     
==========================================
+ Hits        21857    21881      +24     
- Misses        353      359       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

github-actions bot commented Apr 7, 2025

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@Josverl
Copy link
Contributor

Josverl commented Apr 7, 2025

Windows - Git Bash

Some test cases pass :

./test_eval_exec_run.sh: OK
./test_recursive_cp.sh: OK
./test_resume.sh: OK

all others fail

Tests fail when

  • uses {$TMP} or assumes a host path convention

  • comparison of a sha256sum is slightly different with an additional asterix.
    The default mode is to print a line with checksum, a space, a character indicating input mode ('*' for binary, ' ' for text or where binary is insignificant), and name for each FILE`

    can be mitigated with 'sha256sum -t'

  • the ramdisk creation in test_mip_local_install.sh fails while the one in test_filesystem.sh works.
    it is trying to create file in a location where it should not attempt write at all.

       > cp ${TMP}/package :
       ./test_mip_local_install.sh: FAIL
       2,3c2,11
       < mkdir :C:/Program Files/Git/__ramdisk/lib
       < mpremote: mkdir: C:/Program Files/Git/__ramdisk/lib: No such file or directory.
    

    I would suggest ramdisk.py that should not be created on the fly, but be part of the testsuite and referenced from both scripts.

Details

testrub with 'echo -n "" | sha256sum -t' fix ``` EUROPE+josverl@josverl-sb5 MINGW64 /d/mypython/micropython/tools/mpremote/tests (mpremote-testfix) $ ./run-mpremote-tests.sh ./test_eval_exec_run.sh: OK ./test_filesystem.sh: FAIL 13,15c13,15 < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py : < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py :b.py < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py :c.py --- > cp ${TMP}/a.py : &gt; cp ${TMP}/a.py :b.py > cp ${TMP}/a.py :c.py 34,35c34,35 &lt; cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py :aaa &lt; cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py :bbb/b.py --- &gt; cp ${TMP}/a.py :aaa > cp ${TMP}/a.py :bbb/b.py 41c41 &lt; cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py :aaa --- &gt; cp ${TMP}/a.py :aaa 45c45 < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py :aaa/ --- > cp ${TMP}/a.py :aaa/ 47c47 &lt; cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/a.py :aaa/a.py/ --- &gt; cp ${TMP}/a.py :aaa/a.py/ 66d65 < sed: cannot rename ./sedYbcCFs: Invalid cross-device link 69,70c68,69 < 50f0a701dd6cd6125387b96515300c9d5294c006518f8e62fa9eea3b66587f21 < Hello --- > 612c7ddb88390ac86b4174b26a6e5b52fc2f2838b234efd8f6f7c41631a49d04 > Goodbye 73c72 < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/package : --- > cp ${TMP}/package : 86c85 &lt; cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/package :package2 --- &gt; cp ${TMP}/package :package2 100c99 < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/package :test --- > cp ${TMP}/package :test 111c110 &lt; cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/package :test/package2 --- &gt; cp ${TMP}/package :test/package2 123c122 < cp :test/package C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/copy --- > cp :test/package ${TMP}/copy 136c135 &lt; cp :test/package C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/copy/package2 --- &gt; cp :test/package ${TMP}/copy/package2 150c149 < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/package : --- > cp ${TMP}/package : 163c162 &lt; cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/package : --- &gt; cp ${TMP}/package : 179c178 < cp C:/Users/josverl/AppData/Local/Temp/tmp.U2wf3UI3x1/package : --- > cp ${TMP}/package : ./test_mip_local_install.sh: FAIL 2,3c2,11 &lt; mkdir :C:/Program Files/Git/__ramdisk/lib &lt; mpremote: mkdir: C:/Program Files/Git/__ramdisk/lib: No such file or directory. --- &gt; mkdir :/__ramdisk/lib &gt; &gt; ---- Install package &gt; Install ${TMP}/example/package.json > Installing: /__ramdisk/lib/mip_example/__init__.py > Installing: /__ramdisk/lib/mip_example/hello.py > Done > > ---- Test package > Hello, world! ./test_mount.sh: FAIL 4c4 < Local directory C:/Users/josverl/AppData/Local/Temp/tmp.xJ0SnGMaZU is mounted at /remote --- > Local directory ${TMP} is mounted at /remote 6c6 &lt; Local directory C:/Users/josverl/AppData/Local/Temp/tmp.xJ0SnGMaZU is mounted at /remote --- &gt; Local directory ${TMP} is mounted at /remote 10c10 < Local directory C:/Users/josverl/AppData/Local/Temp/tmp.xJ0SnGMaZU is mounted at /remote --- > Local directory ${TMP} is mounted at /remote ./test_recursive_cp.sh: OK ./test_resume.sh: OK ```

@dpgeorge dpgeorge added the tools Relates to tools/ directory in source, or other tooling label Apr 8, 2025
@AJMansfield
Copy link
Contributor Author

AJMansfield commented Apr 8, 2025

@Josverl could you test the latest push? I've now majorly rewritten most of the test runner script with far more paranoid/maximalist quoting, and updated it to use a separate results directory to match the behavior of the main test suite (with a .gitignore line matching the way the main test suite gets its results ignored).

This also reduces some of the jank by inverting the way the tests are compared -- that is, instead of trying to use sed to replace instances of the temporary directory path with the string ${TMP} in the .out file, it instead uses envsubst to generate a copy of the .exp file with TMP expanded.

Also updated, it uses a diff compare mode that ignores carriage returns, and outputs the failure diffs at the end in unified-diff format the same way the main test suite does.

I've also fixed the tests that were individually broken due to insufficient quoting, and added --text to the sha256sum invocations in test_filesystem.sh to make them more windows compatible.

@AJMansfield AJMansfield changed the title mpremote: Fix test suite for multiple-board situations. mpremote/tests: Rewrite test runner to run correctly under Windows. Apr 8, 2025
@Josverl
Copy link
Contributor

Josverl commented Apr 8, 2025

initial results : recursive now also FAILS

./test_eval_exec_run.sh: OK
./test_filesystem.sh: FAIL
./test_mip_local_install.sh: CRASH
./test_mount.sh: FAIL
./test_recursive_cp.sh: FAIL
./test_resume.sh: OK

common issues :
There is a common patter to setup a ramdisk. This should be a common fixture.
Instead it is setup in multiple slightly different ways , and only one (the filesystem test ) works on Windows.

I think the bash syntax: ${target@Q} does not work or work the same. Simpler to just use the correct delimiters instead.

I think the diffing on errors would be better to understand when $DIFF "${result}.exp" "${result}.out"
That shows the extra's as + and the missing lines as - , which makes more sense to my simple Dutch brain.

main problems in filesystem test

  • Windows path <> Posix Path
  • the "editor" test via sed fails / stops the entire script

Details : https://gist.github.com/Josverl/32de060bfa072f19ea55ad326aca6403

🪲While debugging I found this bug 🦗
Git bash + mpremote result in a transport error on mpremote resume mkdir /ramdisk/lib ( no leading :)
#17093

The same command works just fine in Windows powershell or even an old style command prompt.
soo, just testing on Git bash - does not appear to cover enough ( id say that about 2% of windows users use git bash for mpremote)
Even with a perfect bash script , that would cover only about 20% of the end user computing scenarios.

Personally I think that test are best written using a test framework, such as pytest.
And that can handle .exp files, and has much more flexibility and parametrization and fuzzing capabilities, code coverage etc etc that will take so much more time to try to accomplish using bash, or any other shell language for that matter.

https://github.com/Josverl/micropython/blob/mpr/pytest/tools/mpremote/tests/test_filesystem.py

@AJMansfield
Copy link
Contributor Author

AJMansfield commented Apr 8, 2025

I really need to spend some more time getting my Windows build environment working so we're not just playing telephone on this.

There is a common patter to setup a ramdisk. This should be a common fixture. Instead it is setup in multiple slightly different ways , and only one (the filesystem test ) works on Windows.

I'll try to see about refactoring this.

I think the bash syntax: ${target@Q} does not work or work the same. Simpler to just use the correct delimiters instead.

You're right that that that doesn't address all possible escaping problems, but there is one remaining issue I also just spotted even with that, in that it's actually the missing r prefix to get python to not interpret backslashes as escapes.

I think the diffing on errors would be better to understand when $DIFF "${result}.exp" "${result}.out" That shows the extra's as + and the missing lines as - , which makes more sense to my simple Dutch brain.

Good catch, the other test runners also diff their .exp and .out in the order you suggest, so that'll make it more consistent.

🪲While debugging I found this bug 🦗 Git bash + mpremote result in a transport error on mpremote resume mkdir /ramdisk/lib ( no leading :) #17093

Could you run that command with set -x enabled? That'll print a trace as commands run that includes the final interpolated value of everything.

just testing on Git bash - does not appear to cover enough ( id say that about 2% of windows users use git bash for mpremote) Even with a perfect bash script , that would cover only about 20% of the end user computing scenarios.

Agreed, this should also at least support .ps1 test cases, and probably .cmd test cases too.
This might also make it easier to test windows-y behaviors, too, since there's a port of powershell for linux it could use.

Personally I think that test are best written using a test framework, such as pytest. And that can handle .exp files, and has much more flexibility and parametrization and fuzzing capabilities, code coverage etc etc that will take so much more time to try to accomplish using bash, or any other shell language for that matter.

Almost definitely. There's a saying that, if you're using arrays in bash, it's time to rewrite it in a different language... and the rewrite technically does, even if just barely.

As a shell tool, it makes sense to have the test case files for mpremote in shell languages, but I do think it'll be useful to rewrite the test runner in python so it can reuse and integrate with parts of the other test runners.

I want to get to a working 'good' config first before refactoring further, though -- it's not just the runner that's broken, many of the actual tests are too.

@Josverl
Copy link
Contributor

Josverl commented Jul 26, 2025

Is this ready for another test run?

@AJMansfield
Copy link
Contributor Author

Is this ready for another test run?

Not really, no; just rebased this in order to more easily experiment with #12802 --- since its patch theoretically enables raw repl mode on the unix port, and might make it possible to run the mpremote tests as part of CI.

@AJMansfield AJMansfield force-pushed the mpremote-testfix branch 2 times, most recently from 7cf5415 to 545b256 Compare July 26, 2025 23:48
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
@AJMansfield AJMansfield force-pushed the mpremote-testfix branch 3 times, most recently from 05abd10 to c18a821 Compare July 27, 2025 00:00
@AJMansfield
Copy link
Contributor Author

AJMansfield commented Jul 27, 2025

@Josverl Just rewrote the test driver to use more-or-less the same test code as the main suite's run-tests.py.

I've tested it against my own boards and can confirm it works correctly on unix; and since it's now just doing exactly what the main test suite does it should be compatible with windows too. Theoretically, hopefully you can confirm this.

It now also looks for and tries to run test_*.bat and test_*.ps1 scripts in addition to test_*.sh scripts, though I've not been able to test that just yet.

Still need to test the merge with #12802 or #16141 to confirm that the micropython socket server it sets up actually works though, and add the relevant CI jobs for it.

On a separate note, does the Windows port of micropython support raw repl mode? If it's possible to add an mpremote CI job for windows that'd be pretty useful to have too, given that it's been one of the tool's main pain points.

@Josverl
Copy link
Contributor

Josverl commented Jul 27, 2025

Thanks ,
Ill give it a go later today or tomorrow. I'm away from home but have a few boards to test.

does the Windows port of micropython support raw repl mode?

I do not know

Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
@AJMansfield AJMansfield force-pushed the mpremote-testfix branch 3 times, most recently from 33af3c0 to 7fa5d64 Compare July 27, 2025 20:55
test_extensions = ("test_*.sh",)
if sys.platform == "nt":
test_extensions += (
"test_*.bat",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not bother with .bat, that is something from the previous century. if you insist on legendary support use .cmd, on Windows prefer .ps1 over anything older.

Copy link
Contributor Author

@AJMansfield AJMansfield Jul 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!

Also, just added a powershell test!

Not proud of it though, but it's the only thing I could even get to work in the version of powershell I have on my machine:

#!/snap/bin/pwsh -File
iex "/usr/bin/python $Env:MPREMOTE eval 1+1"

I've no idea how to avoid that very non-portable /usr/bin/python part; but without it, the test just complains /usr/bin/env: 'python3': No such file or directory about the shebang in mpremote.py. Though editing mpremote.py to just use #!python as its own shebang does manage to work...

""",
)
cmd_parser.add_argument(
"-t", "--test-instance", default="unix", help="the MicroPython instance to test"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the micropython executable ? ( micropython/micropython.exe)
what other values can be used / default to current platform ?
How is this used in the mpremote tests ?

Copy link
Contributor Author

@AJMansfield AJMansfield Jul 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works almost the same way as running the test harness via run-tests.py, with the only difference being that the value is passed through for mpremote to interpret, rather than being interpreted by the pyboard functions used in the other test runners.

i.e. run-mpremote-tests.py -t whatever results in running each test script with the MPREMOTE environment variable set to tools/mpremote/mpremote.py connect whatever instead of just tools/mpremote/mpremote.py, turning the $MPREMOTE test_command invocations into tools/mpremote/mpremote.py connect whatever test_command.

The value "unix" is special, and causes the test script to instead launch a separate micropython process for each test using socat to open a pty or server socket, and then pass that pty path or socket address to mpremote instead. MPREMOTE becomes tools/mpremote/mpremote.py connect socket://127.0.0.1:12345 or tools/mpremote/mpremote.py connect /tmp/mpremote.AAAAAAA/pty. (The micropython binary to launch in this case is taken from the MICROPY_MICROPYTHON environment variable --- defaulting to unix-standard if that's missing, consistent with the other test runners.)

A blank or whitespace-only value results in omitting this connect <target> part entirely, and just include the mpremote.py path, which IIRC is equivalent to passing auto.

Replacing the original bash script with a test script derived from
the main suite's run-tests.py.

Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
@Josverl
Copy link
Contributor

Josverl commented Jul 28, 2025

I spend some effort to try to port a single test to windows

  • need to copy / change the .sh test
  • extract common micropython.py test fixtures to reduce duplication
  • create a new some_test.ps1.exp file
    further
  • add an alternative to socat / PTY to the test script, while there is a ConPTY on windows 10+ , however you end up in WSL2 , which is not what we need at all.

I wonder if this is the correct direction.

@AJMansfield
Copy link
Contributor Author

  • add an alternative to socat / PTY to the test script, while there is a ConPTY on windows 10+ , however you end up in WSL2 , which is not what we need at all.

Tbh that'd probably be fine --- mpremote is the code under test here, not really the rest of micropython; only mpremote itself needs to run within windows here. For now, test it against hardware with -t COM<x> or -t auto or -t ' ' --- but I'm already considering replacing socat with docker or qemu or something, just to isolate micropython's filesystem.

@Josverl
Copy link
Contributor

Josverl commented Jul 29, 2025

Mpflash just runs Mpremote in a sub process with some minor threading and tenacity to handle time outs and retries. Pure python, and Works reliable across platforms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tools Relates to tools/ directory in source, or other tooling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy