Skip to content
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

riotnode: node abstraction package #10949

Closed

Conversation

cladmi
Copy link
Contributor

@cladmi cladmi commented Feb 5, 2019

Contribution description

This is a start to try providing a common interface that could be used as a test agnostic base that could be used for automating experiments but also, on a shorter term for testing with the current tests, the i2c testing, some framework directions with tests (RobotFramework/pytest), release specs with multiple nodes under test.

Different parallel directions have been taken with different low level wrapper (see refs below). The goal here would be to share a common base where then every usage could benefit of the common developments.

It is currently only a base class without any shell abstraction but wanted to publish early to get feedback and it could also be used in this state. I focused to put the test ecosystem around to help developing.

Usage can be seen in testrunner wrapping and in test_running_error_cases(app_pidfile_env) test.
Current usage in code is

    node = riotnode.node.RIOTNode(application_directory=`optional_dir_if_not_curr_dir`, env=node_specific_env_dict)
    node.start_term(logfile=sys.stdout)  # or use the context manager
    node.term.expect('something')
    node.stop_term()

One important thing I think, is that it implements wrapping for only one node, as it is the only thing we can do in RIOT. Handling two nodes means having two different environments (specific env variables) with two instances of one node.

This pull requests is an aggregate of:

  1. empty python package integration with test suite
  2. an object implementation of a node abstraction. It gives access to the serial port as an expect spawn object. So somehow what testrunner does but with a different interface.
  3. @miri64 pull request dist/pythonlibs: provide unittest TestCase wrapper for testrunner #10431
  4. Usage of this object with previous pull request
  5. Some other changes that could come in

Upcoming work

After different IRL discussions, I am thinking about providing other abstraction classes for shell commands more as a composition than inherited objects (or more let other provide abstractions, as it is the goal to share this between different developers).
And implemented with independent classes that you use alone instead of an aggregated object with everything in the same object.
This could allow a light usage with a specific object, while still allowing to create an aggregated objects if needed but not enforcing it.

class RIOTNodeShellIfconfig():
    RIOTNODE_SHELL_CLASS = RIOTNodeShell
    def __init__(self, node):
        self.node = node
        self.term = self.RIOTNODE_SHELL_CLASS(node)
    def get_default_interface(self):
        ret = self.term.send_command("ifconfig")
        interface = # parse ret to get the correct string
        return int(interface)


node = RIOTNode()
ifconfig_node = RIOTNodeShellIfconfig(node)
interface_num = ifconfig_node.get_default_interface()

This one could still maybe use a common RIOTNodeShellWrapper as base class, it is thing that should be discussed depending on the usages.

Reviewing procedure

I would advise to review commit by commit as they are focusing on different concepts.
Also see if the abstraction could currently work for you.

I still have TODO comments in the code I want to address before merging I would gladly get feedback on them. They are shown when running tox test command.
There is also a TODO.rst for things that were here before in testrunner and could be addressed later.

I was told that node that I took from my work on IoT-LAB was maybe not a good name for this.
I did not choose board as it as the BOARD meaning, you could have two boards being the same BOARD but maybe it is just an issue with me.

Please show me where this could help you or not and if you already have needs around this.
And also, if you have issues with the current implementation.

Testing procedure

Running the test suite can be run with tox (pip install --user tox).

Note: there should now be TODO` warning as I thought about something I want to address before any merging.

See if it can be used a as base for your cases and if you have feedback on the next steps.

Issues/PRs references

Depend on having a terminal without decoration/additional behavior:

My comment on RobotFramework RFC #10241 (comment)

Some example place where it could be used as a base

@cladmi cladmi added State: WIP State: The PR is still work-in-progress and its code is not in its final presentable form yet Area: tests Area: tests and testing framework Discussion: RFC The issue/PR is used as a discussion starting point about the item of the issue/PR State: waiting for other PR State: The PR requires another PR to be merged first Area: tools Area: Supplementary tools Type: tracking The issue tracks and organizes the sub-tasks of a larger effort labels Feb 5, 2019
@miri64
Copy link
Member

miri64 commented Feb 5, 2019

ret = self.term.send_command("ifconfig get_interfaces")

Either you are sending a wrong command here or I don't really understand what you are doing in the background and why (the command is just ifconfig to get all the interfaces ;-)).

@cladmi
Copy link
Contributor Author

cladmi commented Feb 5, 2019

The issue I noted in here for matching stdin is actually a bug in the current implementation I will do a pull request to fix it.
Funny how just writing a simple echo test shows issues.

@cladmi
Copy link
Contributor Author

cladmi commented Feb 5, 2019

ret = self.term.send_command("ifconfig get_interfaces")

Either you are sending a wrong command here or I don't really understand what you are doing in the background and why (the command is just ifconfig to get all the interfaces ;-)).

It was a test to see who will read this, I updated the code :p

@cladmi cladmi mentioned this pull request Feb 5, 2019
2 tasks
@cladmi cladmi requested a review from jcarrano February 11, 2019 17:46
@cladmi cladmi force-pushed the pr/riotnode/riot_node_abstraction_package branch 2 times, most recently from 7649520 to 48b5ce8 Compare February 19, 2019 19:00
@cladmi
Copy link
Contributor Author

cladmi commented Feb 19, 2019

I added tests for process management and started refactoring commits (so rewrote history) to go in a direction where bug fixes could be merged alone and used in the current testrunner.

As long as a name is chosen for the package, some 'utils' functions could be added and used.
I do not like fixing bugs by replacing everything at once so iterating would be easier.

@cladmi
Copy link
Contributor Author

cladmi commented Feb 20, 2019

After one night on this, should the wrapper really take care of a term that cannot clean itself correctly ?

It requires adding psutil dependency (or play with ps output) and doing complex and not guaranteed solutions (if a new process is spawned after the first kill) to detect an issue where the terminal process would not correctly clean its children.

Which means, when a user uses make term and does not do a process group kill, it could leave remaining processes.

I would be maybe in favor of documenting correctly to which signals the TERMPROG should react to cleanup and fix them to work properly.

It is a python directory to start developping the package.
I include all files around for testing and integrating in RIOT.

Test dependencies in tox are already ready for testing with pytest.

* sitecustomize.py: allow implementing 'riotnode' in a subdirectory
* setup.py implement a releaseable package
* requirements.txt: uses 'setup.py' could then be used by our standard
  way of installing
* tox.ini: run the test suite with 'tox'
* setup.cfg/.coveragerc: test suites configuration
@cladmi cladmi force-pushed the pr/riotnode/riot_node_abstraction_package branch from de577c4 to 9ce1f1b Compare March 23, 2019 08:09
The abstraction is a basic class to replicate behavior that was in
`testrunner.

Implementation was sometime directly taken from `testrunner` without
being technically justified to keep backward compatibility. This is
described in `TODO.rst` and dedicated implementation.

This also adds a test `node` implementation with a Makefile.
It is currently an easy to use node as no output is lost and reset
correctly resets.

TODO maybe put this as a comment in the file!!
When pexpect 'close' a 'ptyprocess' it starts by 'SIGHUP'.
It is not handled by default to call 'atexit'.
To not do a specific handling for 'SIGHUP' just forward all signals to
the child. This wrapper should not do any specific things anyway.

TODO Split the 'safe_term_close' to utils with a test not using Make
term and so directly only wanting 'sigkill'
This should allow merging it before and using it in testrunner

TODO settle the `env` handling.
Get feedback on what to do here.

Disable local echo otherwise pexpect matches sent characters.

REMOVE ME: Include fix for RIOT-OS#10952
…nd tests

TODO ADD TEST THAT WE DO NOT RESET IN START TERM
Use 'riotnode' directly in '__init__.py' not with the 'spawn' wrapper.
Allow handling custom pexpect class.
This allows finer analysis of exception as sometime the calling line
is only `expect(variable)` without knowing the value.

testrunner needs to find the correct exception context as the class is
inherited.
@tcschmidt
Copy link
Member

I asked feedback from @adjih and he suggested the alternative name riotdevice instead of riotnode.
Or my funny one riotthing as it is the internet of things :p

On a non-meta level, device relates to a physical board and thing relates to a real-world entity that is represented by an IoT device ... so IMO rather misleading. Node fits better from the perspective of an Internet member ... more generically, agent could be a choice or authority?

@cladmi cladmi requested a review from kb2ma March 24, 2019 15:09
@kb2ma
Copy link
Member

kb2ma commented Mar 24, 2019

Thanks for adding me as a reviewer. Until I get a chance to review, please take a look at my pytest repo for CoAP. In particular see conftest.py for a pexpect host class.

@cladmi
Copy link
Contributor Author

cladmi commented Mar 28, 2019

@kb2ma I somehow try to provide the same, in a riot specific way. So without specifying "make term" as argument because it is "of course" make term. Currently I do not have the "send_recv" as I see it more maybe as an other layer on top.

I like your "nanocoap_server", "gcoap_example" fixtures. "Return me a firmware in a ready state to use". A plan I have for further steps, would be to add a class describing this "nanocoap_server" and the operations you could have on it. Not mandatory as you could still use the "node.term" but giving you other methods that could be useful.

@cladmi
Copy link
Contributor Author

cladmi commented Mar 28, 2019

However, my idea here, would be to give the nanocoap_server object, but not describe it as a fixture.
Describing as a fixture would be in an adaptation layer for the test (it should be almost transparent to implement by saying, wrap this as a) as I would like to keep this non test framework related.

except pexpect.ExceptionPexpect:
# Not sure how to cover this in a test
# 'make term' is not killed by 'term.close()'
self.logger.critical('Could not close make term')
Copy link
Member

Choose a reason for hiding this comment

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

How about

Suggested change
self.logger.critical('Could not close make term')
self.logger.critical('Could not close make term')
finally:
self.term = None

? Thes way one can check (e.g. in a subclass) if the terminal is running or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I may even maybe put a started variable. As currently when starting we wait some time. I will see with the multi nodes handling.
Also, the start could just do nothing if it is already started.

However I did not really see a use case for calling start multiple times. In the current tests, the test function receive a started child. So I assumed to somehow have this initialization done before anything else.

@miri64 miri64 removed the State: waiting for other PR State: The PR requires another PR to be merged first label Apr 16, 2019
@cladmi
Copy link
Contributor Author

cladmi commented Apr 26, 2019

I will look into the usage of the asyncio integration of pexpect.
It would allow using multiple nodes more easily if you want to expect from multiple nodes at the same time to find for example "who received the message" for asynchronous events.

I also found pexpect shell wrapper https://pexpect.readthedocs.io/en/stable/api/replwrap.html It was added in pexpect 4.3 so not the ubuntu bionic version but it could be temporarily extracted if needed or require an upgraded pexpect version.

@cladmi cladmi added this to the Release 2019.10 milestone Jun 14, 2019
@fjmolinas
Copy link
Contributor

I think this is a great contribution that could help improving our test suit. I can see a direct usage in putting in common a lot of the riotboot functions that are used in #11843, #11818 & #11808. It could definitively help with avoiding that much code duplication. Same goes for other WIP branch I have been working on.

@kb2ma I somehow try to provide the same, in a riot specific way. So without specifying "make term" as argument because it is "of course" make term. Currently I do not have the "send_recv" as I see it more maybe as an other layer on top.

I like having a "send_recv" function, I've ended up implementing something like that many times.

I like your "nanocoap_server", "gcoap_example" fixtures. "Return me a firmware in a ready state to use". A plan I have for further steps, would be to add a class describing this "nanocoap_server" and the operations you could have on it. Not mandatory as you could still use the "node.term" but giving you other methods that could be useful.

+1

https://github.com/RIOT-OS/Release-Specs/blob/a1a46649574cc2b7885869789fd924ad672d2915/07-multi-hop/IOTLABHelper.py (but it currently uses serial_aggregator as a base)

An iotlab node extension would be great to have. It would help scripting "manually automatic" (#11954) integrations tests for our release specs, and other features.

I think we should work on integrating this, it will definitively help with having common building blocks.

@fjmolinas
Copy link
Contributor

@cladmi was there a reason why you wanted this in RIOT and not as an external pkg, hosted in RIOT-OS for examples. I would like to make this available, although maybe not yet used since Feature freeze is coming soon.

@cladmi
Copy link
Contributor Author

cladmi commented Jan 3, 2020

@fjmolinas It would be mapped to exactly the exposed interface of RIOT shell so subject to change at any commit. Keeping them in the same repo allows staying in sync without having to merge the tool PR before the code modification.
Also, I am not sure how packages for make test would work in CI, and adding a package for each test may not be the best.

But it is a long term and ecosystem concern, during dev or release you could store it elsewhere indeed.

@fjmolinas
Copy link
Contributor

@miri64 @aabadie Thoughts on this one? Should we postpone again or target integrating as a tool although not used? It could get the ball rolling, it would basically just mean removing the usage in testrunner? I have started to use it to refactor some of the Release-Speces tests, so far it is fitting my use case.

@miri64
Copy link
Member

miri64 commented Jan 6, 2020

I would like to have this in rather sooner than later, even if there are things still to kink out. As you said, this would rather get the ball rolling (also we can go ahead with #11406).

@fjmolinas
Copy link
Contributor

I would like to have this in rather sooner than later, even if there are things still to kink out. As you said, this would rather get the ball rolling (also we can go ahead with #11406).

I would propose going with but without the modifications to testrunner, they can be merged after the release. This also reduces the impact of the change and gives us more time before feature freeze. @aabadie would you agree?


def board(self):
"""Return board type."""
return self.env['BOARD']
Copy link
Contributor

@fjmolinas fjmolinas Feb 4, 2020

Choose a reason for hiding this comment

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

Suggested change
return self.env['BOARD']
return self.env.get('BOARD')

so it doesn't return a key error, or is this on purpose?

@aabadie
Copy link
Contributor

aabadie commented Jun 23, 2020

#13612 was closed because the project moved to https://github.com/riot-os/riotctrl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: tests Area: tests and testing framework Area: tools Area: Supplementary tools Discussion: RFC The issue/PR is used as a discussion starting point about the item of the issue/PR State: WIP State: The PR is still work-in-progress and its code is not in its final presentable form yet Type: tracking The issue tracks and organizes the sub-tasks of a larger effort
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants