Test completeness feature for Pytests#1927
Conversation
| topology: specify which topology testcase can be executed on: (t0, t1, ptf, etc) | ||
| platform: specify which platform testcase can be executed on: (physical, virtual, broadcom, mellanox, etc) | ||
| completeness_level: specify test completeness (coverage) level (Debug, Basic, Confident, Thorough) | ||
| supported_completeness_level: test supported levels of completeness (coverage) level (Debug, Basic, Confident, Thorough) |
There was a problem hiding this comment.
I don't feel necessary for test to declare supported levels.
There was a problem hiding this comment.
Declared supported levels can be used for - 1) Global normalization. 2) If the test needs to supersede the module level of completeness.
There was a problem hiding this comment.
I still don't follow this either. Do you have an example?
There was a problem hiding this comment.
For example, if a testcase has following supported markers:
pytestmark = [
pytest.mark.supported_completeness_level(CompletenessLevel.basic, CompletenessLevel.thorough)
]
If this testcase is executed with CLI argument --completeness_level=confident, normalization is needed to decide what level test should be run at.
In this case, the normalization logic pulls all the supported levels from the testcase during pytest_runtest_setup, and decides the normalized value should be basic.
The testcase will finally be run in basic completeness level.
tests/test_test_completeness.py
Outdated
| defined_levels = [mark.args for mark in request.node.iter_markers(name="completeness_level")] | ||
| logger.info("Completeness level set: {}".format(str(defined_levels))) | ||
|
|
||
| @pytest.mark.supported_completeness_level(CompletenessLevel.Debug) |
There was a problem hiding this comment.
I don't think this should be a marker.
By making it a maker, when a completeness level is specified. Test would be selected or deselected. This is not what we want. We want to have this as an argument so that the test could change behavior dynamically.
There was a problem hiding this comment.
As per the discussion - markers seems to be an acceptable method to implement test-completeness.
Marker will not select or deselect the testcase necessarily - selecting/deselecting depends on whether pytest.skip is used.
In the proposed approach, markers enables the testcases to run at different completeness-levels. Global logic to normalize the level if the specified and defined levels do not match will be handled at the test setup level. This prevents duplication of normalization logic in every testcase. Also, after the completeness level is normalized, the testcase is free to decide what to do/execute at different levels of completeness.
| help="only run tests matching the device DEV_TYPE ('physical', 'vs')") | ||
| parser.addoption("--completeness_level", metavar="TEST_LEVEL", action="store", type=int, \ | ||
| help="Coverage level of test - partial to full execution.\n Defined levels: \ | ||
| Debug = 0, Basic = 1, Confident = 2, Thorough = 3") |
There was a problem hiding this comment.
Define both name and value are confusing, please stick with named levels.
There was a problem hiding this comment.
Agreed, it will be much better if just named levels are used. Fixed this.
|
can we have a real example? |
| "markers", "device_type(DEV_TYPE): mark test to specify the need for a physical dut or vs only test. allowed values: 'physical', 'vs'" | ||
| ) | ||
| config.addinivalue_line( | ||
| "markers", "device_type(TEST_LEVEL): mark test to specify the completeness level for the test. Allowed values: 'Debug', 'Basic' ,'Confident', 'Thorough'" |
There was a problem hiding this comment.
device_type(TEST_LEVEL) -> test_level(TEST_LEVEL)
There was a problem hiding this comment.
Fixed now. Thanks.
@lguohan - Sure, completeness level usage is now incorporated in |
| 4. Specified level matches one of the defined levels | ||
| ''' | ||
| specified_level = item.config.getoption("--completeness_level") | ||
| if not specified_level: # Case 1 |
There was a problem hiding this comment.
This method is a little long and there's a bunch of different cases to cover. What I would suggest here is to have helper methods for the different cases, so something like:
set_defaultto handle cases 1 & 2normalize_levelsto handle case 3
And then once those two are covered you can just set the completeness level at the end and return. The comments are definitely helpful for following what is going on, but it's even easier/quicker to follow if you can just look at the method calls and figure out what each step is doing. :)
There was a problem hiding this comment.
Agreed and added the methods to make it more easy to follow. Thanks.
| topology: specify which topology testcase can be executed on: (t0, t1, ptf, etc) | ||
| platform: specify which platform testcase can be executed on: (physical, virtual, broadcom, mellanox, etc) | ||
| completeness_level: specify test completeness (coverage) level (Debug, Basic, Confident, Thorough) | ||
| supported_completeness_level: test supported levels of completeness (coverage) level (Debug, Basic, Confident, Thorough) |
There was a problem hiding this comment.
I still don't follow this either. Do you have an example?
| for fanout, fanout_port in self.ports_shutdown_by_test: | ||
| logging.debug("Restoring fanout switch {} port {} shut down by test".format(fanout.hostname, fanout_port)) | ||
| fanout.no_shutdown(fanout_port) | ||
| normalized_level = [mark.args for mark in request.node.iter_markers(name="completeness_level")][0] |
There was a problem hiding this comment.
I'm not sure that I understand why the mark is necessary if we're just fetching it from the request and checking it in the method. How is this different than having a global normalized_level variable that we can reference or making it a field of the testbed fixture or something similar?
There was a problem hiding this comment.
There might be a scope of improvement and simplification.
To explain the machinery - there are two markers being used here:
supported_completeness_levelis used to advertise the supported levels. This marker is being used by the normalization logic to fetch the supported levels and normalize it against the specified level. This will happen during testcase setup for every testcase. I think this marker is still needed for the normalization to work during testcase setup.completeness_levelis basically the normalized level that a testcase will be run against. This does NOT have to be a marker. Will the global variable not cause problem if there are two or more testcases being executed simultaneously? Making a field withintestbedfixture definitely sounds more reasonable than just global variable.
There was a problem hiding this comment.
I didn't get that part of how this can be a field in testbed fixture. I thought normalization is done per testcase depending on what levels are supported by the testcase vs what the user requests.
There was a problem hiding this comment.
You are right, Neetha. The normalization is done per testcase and testbed fixture itself will not be testcase specific. I think Danny is suggesting to add a field in the testbed fixture for every testcase during testcase setup step. It wouldn't be very different approach from just setting a global variable. I am open to suggestions.
My preference with marker was simply to stamp the testcase with its normalized level in a not-so-global variable. Hence a marker in the testcase's request data.
What are the concerns with a marker carrying this normalized value?
There was a problem hiding this comment.
Update - modified the code to use just one marker instead of two. The normalized completeness level is now added to the list of supported levels.
| for fanout, fanout_port in self.ports_shutdown_by_test: | ||
| logging.debug("Restoring fanout switch {} port {} shut down by test".format(fanout.hostname, fanout_port)) | ||
| fanout.no_shutdown(fanout_port) | ||
| normalized_level = [mark.args for mark in request.node.iter_markers(name="completeness_level")][0] |
There was a problem hiding this comment.
I didn't get that part of how this can be a field in testbed fixture. I thought normalization is done per testcase depending on what levels are supported by the testcase vs what the user requests.
f81043b1f9ff02196629655f4735b33afd7f0ae1 (HEAD -> 202111, origin/202111) [port2alias]: Fix to get right number of return values (sonic-net#1906) bbbf65943ec46e9330eadaed8bcdf1612cb8bd55 [CLI][show bgp] On chassis don't show internal BGP sessions by default (sonic-net#1927) e12de7e7bf6cff3ec127f261bf88e4d29776d27b [port] Fix port speed set (sonic-net#1952) cae7af752d484956d7fe40e4c3a849ddad460976 Fix invalid output of syslog IPv6 servers (sonic-net#1933) 6009341ddf790094166be5f0a81b4c114f00220b Routed subinterface enhancements (sonic-net#1821) 6ab9d67ca6550c592b97afb513804be474f84eb0 Enhance sfputil for CMIS QSFP (sonic-net#1949) 76cc67ba4f81c69b20efb3341808037c9db8f703 [debug dump] Refactoring Modules and Unit Tests (sonic-net#1943) cff58a8171423e4012bc8caf9748996a1e98b7e2 Add command reference for trap flow counters (sonic-net#1876) 71cf3ee43524d56ad57dd90b937cfbf4bf63ba6a [Reclaim buffer] [Mellanox] Db migrator support reclaiming reserved buffer for unused ports (sonic-net#1822) e699b49fb722e6d6fe5a1d2dacd2d39eb085c1e4 Add show command for BFD sessions (sonic-net#1942) bb6c5774c843dbfad5f1ba00ee76dae7720902d1 [warm-reboot] Fix failures of warm reboot on disconnect of ssh session (sonic-net#1529) 2e8bbb308477862a76d2327fcf696875e8f08650 Add trap flow counter support (sonic-net#1868) 58407c1386ef13772a9a9320a795e380f162ab2c [load_minigraph] Delay pfcwd start until the buffer templates are rendered (sonic-net#1937) eb388e0584ba1fe8d8dba58f1c5a148036ffe047 [sonic-package-manager] support sonic-cli-gen and packages with YANG model (sonic-net#1650) 2371d84e7d281bdb9988b5a1a012498dbbfb89ec generic_config_updater: Filename changed & VLAN validator added (sonic-net#1919) 7c0718dfaf23289d4ecc3ada9332e465c9a4e56b [config reload] Update command reference (sonic-net#1941) Signed-off-by: Abhishek Dosi <abdosi@microsoft.com>
Description of PR
Summary: Test completeness feature for Pytests
Fixes # (issue)
Type of change
Approach
What is the motivation for this PR?
To save time and allow running tests at different levels of completeness. Some tests are very elaborated and at present there is no way to run this tests partially or until a desired level. With the proposed feature, tests can now define multiple levels of completeness and a logical knob (Pytest marker) is provided to adjust the coverage of test execution.
How did you do it?
Test completeness is provided as a Pytest marker. These markers can be located at the module/test/etc level and the inner most level (if present) will supersede the higher levels.
How did you verify/test it?
Tested different Pytest tests at different levels. Also verified hierarchy at the module and test level.
link_flaptestcase with--completeness_level=debug:link_flaptestcase with--completeness_level=basic:Documentation
Feature details and usage are documented here - tests/common/plugins/test_completeness/README.md