Merge branch 'net-Introduction-of-the-tc-tests'
Lucas Bates says: ==================== net: Introduction of the tc tests Apologies for sending this as one big patch. I've been sitting on this a little too long, but it's ready and I wanted to get it out. There are a limited number of tests to start - I plan to add more on a regular basis. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
708d32e4e5
|
@ -0,0 +1 @@
|
||||||
|
__pycache__/
|
|
@ -0,0 +1,102 @@
|
||||||
|
tdc - Linux Traffic Control (tc) unit testing suite
|
||||||
|
|
||||||
|
Author: Lucas Bates - lucasb@mojatatu.com
|
||||||
|
|
||||||
|
tdc is a Python script to load tc unit tests from a separate JSON file and
|
||||||
|
execute them inside a network namespace dedicated to the task.
|
||||||
|
|
||||||
|
|
||||||
|
REQUIREMENTS
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Minimum Python version of 3.4. Earlier 3.X versions may work but are not
|
||||||
|
guaranteed.
|
||||||
|
|
||||||
|
* The kernel must have network namespace support
|
||||||
|
|
||||||
|
* The kernel must have veth support available, as a veth pair is created
|
||||||
|
prior to running the tests.
|
||||||
|
|
||||||
|
* All tc-related features must be built in or available as modules.
|
||||||
|
To check what is required in current setup run:
|
||||||
|
./tdc.py -c
|
||||||
|
|
||||||
|
Note:
|
||||||
|
In the current release, tdc run will abort due to a failure in setup or
|
||||||
|
teardown commands - which includes not being able to run a test simply
|
||||||
|
because the kernel did not support a specific feature. (This will be
|
||||||
|
handled in a future version - the current workaround is to run the tests
|
||||||
|
on specific test categories that your kernel supports)
|
||||||
|
|
||||||
|
|
||||||
|
BEFORE YOU RUN
|
||||||
|
--------------
|
||||||
|
|
||||||
|
The path to the tc executable that will be most commonly tested can be defined
|
||||||
|
in the tdc_config.py file. Find the 'TC' entry in the NAMES dictionary and
|
||||||
|
define the path.
|
||||||
|
|
||||||
|
If you need to test a different tc executable on the fly, you can do so by
|
||||||
|
using the -p option when running tdc:
|
||||||
|
./tdc.py -p /path/to/tc
|
||||||
|
|
||||||
|
|
||||||
|
RUNNING TDC
|
||||||
|
-----------
|
||||||
|
|
||||||
|
To use tdc, root privileges are required. tdc will not run otherwise.
|
||||||
|
|
||||||
|
All tests are executed inside a network namespace to prevent conflicts
|
||||||
|
within the host.
|
||||||
|
|
||||||
|
Running tdc without any arguments will run all tests. Refer to the section
|
||||||
|
on command line arguments for more information, or run:
|
||||||
|
./tdc.py -h
|
||||||
|
|
||||||
|
tdc will list the test names as they are being run, and print a summary in
|
||||||
|
TAP (Test Anything Protocol) format when they are done. If tests fail,
|
||||||
|
output captured from the failing test will be printed immediately following
|
||||||
|
the failed test in the TAP output.
|
||||||
|
|
||||||
|
|
||||||
|
USER-DEFINED CONSTANTS
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The tdc_config.py file contains multiple values that can be altered to suit
|
||||||
|
your needs. Any value in the NAMES dictionary can be altered without affecting
|
||||||
|
the tests to be run. These values are used in the tc commands that will be
|
||||||
|
executed as part of the test. More will be added as test cases require.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
$TC qdisc add dev $DEV1 ingress
|
||||||
|
|
||||||
|
|
||||||
|
COMMAND LINE ARGUMENTS
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Run tdc.py -h to see the full list of available arguments.
|
||||||
|
|
||||||
|
-p PATH Specify the tc executable located at PATH to be used on this
|
||||||
|
test run
|
||||||
|
-c Show the available test case categories in this test file
|
||||||
|
-c CATEGORY Run only tests that belong to CATEGORY
|
||||||
|
-f FILE Read test cases from the JSON file named FILE
|
||||||
|
-l [CATEGORY] List all test cases in the JSON file. If CATEGORY is
|
||||||
|
specified, list test cases matching that category.
|
||||||
|
-s ID Show the test case matching ID
|
||||||
|
-e ID Execute the test case identified by ID
|
||||||
|
-i Generate unique ID numbers for test cases with no existing
|
||||||
|
ID number
|
||||||
|
|
||||||
|
|
||||||
|
ACKNOWLEDGEMENTS
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Thanks to:
|
||||||
|
|
||||||
|
Jamal Hadi Salim, for providing valuable test cases
|
||||||
|
Keara Leibovitz, who wrote the CLI test driver that I used as a base for the
|
||||||
|
first version of the tc testing suite. This work was presented at
|
||||||
|
Netdev 1.2 Tokyo in October 2016.
|
||||||
|
Samir Hussain, for providing help while I dove into Python for the first time
|
||||||
|
and being a second eye for this code.
|
|
@ -0,0 +1,10 @@
|
||||||
|
tc Testing Suite To-Do list:
|
||||||
|
|
||||||
|
- Determine what tc features are supported in the kernel. If features are not
|
||||||
|
present, prevent the related categories from running.
|
||||||
|
|
||||||
|
- Add support for multiple versions of tc to run successively
|
||||||
|
|
||||||
|
- Improve error messages when tdc aborts its run
|
||||||
|
|
||||||
|
- Allow tdc to write its results to file
|
|
@ -0,0 +1,69 @@
|
||||||
|
tdc - Adding test cases for tdc
|
||||||
|
|
||||||
|
Author: Lucas Bates - lucasb@mojatatu.com
|
||||||
|
|
||||||
|
ADDING TEST CASES
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
User-defined tests should be added by defining a separate JSON file. This
|
||||||
|
will help prevent conflicts when updating the repository. Refer to
|
||||||
|
template.json for the required JSON format for test cases.
|
||||||
|
|
||||||
|
Include the 'id' field, but do not assign a value. Running tdc with the -i
|
||||||
|
option will generate a unique ID for that test case.
|
||||||
|
|
||||||
|
tdc will recursively search the 'tc' subdirectory for .json files. Any
|
||||||
|
test case files you create in these directories will automatically be included.
|
||||||
|
If you wish to store your custom test cases elsewhere, be sure to run tdc
|
||||||
|
with the -f argument and the path to your file.
|
||||||
|
|
||||||
|
Be aware of required escape characters in the JSON data - particularly when
|
||||||
|
defining the match pattern. Refer to the tctests.json file for examples when
|
||||||
|
in doubt.
|
||||||
|
|
||||||
|
|
||||||
|
TEST CASE STRUCTURE
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Each test case has required data:
|
||||||
|
|
||||||
|
id: A unique alphanumeric value to identify a particular test case
|
||||||
|
name: Descriptive name that explains the command under test
|
||||||
|
category: A list of single-word descriptions covering what the command
|
||||||
|
under test is testing. Example: filter, actions, u32, gact, etc.
|
||||||
|
setup: The list of commands required to ensure the command under test
|
||||||
|
succeeds. For example: if testing a filter, the command to create
|
||||||
|
the qdisc would appear here.
|
||||||
|
cmdUnderTest: The tc command being tested itself.
|
||||||
|
expExitCode: The code returned by the command under test upon its termination.
|
||||||
|
tdc will compare this value against the actual returned value.
|
||||||
|
verifyCmd: The tc command to be run to verify successful execution.
|
||||||
|
For example: if the command under test creates a gact action,
|
||||||
|
verifyCmd should be "$TC actions show action gact"
|
||||||
|
matchPattern: A regular expression to be applied against the output of the
|
||||||
|
verifyCmd to prove the command under test succeeded. This pattern
|
||||||
|
should be as specific as possible so that a false positive is not
|
||||||
|
matched.
|
||||||
|
matchCount: How many times the regex in matchPattern should match. A value
|
||||||
|
of 0 is acceptable.
|
||||||
|
teardown: The list of commands to clean up after the test is completed.
|
||||||
|
The environment should be returned to the same state as when
|
||||||
|
this test was started: qdiscs deleted, actions flushed, etc.
|
||||||
|
|
||||||
|
|
||||||
|
SETUP/TEARDOWN ERRORS
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
If an error is detected during the setup/teardown process, execution of the
|
||||||
|
tests will immediately stop with an error message and the namespace in which
|
||||||
|
the tests are run will be destroyed. This is to prevent inaccurate results
|
||||||
|
in the test cases.
|
||||||
|
|
||||||
|
Repeated failures of the setup/teardown may indicate a problem with the test
|
||||||
|
case, or possibly even a bug in one of the commands that are not being tested.
|
||||||
|
|
||||||
|
It's possible to include acceptable exit codes with the setup/teardown command
|
||||||
|
so that it doesn't halt the script for an error that doesn't matter. Turn the
|
||||||
|
individual command into a list, with the command being first, followed by all
|
||||||
|
acceptable exit codes for the command.
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"name": "",
|
||||||
|
"category": [
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"setup": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"cmdUnderTest": "",
|
||||||
|
"expExitCode": "",
|
||||||
|
"verifyCmd": "",
|
||||||
|
"matchPattern": "",
|
||||||
|
"matchCount": "",
|
||||||
|
"teardown": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"name": "",
|
||||||
|
"category": [
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"setup": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"cmdUnderTest": "",
|
||||||
|
"expExitCode": "",
|
||||||
|
"verifyCmd": "",
|
||||||
|
"matchPattern": "",
|
||||||
|
"matchCount": "",
|
||||||
|
"teardown": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "e9a3",
|
||||||
|
"name": "Add u32 with source match",
|
||||||
|
"category": [
|
||||||
|
"filter",
|
||||||
|
"u32"
|
||||||
|
],
|
||||||
|
"setup": [
|
||||||
|
"$TC qdisc add dev $DEV1 ingress"
|
||||||
|
],
|
||||||
|
"cmdUnderTest": "$TC filter add dev $DEV1 parent ffff: protocol ip prio 1 u32 match ip src 127.0.0.1/32 flowid 1:1 action ok",
|
||||||
|
"expExitCode": "0",
|
||||||
|
"verifyCmd": "$TC filter show dev $DEV1 parent ffff:",
|
||||||
|
"matchPattern": "match 7f000002/ffffffff at 12",
|
||||||
|
"matchCount": "0",
|
||||||
|
"teardown": [
|
||||||
|
"$TC qdisc del dev $DEV1 ingress"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,413 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
tdc.py - Linux tc (Traffic Control) unit test driver
|
||||||
|
|
||||||
|
Copyright (C) 2017 Lucas Bates <lucasb@mojatatu.com>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from collections import OrderedDict
|
||||||
|
from string import Template
|
||||||
|
|
||||||
|
from tdc_config import *
|
||||||
|
from tdc_helper import *
|
||||||
|
|
||||||
|
|
||||||
|
USE_NS = True
|
||||||
|
|
||||||
|
|
||||||
|
def replace_keywords(cmd):
|
||||||
|
"""
|
||||||
|
For a given executable command, substitute any known
|
||||||
|
variables contained within NAMES with the correct values
|
||||||
|
"""
|
||||||
|
tcmd = Template(cmd)
|
||||||
|
subcmd = tcmd.safe_substitute(NAMES)
|
||||||
|
return subcmd
|
||||||
|
|
||||||
|
|
||||||
|
def exec_cmd(command, nsonly=True):
|
||||||
|
"""
|
||||||
|
Perform any required modifications on an executable command, then run
|
||||||
|
it in a subprocess and return the results.
|
||||||
|
"""
|
||||||
|
if (USE_NS and nsonly):
|
||||||
|
command = 'ip netns exec $NS ' + command
|
||||||
|
|
||||||
|
if '$' in command:
|
||||||
|
command = replace_keywords(command)
|
||||||
|
|
||||||
|
proc = subprocess.Popen(command,
|
||||||
|
shell=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
(rawout, serr) = proc.communicate()
|
||||||
|
|
||||||
|
if proc.returncode != 0:
|
||||||
|
foutput = serr.decode("utf-8")
|
||||||
|
else:
|
||||||
|
foutput = rawout.decode("utf-8")
|
||||||
|
|
||||||
|
proc.stdout.close()
|
||||||
|
proc.stderr.close()
|
||||||
|
return proc, foutput
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_env(cmdlist):
|
||||||
|
"""
|
||||||
|
Execute the setup/teardown commands for a test case. Optionally
|
||||||
|
terminate test execution if the command fails.
|
||||||
|
"""
|
||||||
|
for cmdinfo in cmdlist:
|
||||||
|
if (type(cmdinfo) == list):
|
||||||
|
exit_codes = cmdinfo[1:]
|
||||||
|
cmd = cmdinfo[0]
|
||||||
|
else:
|
||||||
|
exit_codes = [0]
|
||||||
|
cmd = cmdinfo
|
||||||
|
|
||||||
|
if (len(cmd) == 0):
|
||||||
|
continue
|
||||||
|
|
||||||
|
(proc, foutput) = exec_cmd(cmd)
|
||||||
|
|
||||||
|
if proc.returncode not in exit_codes:
|
||||||
|
print
|
||||||
|
print("Could not execute:")
|
||||||
|
print(cmd)
|
||||||
|
print("\nError message:")
|
||||||
|
print(foutput)
|
||||||
|
print("\nAborting test run.")
|
||||||
|
ns_destroy()
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_runner(filtered_tests):
|
||||||
|
"""
|
||||||
|
Driver function for the unit tests.
|
||||||
|
|
||||||
|
Prints information about the tests being run, executes the setup and
|
||||||
|
teardown commands and the command under test itself. Also determines
|
||||||
|
success/failure based on the information in the test case and generates
|
||||||
|
TAP output accordingly.
|
||||||
|
"""
|
||||||
|
testlist = filtered_tests
|
||||||
|
tcount = len(testlist)
|
||||||
|
index = 1
|
||||||
|
tap = str(index) + ".." + str(tcount) + "\n"
|
||||||
|
|
||||||
|
for tidx in testlist:
|
||||||
|
result = True
|
||||||
|
tresult = ""
|
||||||
|
print("Test " + tidx["id"] + ": " + tidx["name"])
|
||||||
|
prepare_env(tidx["setup"])
|
||||||
|
(p, procout) = exec_cmd(tidx["cmdUnderTest"])
|
||||||
|
exit_code = p.returncode
|
||||||
|
|
||||||
|
if (exit_code != int(tidx["expExitCode"])):
|
||||||
|
result = False
|
||||||
|
print("exit:", exit_code, int(tidx["expExitCode"]))
|
||||||
|
print(procout)
|
||||||
|
else:
|
||||||
|
match_pattern = re.compile(str(tidx["matchPattern"]), re.DOTALL)
|
||||||
|
(p, procout) = exec_cmd(tidx["verifyCmd"])
|
||||||
|
match_index = re.findall(match_pattern, procout)
|
||||||
|
if len(match_index) != int(tidx["matchCount"]):
|
||||||
|
result = False
|
||||||
|
|
||||||
|
if result == True:
|
||||||
|
tresult += "ok "
|
||||||
|
else:
|
||||||
|
tresult += "not ok "
|
||||||
|
tap += tresult + str(index) + " " + tidx["id"] + " " + tidx["name"] + "\n"
|
||||||
|
|
||||||
|
if result == False:
|
||||||
|
tap += procout
|
||||||
|
|
||||||
|
prepare_env(tidx["teardown"])
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return tap
|
||||||
|
|
||||||
|
|
||||||
|
def ns_create():
|
||||||
|
"""
|
||||||
|
Create the network namespace in which the tests will be run and set up
|
||||||
|
the required network devices for it.
|
||||||
|
"""
|
||||||
|
if (USE_NS):
|
||||||
|
cmd = 'ip netns add $NS'
|
||||||
|
exec_cmd(cmd, False)
|
||||||
|
cmd = 'ip link add $DEV0 type veth peer name $DEV1'
|
||||||
|
exec_cmd(cmd, False)
|
||||||
|
cmd = 'ip link set $DEV1 netns $NS'
|
||||||
|
exec_cmd(cmd, False)
|
||||||
|
cmd = 'ip link set $DEV0 up'
|
||||||
|
exec_cmd(cmd, False)
|
||||||
|
cmd = 'ip -s $NS link set $DEV1 up'
|
||||||
|
exec_cmd(cmd, False)
|
||||||
|
|
||||||
|
|
||||||
|
def ns_destroy():
|
||||||
|
"""
|
||||||
|
Destroy the network namespace for testing (and any associated network
|
||||||
|
devices as well)
|
||||||
|
"""
|
||||||
|
if (USE_NS):
|
||||||
|
cmd = 'ip netns delete $NS'
|
||||||
|
exec_cmd(cmd, False)
|
||||||
|
|
||||||
|
|
||||||
|
def has_blank_ids(idlist):
|
||||||
|
"""
|
||||||
|
Search the list for empty ID fields and return true/false accordingly.
|
||||||
|
"""
|
||||||
|
return not(all(k for k in idlist))
|
||||||
|
|
||||||
|
|
||||||
|
def load_from_file(filename):
|
||||||
|
"""
|
||||||
|
Open the JSON file containing the test cases and return them as an
|
||||||
|
ordered dictionary object.
|
||||||
|
"""
|
||||||
|
with open(filename) as test_data:
|
||||||
|
testlist = json.load(test_data, object_pairs_hook=OrderedDict)
|
||||||
|
idlist = get_id_list(testlist)
|
||||||
|
if (has_blank_ids(idlist)):
|
||||||
|
for k in testlist:
|
||||||
|
k['filename'] = filename
|
||||||
|
return testlist
|
||||||
|
|
||||||
|
|
||||||
|
def args_parse():
|
||||||
|
"""
|
||||||
|
Create the argument parser.
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(description='Linux TC unit tests')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def set_args(parser):
|
||||||
|
"""
|
||||||
|
Set the command line arguments for tdc.
|
||||||
|
"""
|
||||||
|
parser.add_argument('-p', '--path', type=str,
|
||||||
|
help='The full path to the tc executable to use')
|
||||||
|
parser.add_argument('-c', '--category', type=str, nargs='?', const='+c',
|
||||||
|
help='Run tests only from the specified category, or if no category is specified, list known categories.')
|
||||||
|
parser.add_argument('-f', '--file', type=str,
|
||||||
|
help='Run tests from the specified file')
|
||||||
|
parser.add_argument('-l', '--list', type=str, nargs='?', const="", metavar='CATEGORY',
|
||||||
|
help='List all test cases, or those only within the specified category')
|
||||||
|
parser.add_argument('-s', '--show', type=str, nargs=1, metavar='ID', dest='showID',
|
||||||
|
help='Display the test case with specified id')
|
||||||
|
parser.add_argument('-e', '--execute', type=str, nargs=1, metavar='ID',
|
||||||
|
help='Execute the single test case with specified ID')
|
||||||
|
parser.add_argument('-i', '--id', action='store_true', dest='gen_id',
|
||||||
|
help='Generate ID numbers for new test cases')
|
||||||
|
return parser
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def check_default_settings(args):
|
||||||
|
"""
|
||||||
|
Process any arguments overriding the default settings, and ensure the
|
||||||
|
settings are correct.
|
||||||
|
"""
|
||||||
|
# Allow for overriding specific settings
|
||||||
|
global NAMES
|
||||||
|
|
||||||
|
if args.path != None:
|
||||||
|
NAMES['TC'] = args.path
|
||||||
|
if not os.path.isfile(NAMES['TC']):
|
||||||
|
print("The specified tc path " + NAMES['TC'] + " does not exist.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_id_list(alltests):
|
||||||
|
"""
|
||||||
|
Generate a list of all IDs in the test cases.
|
||||||
|
"""
|
||||||
|
return [x["id"] for x in alltests]
|
||||||
|
|
||||||
|
|
||||||
|
def check_case_id(alltests):
|
||||||
|
"""
|
||||||
|
Check for duplicate test case IDs.
|
||||||
|
"""
|
||||||
|
idl = get_id_list(alltests)
|
||||||
|
return [x for x in idl if idl.count(x) > 1]
|
||||||
|
|
||||||
|
|
||||||
|
def does_id_exist(alltests, newid):
|
||||||
|
"""
|
||||||
|
Check if a given ID already exists in the list of test cases.
|
||||||
|
"""
|
||||||
|
idl = get_id_list(alltests)
|
||||||
|
return (any(newid == x for x in idl))
|
||||||
|
|
||||||
|
|
||||||
|
def generate_case_ids(alltests):
|
||||||
|
"""
|
||||||
|
If a test case has a blank ID field, generate a random hex ID for it
|
||||||
|
and then write the test cases back to disk.
|
||||||
|
"""
|
||||||
|
import random
|
||||||
|
for c in alltests:
|
||||||
|
if (c["id"] == ""):
|
||||||
|
while True:
|
||||||
|
newid = str('%04x' % random.randrange(16**4))
|
||||||
|
if (does_id_exist(alltests, newid)):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
c['id'] = newid
|
||||||
|
break
|
||||||
|
|
||||||
|
ufilename = []
|
||||||
|
for c in alltests:
|
||||||
|
if ('filename' in c):
|
||||||
|
ufilename.append(c['filename'])
|
||||||
|
ufilename = get_unique_item(ufilename)
|
||||||
|
for f in ufilename:
|
||||||
|
testlist = []
|
||||||
|
for t in alltests:
|
||||||
|
if 'filename' in t:
|
||||||
|
if t['filename'] == f:
|
||||||
|
del t['filename']
|
||||||
|
testlist.append(t)
|
||||||
|
outfile = open(f, "w")
|
||||||
|
json.dump(testlist, outfile, indent=4)
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_cases(args):
|
||||||
|
"""
|
||||||
|
If a test case file is specified, retrieve tests from that file.
|
||||||
|
Otherwise, glob for all json files in subdirectories and load from
|
||||||
|
each one.
|
||||||
|
"""
|
||||||
|
import fnmatch
|
||||||
|
if args.file != None:
|
||||||
|
if not os.path.isfile(args.file):
|
||||||
|
print("The specified test case file " + args.file + " does not exist.")
|
||||||
|
exit(1)
|
||||||
|
flist = [args.file]
|
||||||
|
else:
|
||||||
|
flist = []
|
||||||
|
for root, dirnames, filenames in os.walk('tc-tests'):
|
||||||
|
for filename in fnmatch.filter(filenames, '*.json'):
|
||||||
|
flist.append(os.path.join(root, filename))
|
||||||
|
alltests = list()
|
||||||
|
for casefile in flist:
|
||||||
|
alltests = alltests + (load_from_file(casefile))
|
||||||
|
return alltests
|
||||||
|
|
||||||
|
|
||||||
|
def set_operation_mode(args):
|
||||||
|
"""
|
||||||
|
Load the test case data and process remaining arguments to determine
|
||||||
|
what the script should do for this run, and call the appropriate
|
||||||
|
function.
|
||||||
|
"""
|
||||||
|
alltests = get_test_cases(args)
|
||||||
|
|
||||||
|
if args.gen_id:
|
||||||
|
idlist = get_id_list(alltests)
|
||||||
|
if (has_blank_ids(idlist)):
|
||||||
|
alltests = generate_case_ids(alltests)
|
||||||
|
else:
|
||||||
|
print("No empty ID fields found in test files.")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
duplicate_ids = check_case_id(alltests)
|
||||||
|
if (len(duplicate_ids) > 0):
|
||||||
|
print("The following test case IDs are not unique:")
|
||||||
|
print(str(set(duplicate_ids)))
|
||||||
|
print("Please correct them before continuing.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
ucat = get_test_categories(alltests)
|
||||||
|
|
||||||
|
if args.showID:
|
||||||
|
show_test_case_by_id(alltests, args.showID[0])
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if args.execute:
|
||||||
|
target_id = args.execute[0]
|
||||||
|
else:
|
||||||
|
target_id = ""
|
||||||
|
|
||||||
|
if args.category:
|
||||||
|
if (args.category == '+c'):
|
||||||
|
print("Available categories:")
|
||||||
|
print_sll(ucat)
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
|
target_category = args.category
|
||||||
|
else:
|
||||||
|
target_category = ""
|
||||||
|
|
||||||
|
|
||||||
|
testcases = get_categorized_testlist(alltests, ucat)
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
if (len(args.list) == 0):
|
||||||
|
list_test_cases(alltests)
|
||||||
|
exit(0)
|
||||||
|
elif(len(args.list > 0)):
|
||||||
|
if (args.list not in ucat):
|
||||||
|
print("Unknown category " + args.list)
|
||||||
|
print("Available categories:")
|
||||||
|
print_sll(ucat)
|
||||||
|
exit(1)
|
||||||
|
list_test_cases(testcases[args.list])
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if (os.geteuid() != 0):
|
||||||
|
print("This script must be run with root privileges.\n")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
ns_create()
|
||||||
|
|
||||||
|
if (len(target_category) == 0):
|
||||||
|
if (len(target_id) > 0):
|
||||||
|
alltests = list(filter(lambda x: target_id in x['id'], alltests))
|
||||||
|
if (len(alltests) == 0):
|
||||||
|
print("Cannot find a test case with ID matching " + target_id)
|
||||||
|
exit(1)
|
||||||
|
catresults = test_runner(alltests)
|
||||||
|
print("All test results: " + "\n\n" + catresults)
|
||||||
|
elif (len(target_category) > 0):
|
||||||
|
if (target_category not in ucat):
|
||||||
|
print("Specified category is not present in this file.")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
catresults = test_runner(testcases[target_category])
|
||||||
|
print("Category " + target_category + "\n\n" + catresults)
|
||||||
|
|
||||||
|
ns_destroy()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Start of execution; set up argument parser and get the arguments,
|
||||||
|
and start operations.
|
||||||
|
"""
|
||||||
|
parser = args_parse()
|
||||||
|
parser = set_args(parser)
|
||||||
|
(args, remaining) = parser.parse_known_args()
|
||||||
|
check_default_settings(args)
|
||||||
|
|
||||||
|
set_operation_mode(args)
|
||||||
|
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,17 @@
|
||||||
|
"""
|
||||||
|
tdc_config.py - tdc user-specified values
|
||||||
|
|
||||||
|
Copyright (C) 2017 Lucas Bates <lucasb@mojatatu.com>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Dictionary containing all values that can be substituted in executable
|
||||||
|
# commands.
|
||||||
|
NAMES = {
|
||||||
|
# Substitute your own tc path here
|
||||||
|
'TC': '/sbin/tc',
|
||||||
|
# Name of veth devices to be created for the namespace
|
||||||
|
'DEV0': 'v0p0',
|
||||||
|
'DEV1': 'v0p1',
|
||||||
|
# Name of the namespace to use
|
||||||
|
'NS': 'tcut'
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
"""
|
||||||
|
tdc_helper.py - tdc helper functions
|
||||||
|
|
||||||
|
Copyright (C) 2017 Lucas Bates <lucasb@mojatatu.com>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_categorized_testlist(alltests, ucat):
|
||||||
|
""" Sort the master test list into categories. """
|
||||||
|
testcases = dict()
|
||||||
|
|
||||||
|
for category in ucat:
|
||||||
|
testcases[category] = list(filter(lambda x: category in x['category'], alltests))
|
||||||
|
|
||||||
|
return(testcases)
|
||||||
|
|
||||||
|
|
||||||
|
def get_unique_item(lst):
|
||||||
|
""" For a list, return a set of the unique items in the list. """
|
||||||
|
return list(set(lst))
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_categories(alltests):
|
||||||
|
""" Discover all unique test categories present in the test case file. """
|
||||||
|
ucat = []
|
||||||
|
for t in alltests:
|
||||||
|
ucat.extend(get_unique_item(t['category']))
|
||||||
|
ucat = get_unique_item(ucat)
|
||||||
|
return ucat
|
||||||
|
|
||||||
|
def list_test_cases(testlist):
|
||||||
|
""" Print IDs and names of all test cases. """
|
||||||
|
for curcase in testlist:
|
||||||
|
print(curcase['id'] + ': (' + ', '.join(curcase['category']) + ") " + curcase['name'])
|
||||||
|
|
||||||
|
|
||||||
|
def list_categories(testlist):
|
||||||
|
""" Show all categories that are present in a test case file. """
|
||||||
|
categories = set(map(lambda x: x['category'], testlist))
|
||||||
|
print("Available categories:")
|
||||||
|
print(", ".join(str(s) for s in categories))
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
def print_list(cmdlist):
|
||||||
|
""" Print a list of strings prepended with a tab. """
|
||||||
|
for l in cmdlist:
|
||||||
|
if (type(l) == list):
|
||||||
|
print("\t" + str(l[0]))
|
||||||
|
else:
|
||||||
|
print("\t" + str(l))
|
||||||
|
|
||||||
|
|
||||||
|
def print_sll(items):
|
||||||
|
print("\n".join(str(s) for s in items))
|
||||||
|
|
||||||
|
|
||||||
|
def print_test_case(tcase):
|
||||||
|
""" Pretty-printing of a given test case. """
|
||||||
|
for k in tcase.keys():
|
||||||
|
if (type(tcase[k]) == list):
|
||||||
|
print(k + ":")
|
||||||
|
print_list(tcase[k])
|
||||||
|
else:
|
||||||
|
print(k + ": " + tcase[k])
|
||||||
|
|
||||||
|
|
||||||
|
def show_test_case_by_id(testlist, caseID):
|
||||||
|
""" Find the specified test case to pretty-print. """
|
||||||
|
if not any(d.get('id', None) == caseID for d in testlist):
|
||||||
|
print("That ID does not exist.")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
print_test_case(next((d for d in testlist if d['id'] == caseID)))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue