labgrid icon indicating copy to clipboard operation
labgrid copied to clipboard

Add support for running process asynchronously

Open JoshuaWatt opened this issue 4 years ago • 24 comments

Description Adds support for running processes asynchronously with the main test code. Attempts to incorporate the feed back from #552

Checklist

  • [ ] Documentation for the feature
  • [x] Tests for the feature
  • [ ] The arguments and description in doc/configuration.rst have been updated
  • [ ] Add a section on how to use the feature to doc/usage.rst
  • [ ] Add a section on how to use the feature to doc/development.rst
  • [ ] CHANGES.rst has been updated
  • [x] PR has been tested
  • [ ] Man pages have been regenerated

JoshuaWatt avatar Sep 16 '21 21:09 JoshuaWatt

Codecov Report

Merging #835 (58d847d) into master (bd0ede7) will increase coverage by 0.5%. The diff coverage is 73.3%.

Impacted file tree graph

@@           Coverage Diff            @@
##           master    #835     +/-   ##
========================================
+ Coverage    56.9%   57.4%   +0.5%     
========================================
  Files         145     146      +1     
  Lines       10859   11102    +243     
========================================
+ Hits         6180    6374    +194     
- Misses       4679    4728     +49     
Impacted Files Coverage Δ
labgrid/util/timeout.py 100.0% <ø> (ø)
labgrid/util/expect.py 73.9% <28.5%> (-19.9%) :arrow_down:
labgrid/driver/bareboxdriver.py 60.3% <32.0%> (-2.1%) :arrow_down:
labgrid/driver/ubootdriver.py 60.7% <36.0%> (-0.3%) :arrow_down:
labgrid/driver/shelldriver.py 29.1% <37.5%> (+0.8%) :arrow_up:
labgrid/util/marker.py 83.3% <82.6%> (-16.7%) :arrow_down:
labgrid/driver/sshdriver.py 63.5% <90.3%> (+5.2%) :arrow_up:
labgrid/exceptions.py 100.0% <100.0%> (ø)
labgrid/protocol/__init__.py 100.0% <100.0%> (ø)
labgrid/protocol/commandprotocol.py 100.0% <100.0%> (ø)
... and 4 more

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update bd0ede7...58d847d. Read the comment docs.

codecov[bot] avatar Sep 16 '21 21:09 codecov[bot]

Overall this design looks like what we had in mind in #552. Thank you very much for the implementation, looking forward to testing this on my lab at home.

Emantor avatar Sep 17 '21 07:09 Emantor

It is unfortunately a little hard to write automated tests for most of this since it requires actual hardware interaction, but I'll take a stab at it. I was able to test on actual hardware (Raspberry Pi 4 w/ u-boot) with the following test suite:

import socket
import os
import time

def test_uboot_async(uboot_command):
    with uboot_command.popen("echo hello") as p:
        assert p.read(4).decode("utf-8").splitlines() == ["hell"]
        assert p.read().decode("utf-8").splitlines() == ["o"]

    with uboot_command.popen("echo bzap hello bzap") as p:
        index, before, match, after = p.expect("hello")
        assert index == 0
        assert match.group(0).decode("utf-8") == "hello"
        assert before.decode("utf-8") == "bzap "
        assert after.decode("utf-8") == "hello"
        assert p.read().decode("utf-8").splitlines() == [" bzap"]

def test_ssh_async(ssh_command):
    with ssh_command.popen("echo hello") as p:
        assert p.read(4).decode("utf-8").splitlines() == ["hell"]
        assert p.read().decode("utf-8").splitlines() == ["o"]

    with ssh_command.popen("echo bzap hello bzap") as p:
        index, before, match, after = p.expect("hello")
        assert index == 0
        assert match.group(0).decode("utf-8") == "hello"
        assert before.decode("utf-8") == "bzap "
        assert after.decode("utf-8") == "hello"
        assert p.read().decode("utf-8").splitlines() == [" bzap"]

def test_shell_nc(shell_command, ssh_command):
    with shell_command.popen("nc -l -p 5555") as p:
        # Wait for netcat to start listening
        time.sleep(2)
        with ssh_command.forward_local_port(5555) as local_port:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.connect(("localhost", local_port))
                s.sendall(b"Hello World")
            p.expect(b"Hello World")
            time.sleep(2)

def test_ssh_nc(ssh_command):
    with ssh_command.popen("nc -l -p 5555 127.0.0.1") as p:
        # Wait for netcat to start listening
        time.sleep(2)
        with ssh_command.forward_local_port(5555) as local_port:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.connect(("localhost", local_port))
                s.sendall(b"Hello World")
            p.expect(b"Hello World")
            time.sleep(2)


def test_shell_async(shell_command):
    with shell_command.popen("echo hello") as p:
        assert p.read(4).decode("utf-8").splitlines() == ["hell"]
        assert p.read().decode("utf-8").splitlines() == ["o"]

    with shell_command.popen("echo bzap hello bzap") as p:
        index, before, match, after = p.expect("hello")
        assert index == 0
        assert match.group(0).decode("utf-8") == "hello"
        assert before.decode("utf-8") == "bzap "
        assert after.decode("utf-8") == "hello"
        assert p.read().decode("utf-8").splitlines() == [" bzap"]

def test_shell_interrupt(shell_command):
    with shell_command.popen("yes") as p:
        l = p.read(1024).decode("utf-8").splitlines()
        assert l == ["y"] * len(l)

    shell_command.run_check("true")

Note that I wasn't able to test the barebox driver, so manual testing there would be appreciated

JoshuaWatt avatar Sep 17 '21 12:09 JoshuaWatt

I added some tests, which revealed a few bugs that needed fixed. I think it's all good to go now though (although I still have no way to test barebox)

JoshuaWatt avatar Sep 17 '21 19:09 JoshuaWatt

Great work! I found some trivial things. I'll have to dive deeper into this later.

This should be mentioned in CHANGES.rst and probably needs some documentation elsewhere?

I added a little to CHANGES.rst; I can add more documentation but I'm not really sure where it should go.

JoshuaWatt avatar Sep 20 '21 13:09 JoshuaWatt

I've tested this briefly now.

Issues I stumbled upon:

* `BareboxDriver` now times out on `run()`

I don't have any way of testing barebox, can you provide more information so I can fix it?

* running multiple processes on `BareboxDriver`/`ShellDriver` is not forbidden, but runs in timeouts

Do we want to explicitly disallow that? I'm fine with that if that's what everyone wants to do.

* calling `stop()` on `ConsoleMarkerProcess` returned by `ShellDriver.popen()` leads to a timeout (e.g. stopping a "sleep 100") - the implementation looks incomplete, I don't see the prompt being detected on `stop()`

Open questions:

* why is `SSHDriver.run()` not using `popen()`?

It would be nice, but I can't do that without breaking the SSH driver because of the stderr_merge option, which is not possible to implement with popen()

* I do not really understand the difference between `read_nonblocking()`/`read()`

read_nonblocking allows a CommandProcessProcotol to be directly used as the backend for a popen.spawn object, since the latter requires the the read_nonblocking function to be implemented. read is the API for normal folks :)

Styling:

* we use f-strings where appropriate since #838, please do the same

I tried to use f-strings everywhere.... IHMO it's confusing to developers to require different string formatting for different uses (unless there is some really compelling performance reason to do otherwise).

Note: this is no comprehensive review yet, just a collection of things I saw while testing with a QEMU and a physical target.

JoshuaWatt avatar Sep 28 '21 20:09 JoshuaWatt

  • BareboxDriver now times out on run()

I don't have any way of testing barebox, can you provide more information so I can fix it?

Here is my demo script:

#!/usr/bin/env python3

import labgrid

labgrid.StepReporter()

env = labgrid.Environment("beaglebone.yaml")
target = env.get_target()

barebox = target.get_driver("BareboxDriver")
target.activate(barebox)

barebox.run("echo hi")

This results in: labgrid-barebox-run.log

I have not looked into it yet.

Bastian-Krause avatar Sep 28 '21 21:09 Bastian-Krause

I think I've addressed most of the comment, is there anything else needed to merge this?

JoshuaWatt avatar Oct 18 '21 14:10 JoshuaWatt

@JoshuaWatt Thanks, re-requesting review or a short notice like yours is always welcome, so we know when to look at PRs again. I need to look at/test the current state again. I'll come back to you.

Bastian-Krause avatar Oct 19 '21 08:10 Bastian-Krause

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass

This ends up with a TIMEOUT exception.

Another thing: how would you read the output of a long-running command (e.g. find /) asynchronously? Could you provide an example? I tried something with while p.poll() is None, but the poll() method blocks for a second if the process is still alive, so that feels wrong.

Bastian-Krause avatar Oct 28 '21 10:10 Bastian-Krause

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass

This ends up with a TIMEOUT exception.

Ah, this has nothing to do with the long-running command. The context manager hits CTRL-c on close, which prevents the second marker from being echoed (on the shell, not in barebox though). That's why we run into the timeout there.

Bastian-Krause avatar Oct 28 '21 11:10 Bastian-Krause

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass

This ends up with a TIMEOUT exception.

Another thing: how would you read the output of a long-running command (e.g. find /) asynchronously? Could you provide an example? I tried something with while p.poll() is None, but the poll() method blocks for a second if the process is still alive, so that feels wrong.

poll is for checking if the process is still alive; to read output use p.read() or p.expect()

JoshuaWatt avatar Nov 02 '21 17:11 JoshuaWatt

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass

This ends up with a TIMEOUT exception.

Ah, this has nothing to do with the long-running command. The context manager hits CTRL-c on close, which prevents the second marker from being echoed (on the shell, not in barebox though). That's why we run into the timeout there.

Does something need to change? It seems like it's doing what you are asking for (launching the process, then immediately terminating it). If you want to wait for it to exit normally, use p.poll() in a loop, or p.wait()

JoshuaWatt avatar Nov 02 '21 18:11 JoshuaWatt

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass

This ends up with a TIMEOUT exception. Another thing: how would you read the output of a long-running command (e.g. find /) asynchronously? Could you provide an example? I tried something with while p.poll() is None, but the poll() method blocks for a second if the process is still alive, so that feels wrong.

poll is for checking if the process is still alive; to read output use p.read() or p.expect()

Okay, will try that.

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass
  
This ends up with a `TIMEOUT` exception.

Ah, this has nothing to do with the long-running command. The context manager hits CTRL-c on close, which prevents the second marker from being echoed (on the shell, not in barebox though). That's why we run into the timeout there.

Does something need to change? It seems like it's doing what you are asking for (launching the process, then immediately terminating it). If you want to wait for it to exit normally, use p.poll() in a loop, or p.wait()

I think the behavior between barebox and shell should be consistent. I would not expect to get a TIMEOUT exception if I do not wait for the process to finish. Do you agree?

Bastian-Krause avatar Nov 03 '21 11:11 Bastian-Krause

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass

This ends up with a TIMEOUT exception. Another thing: how would you read the output of a long-running command (e.g. find /) asynchronously? Could you provide an example? I tried something with while p.poll() is None, but the poll() method blocks for a second if the process is still alive, so that feels wrong.

poll is for checking if the process is still alive; to read output use p.read() or p.expect()

Okay, will try that.

How would you run a command that takes longer than 30 seconds?

def test_async_output(strategy):
    strategy.transition("shell")
    with strategy.shell.popen("sleep 40") as p:
        pass
  
This ends up with a `TIMEOUT` exception.

Ah, this has nothing to do with the long-running command. The context manager hits CTRL-c on close, which prevents the second marker from being echoed (on the shell, not in barebox though). That's why we run into the timeout there.

Does something need to change? It seems like it's doing what you are asking for (launching the process, then immediately terminating it). If you want to wait for it to exit normally, use p.poll() in a loop, or p.wait()

I think the behavior between barebox and shell should be consistent. I would not expect to get a TIMEOUT exception if I do not wait for the process to finish. Do you agree?

Ya, that makes sense; let me see what I can do

JoshuaWatt avatar Nov 03 '21 13:11 JoshuaWatt

@Bastian-Krause Ok, I tracked this down to a behavior difference is bash (which I was using) vs. busybox. I've corrected it and now it should work the same for both shells

JoshuaWatt avatar Nov 03 '21 14:11 JoshuaWatt

For those interested, the difference is:

bash:

raspberrypi4-64:~# echo -n 'ABC'; sleep 10; echo 'ABC' $?; 
ABC^C
ABC 130
raspberrypi4-64:~#

busybox:

raspberrypi4-64:~# echo -n 'ABC'; sleep 10; echo 'ABC' $?;
ABC^C
raspberrypi4-64:~#

Note that interrupting the command on busybox prevents all subsequent commands (the echo with the exit code) from running, where as they still run under bash.

JoshuaWatt avatar Nov 03 '21 14:11 JoshuaWatt

Ping

JoshuaWatt avatar Nov 15 '21 15:11 JoshuaWatt

Ping

JoshuaWatt avatar Dec 06 '21 15:12 JoshuaWatt

Ping

JoshuaWatt avatar Jan 04 '22 19:01 JoshuaWatt

Ping

JoshuaWatt avatar Jan 19 '22 18:01 JoshuaWatt

Hi,

I deployed this internally and let it run over our internal test suites and didn't find an error related to this PR. Thats great, thanks a lot for the work! The issue I see at the moment is that the verbose pytest output becomes unreadable:

10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'y'>, b'y')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b's'>, b's')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b't'>, b't')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'e'>, b'e')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'm'>, b'm')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'/'>, b'/')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b's'>, b's')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'y'>, b'y')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b's'>, b's')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b't'>, b't')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'e'>, b'e')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'm'>, b'm')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'd'>, b'd')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'-'>, b'-')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'a'>, b'a')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'u'>, b'u')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}

Since we match on a single byte a lot of the steps don't contain any meaningful information. I am not sure how to solve this, one way would be to create a helper which does not contain the step decorator to supress this output. Note that expect() is still called by drivers and I wouldn't want to get rid of this as is.

Emantor avatar Feb 11 '22 14:02 Emantor

Hi,

I deployed this internally and let it run over our internal test suites and didn't find an error related to this PR. Thats great, thanks a lot for the work! The issue I see at the moment is that the verbose pytest output becomes unreadable:

10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'y'>, b'y')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b's'>, b's')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b't'>, b't')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'e'>, b'e')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'm'>, b'm')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'/'>, b'/')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b's'>, b's')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'y'>, b'y')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b's'>, b's')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b't'>, b't')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'e'>, b'e')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'm'>, b'm')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'd'>, b'd')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'-'>, b'-')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'a'>, b'a')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}
10:13:34      SerialDriver.expect state='stop' result=(0, b'', <re.Match object; span=(0, 1), match=b'u'>, b'u')
10:13:34      SerialDriver.expect state='start' args={'pattern': [re.compile(b'^(?!WASPAEMYUO)(?!WASPAEMYU$)(?!WASPAEMY$)(?!WASPAEM$)(?!WASPAE$)(?!WASPA$)(?!WASP$)(?!WAS$)(?!WA$)(?!W$).', re.DOTALL), re.compile(b'^WASPAEMYUO\\s+(\\d+)\\s+.*root@[^:]+:[^ ]+'), <class 'pexpect.exceptions.TIMEOUT'>]}

Since we match on a single byte a lot of the steps don't contain any meaningful information. I am not sure how to solve this, one way would be to create a helper which does not contain the step decorator to supress this output. Note that expect() is still called by drivers and I wouldn't want to get rid of this as is.

Yes, I noticed that; do you have any suggestions for how to make it more reasonable?

JoshuaWatt avatar Feb 11 '22 20:02 JoshuaWatt

Yes, I noticed that; do you have any suggestions for how to make it more reasonable?

Hah, you did give a suggestion! My fault for not reading thoroughly. I'll look into doing that.

JoshuaWatt avatar Feb 18 '22 14:02 JoshuaWatt