Skip to content

Commit e795b84

Browse files
authored
Clean up BMv2's run-stf-test script and integrate it with testutils (#4981)
* Modernize run-stf-test script. Signed-off-by: fruffy <[email protected]> * Add ruff support and fix linters. Signed-off-by: fruffy <[email protected]> --------- Signed-off-by: fruffy <[email protected]>
1 parent 7629097 commit e795b84

File tree

7 files changed

+353
-334
lines changed

7 files changed

+353
-334
lines changed

backends/bmv2/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,4 @@ set (GTEST_BMV2_SOURCES
253253
gtest/load_ir_from_json.cpp
254254
)
255255
set (GTEST_SOURCES ${GTEST_SOURCES} ${GTEST_BMV2_SOURCES} PARENT_SCOPE)
256-
set (GTEST_LDADD ${GTEST_LDADD} bmv2backend PARENT_SCOPE)
256+
set (GTEST_LDADD ${GTEST_LDADD} bmv2backend PARENT_SCOPE)

backends/bmv2/bmv2stf.py

+50-36
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from collections import OrderedDict
2828
from glob import glob
2929
from pathlib import Path
30+
from typing import Dict, List, Optional, Tuple
3031

3132
try:
3233
import scapy.utils as scapy_util
@@ -35,10 +36,10 @@
3536
pass
3637

3738
FILE_DIR = Path(__file__).resolve().parent
38-
# Append tools to the import path.
39-
sys.path.append(str(FILE_DIR.joinpath("../../tools")))
40-
import testutils
41-
from stf.stf_parser import STFLexer, STFParser
39+
# Append the root to the import path.
40+
sys.path.append(str(FILE_DIR.joinpath("../..")))
41+
import tools.testutils as testutils
42+
from tools.stf.stf_parser import STFLexer, STFParser
4243

4344

4445
class TimeoutException(Exception):
@@ -52,13 +53,28 @@ def signal_handler(signum, frame):
5253
signal.signal(signal.SIGALRM, signal_handler)
5354

5455

55-
class Options(object):
56-
def __init__(self):
57-
self.binary = None
58-
self.verbose = False
59-
self.preserveTmp = False
60-
self.observationLog = None
61-
self.usePsa = False
56+
class Options:
57+
# File that is being compiled.
58+
p4_file: Path = Path(".")
59+
# Path to stf test file that is used.
60+
test_file: Optional[Path] = None
61+
# Actual location of the test framework.
62+
testdir: Path = Path(".")
63+
# The base directory of the compiler..
64+
rootdir: Path = Path(".")
65+
cleanupTmp = True # if false do not remote tmp folder created
66+
compiler_build_dir: Path = Path(".") # path to compiler build directory
67+
testName: str = "" # Name of the test
68+
replace: bool = False # replace previous outputs
69+
compiler_options: List[str] = []
70+
switch_options: List[str] = []
71+
switch_target_options: List[str] = []
72+
has_bmv2: bool = False # Is the behavioral model installed?
73+
use_psa: bool = False # Use the psa switch behavioral model?
74+
run_debugger: str = ""
75+
# Log packets produced by the BMV2 model if path to log is supplied
76+
observation_log: Optional[Path] = None
77+
init_commands: List[str] = []
6278

6379

6480
def ByteToHex(byteStr):
@@ -488,8 +504,8 @@ def p_meter_rate_one(self, p):
488504
# able to invoke the BMV2 simulator, create a CLI file
489505
# and test packets in pcap files.
490506
class RunBMV2(object):
491-
def __init__(self, folder, options, jsonfile):
492-
self.clifile = folder + "/cli.txt"
507+
def __init__(self, folder: Path, options: Options, jsonfile: Path) -> None:
508+
self.clifile = folder.joinpath("cli.txt")
493509
self.jsonfile = jsonfile
494510
self.stffile = None
495511
self.folder = folder
@@ -503,10 +519,8 @@ def __init__(self, folder, options, jsonfile):
503519
self.actions = []
504520
self.switchLogFile = "switch.log" # .txt is added by BMv2
505521
self.readJson()
506-
self.cmd_line_args = getattr(options, "switchOptions", ())
507-
self.target_specific_cmd_line_args = getattr(options, "switchTargetSpecificOptions", ())
508522

509-
def readJson(self):
523+
def readJson(self) -> None:
510524
with open(self.jsonfile) as jf:
511525
self.json = json.load(jf)
512526
for a in self.json["actions"]:
@@ -516,10 +530,10 @@ def readJson(self):
516530
for t in self.json["pipelines"][1]["tables"]:
517531
self.tables.append(BMV2Table(t))
518532

519-
def filename(self, interface, direction):
520-
return self.folder + "/" + self.pcapPrefix + str(interface) + "_" + direction + ".pcap"
533+
def filename(self, interface: str, direction: str) -> Path:
534+
return self.folder.joinpath(f"{self.pcapPrefix}{interface}_{direction}.pcap")
521535

522-
def interface_of_filename(self, f):
536+
def interface_of_filename(self, f) -> int:
523537
return int(os.path.basename(f).rstrip(".pcap").lstrip(self.pcapPrefix).rsplit("_", 1)[0])
524538

525539
def do_cli_command(self, cmd):
@@ -529,7 +543,7 @@ def do_cli_command(self, cmd):
529543
self.packetDelay = 1
530544

531545
def execute_stf_command(self, stf_entry):
532-
if self.options.verbose and stf_entry:
546+
if stf_entry:
533547
testutils.log.info("STF Command: %s", stf_entry)
534548
cmd = stf_entry[0]
535549
if cmd == "":
@@ -679,14 +693,14 @@ def tableByName(self, tableName):
679693
return candidate
680694
raise Exception("Could not find table " + tableName)
681695

682-
def interfaceArgs(self):
696+
def interfaceArgs(self) -> List[str]:
683697
# return list of interface names suitable for bmv2
684698
result = []
685699
for interface in sorted(self.interfaces):
686700
result.append("-i " + str(interface) + "@" + self.pcapPrefix + str(interface))
687701
return result
688702

689-
def generate_model_inputs(self, stf_map):
703+
def generate_model_inputs(self, stf_map: Dict[str, str]) -> int:
690704
for entry in stf_map:
691705
cmd = entry[0]
692706
if cmd in ["packet", "expect"]:
@@ -711,11 +725,11 @@ def check_switch_server_ready(self, proc, thriftPort):
711725
if result == 0:
712726
return True
713727

714-
def run(self, stf_map):
728+
def run(self, stf_map: Dict[str, str]) -> int:
715729
testutils.log.info("Running model")
716730
wait = 0 # Time to wait before model starts running
717731

718-
if self.options.usePsa:
732+
if self.options.use_psa:
719733
switch = "psa_switch"
720734
switch_cli = "psa_switch_CLI"
721735
else:
@@ -750,17 +764,15 @@ def run(self, stf_map):
750764
+ self.interfaceArgs()
751765
+ [self.jsonfile]
752766
)
753-
if self.cmd_line_args:
754-
runswitch += self.cmd_line_args
755-
if self.target_specific_cmd_line_args:
767+
runswitch += self.options.switch_options
768+
if self.options.switch_target_options:
756769
runswitch += [
757770
"--",
758-
] + self.target_specific_cmd_line_args
759-
testutils.log.info("Running %s", " ".join(runswitch))
771+
] + self.options.switch_target_options
760772
sw = subprocess.Popen(runswitch, cwd=self.folder)
761773

762774
def openInterface(ifname):
763-
fp = self.interfaces[interface] = scapy_util.RawPcapWriter(ifname, linktype=0)
775+
fp = self.interfaces[interface] = scapy_util.RawPcapWriter(str(ifname), linktype=0)
764776
fp._write_header(None)
765777

766778
# Try to open input interfaces. Each time, we set a 2 second
@@ -845,16 +857,18 @@ def openInterface(ifname):
845857
testutils.log.info("Execution completed")
846858
return rv
847859

848-
def showLog(self):
849-
with open(self.folder + "/" + self.switchLogFile + ".txt") as a:
860+
def showLog(self) -> None:
861+
"""Show the log file"""
862+
folder = self.folder.joinpath(self.switchLogFile).with_suffix(".txt")
863+
with folder.open() as a:
850864
log = a.read()
851865
testutils.log.info("Log file:\n%s", log)
852866

853-
def checkOutputs(self):
867+
def checkOutputs(self) -> int:
854868
"""Checks if the output of the filter matches expectations"""
855869
testutils.log.info("Comparing outputs")
856870
direction = "out"
857-
for file in glob(self.filename("*", direction)):
871+
for file in glob(str(self.filename("*", direction))):
858872
testutils.log.info("Checking file %s", file)
859873
interface = self.interface_of_filename(file)
860874
if os.stat(file).st_size == 0:
@@ -907,8 +921,8 @@ def checkOutputs(self):
907921
testutils.log.info("All went well.")
908922
return testutils.SUCCESS
909923

910-
def parse_stf_file(self, testfile):
911-
with open(testfile) as raw_stf:
924+
def parse_stf_file(self, testfile: Path) -> Tuple[Dict[str, str], int]:
925+
with testfile.open() as raw_stf:
912926
parser = Bmv2StfParser()
913927
stf_str = raw_stf.read()
914928
return parser.parse(stf_str)

0 commit comments

Comments
 (0)