Skip to content

Commit

Permalink
Merge branch 'master' into bst/py3.13
Browse files Browse the repository at this point in the history
Signed-off-by: Rouven Czerwinski <[email protected]>
  • Loading branch information
Emantor authored Oct 21, 2024
2 parents e136f2d + 158d79f commit 15a9c3f
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 98 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/push-pr-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
uses: ./.github/workflows/reusable-unit-tests.yml
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
python-version: ${{ matrix.python-version }}
push-pr-unit-tests-docker:
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/reusable-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:
branch:
type: string
required: false
secrets:
CODECOV_TOKEN:
required: false

jobs:
build:
Expand Down Expand Up @@ -56,6 +59,10 @@ jobs:
- name: Test with pytest
run: |
pytest -r a --cov-config .coveragerc --cov=labgrid --local-sshmanager --ssh-username runner -k "not test_docker_with_daemon"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Build documentation
run: |
make -C doc clean
Expand All @@ -67,4 +74,3 @@ jobs:
# check README.rst separately
pip install rstcheck
rstcheck --ignore-languages=bash --report-level=WARNING README.rst
- uses: codecov/codecov-action@v3
2 changes: 2 additions & 0 deletions .github/workflows/scheduled-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
branch: ['master']
uses: ./.github/workflows/reusable-unit-tests.yml
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
python-version: ${{ matrix.python-version }}
branch: ${{ matrix.branch }}
Expand Down
66 changes: 42 additions & 24 deletions helpers/labgrid-raw-interface
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ def get_denylist():
return denylist


def main(program, ifname, count):
if not ifname:
def main(program, options):
if not options.ifname:
raise ValueError("Empty interface name.")
if any((c == "/" or c.isspace()) for c in ifname):
raise ValueError(f"Interface name '{ifname}' contains invalid characters.")
if len(ifname) > 16:
raise ValueError(f"Interface name '{ifname}' is too long.")
if any((c == "/" or c.isspace()) for c in options.ifname):
raise ValueError(f"Interface name '{options.ifname}' contains invalid characters.")
if len(options.ifname) > 16:
raise ValueError(f"Interface name '{options.ifname}' is too long.")

denylist = get_denylist()

if ifname in denylist:
raise ValueError(f"Interface name '{ifname}' is denied in denylist.")
if options.ifname in denylist:
raise ValueError(f"Interface name '{options.ifname}' is denied in denylist.")

programs = ["tcpreplay", "tcpdump"]
if program not in programs:
Expand All @@ -54,18 +54,30 @@ def main(program, ifname, count):
]

if program == "tcpreplay":
args.append(f"--intf1={ifname}")
args.append('-')
args.append(f"--intf1={options.ifname}")
args.append("-")

if program == "tcpdump":
args.append("-n")
args.append(f"--interface={ifname}")
args.append(f"--interface={options.ifname}")
# Write out each packet as it is received
args.append("--packet-buffered")
# Capture complete packets (for compatibility with older tcpdump versions)
args.append("--snapshot-length=0")
args.append("-w")
args.append('-')
args.append("-")

if count:
if options.count:
args.append("-c")
args.append(str(count))
args.append(str(options.count))

if options.timeout:
# The timeout is implemented by specifying the number of seconds before rotating the
# dump file, but limiting the number of files to 1
args.append("-G")
args.append(str(options.timeout))
args.append("-W")
args.append("1")

try:
os.execvp(args[0], args)
Expand All @@ -75,22 +87,28 @@ def main(program, ifname, count):

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
'-d',
'--debug',
action='store_true',
default=False,
help="enable debug mode"
parser.add_argument("-d", "--debug", action="store_true", default=False, help="enable debug mode")
subparsers = parser.add_subparsers(dest="program", help="program to run")

# tcpdump
tcpdump_parser = subparsers.add_parser("tcpdump")
tcpdump_parser.add_argument("ifname", type=str, help="interface name")
tcpdump_parser.add_argument("count", type=int, default=None, help="amount of frames to capture while recording")
tcpdump_parser.add_argument(
"--timeout", type=int, default=None, help="Amount of time to capture while recording. 0 means capture forever"
)
parser.add_argument('program', type=str, help='program to run, either tcpreplay or tcpdump')
parser.add_argument('interface', type=str, help='interface name')
parser.add_argument('count', nargs="?", type=int, default=None, help='amount of frames to capture while recording')

# tcpreplay
tcpreplay_parser = subparsers.add_parser("tcpreplay")
tcpreplay_parser.add_argument("ifname", type=str, help="interface name")

args = parser.parse_args()
try:
main(args.program, args.interface, args.count)
main(args.program, args)
except Exception as e: # pylint: disable=broad-except
if args.debug:
import traceback

traceback.print_exc(file=sys.stderr)
print(f"ERROR: {e}", file=sys.stderr)
exit(1)
40 changes: 24 additions & 16 deletions labgrid/driver/rawnetworkinterfacedriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ def _stop(self, proc, *, timeout=None):
)

@Driver.check_active
@step(args=["filename", "count"])
def start_record(self, filename, *, count=None):
@step(args=["filename", "count", "timeout"])
def start_record(self, filename, *, count=None, timeout=None):
"""
Starts tcpdump on bound network interface resource.
Args:
filename (str): name of a file to record to
filename (str): name of a file to record to, or None to record to stdout
count (int): optional, exit after receiving this many number of packets
timeout (int): optional, number of seconds to capture packets before tcpdump exits
Returns:
Popen object of tcpdump process
"""
Expand All @@ -69,9 +70,15 @@ def start_record(self, filename, *, count=None):
cmd = ["tcpdump", self.iface.ifname]
if count is not None:
cmd.append(str(count))
if timeout is not None:
cmd.append("--timeout")
cmd.append(str(timeout))
cmd = self._wrap_command(cmd)
with open(filename, "wb") as outdata:
self._record_handle = subprocess.Popen(cmd, stdout=outdata, stderr=subprocess.PIPE)
if filename is None:
self._record_handle = subprocess.Popen(cmd, stdout=subprocess.PIPE)
else:
with open(filename, "wb") as outdata:
self._record_handle = subprocess.Popen(cmd, stdout=outdata, stderr=subprocess.PIPE)
return self._record_handle

@Driver.check_active
Expand All @@ -86,6 +93,11 @@ def stop_record(self, *, timeout=None):
"""
try:
self._stop(self._record_handle, timeout=timeout)
except subprocess.TimeoutExpired:
# If live streaming packets, there is no reason to wait for tcpdump
# to finish, so expect a timeout if piping to stdout
if self._record_handle.stdout is None:
raise
finally:
self._record_handle = None

Expand All @@ -97,17 +109,18 @@ def record(self, filename, *, count=None, timeout=None):
Either count or timeout must be specified.
Args:
filename (str): name of a file to record to
filename (str): name of a file to record to, or None to live stream packets
count (int): optional, exit after receiving this many number of packets
timeout (int): optional, maximum number of seconds to wait for the tcpdump process to
terminate
timeout (int): optional, number of seconds to capture packets before tcpdump exits
Returns:
Popen object of tcpdump process. If filename is None, packets can be read from stdout
"""
assert count or timeout

try:
yield self.start_record(filename, count=count)
yield self.start_record(filename, count=count, timeout=timeout)
finally:
self.stop_record(timeout=timeout)
self.stop_record(timeout=0 if filename is None else None)

@Driver.check_active
@step(args=["filename"])
Expand Down Expand Up @@ -170,12 +183,7 @@ def get_statistics(self):
"""
Returns basic interface statistics of bound network interface resource.
"""
cmd = self.iface.command_prefix + [
"ip",
"--json",
"-stats", "-stats",
"link", "show",
self.iface.ifname]
cmd = self.iface.command_prefix + ["ip", "--json", "-stats", "-stats", "link", "show", self.iface.ifname]
output = processwrapper.check_output(cmd)
return json.loads(output)[0]

Expand Down
46 changes: 36 additions & 10 deletions labgrid/util/agents/usb_hid_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
- Turn digital output on and off
"""

import errno
from contextlib import contextmanager
from time import monotonic, sleep

import usb.core
import usb.util

Expand All @@ -26,18 +30,35 @@ def __init__(self, **args):
raise ValueError("Device not found")

if self._dev.idVendor == 0x16C0:
self.set_output = self.set_output_dcttech
self.get_output = self.get_output_dcttech
self._set_output = self._set_output_dcttech
self._get_output = self._get_output_dcttech
elif self._dev.idVendor == 0x5131:
self.set_output = self.set_output_lcus
self.get_output = self.get_output_lcus
self._set_output = self._set_output_lcus
self._get_output = self._get_output_lcus
else:
raise ValueError(f"Unknown vendor/protocol for VID {self._dev.idVendor:x}")

if self._dev.is_kernel_driver_active(0):
self._dev.detach_kernel_driver(0)

def set_output_dcttech(self, number, status):
@contextmanager
def _claimed(self):
timeout = monotonic() + 1.0
while True:
try:
usb.util.claim_interface(self._dev, 0)
break
except usb.core.USBError as e:
if monotonic() > timeout:
raise e
if e.errno == errno.EBUSY:
sleep(0.01)
else:
raise e
yield
usb.util.release_interface(self._dev, 0)

def _set_output_dcttech(self, number, status):
assert 1 <= number <= 8
req = [0xFF if status else 0xFD, number]
self._dev.ctrl_transfer(
Expand All @@ -48,7 +69,7 @@ def set_output_dcttech(self, number, status):
req, # payload
)

def get_output_dcttech(self, number):
def _get_output_dcttech(self, number):
assert 1 <= number <= 8
resp = self._dev.ctrl_transfer(
usb.util.CTRL_TYPE_CLASS | usb.util.CTRL_RECIPIENT_DEVICE | usb.util.ENDPOINT_IN,
Expand All @@ -59,7 +80,7 @@ def get_output_dcttech(self, number):
)
return bool(resp[7] & (1 << (number - 1)))

def set_output_lcus(self, number, status):
def _set_output_lcus(self, number, status):
assert 1 <= number <= 8
ep_in = self._dev[0][(0, 0)][0]
ep_out = self._dev[0][(0, 0)][1]
Expand All @@ -68,13 +89,18 @@ def set_output_lcus(self, number, status):
ep_out.write(req)
ep_in.read(64)

def get_output_lcus(self, number):
def _get_output_lcus(self, number):
assert 1 <= number <= 8
# we have no information on how to read the current value
return False

def __del__(self):
usb.util.release_interface(self._dev, 0)
def set_output(self, number, status):
with self._claimed():
self._set_output(number, status)

def get_output(self, number):
with self._claimed():
self._get_output(number)


_relays = {}
Expand Down
49 changes: 2 additions & 47 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,54 +75,9 @@ snmp = [
]
vxi11 = ["python-vxi11>=0.9"]
xena = ["xenavalkyrie>=3.0.1"]
deb = [
# labgrid[modbus]
"pyModbusTCP>=0.1.10",

# labgrid[onewire]
"onewire>=0.2",

# labgrid[snmp]
"pysnmp>=4.4.12, <6",
"pyasn1<0.6.1",
]
deb = ["labgrid[modbus,onewire,snmp]"]
dev = [
# references to other optional dependency groups
# labgrid[doc]
"sphinx_rtd_theme>=1.0.0",
"Sphinx>=2.0.0",

# labgrid[docker]
"docker>=5.0.2",

# labgrid[graph]
"graphviz>=0.17.0",

# labgrid[kasa]
"python-kasa>=0.4.0",

# labgrid[modbus]
"pyModbusTCP>=0.1.10",

# labgrid[modbusrtu]
"minimalmodbus>=1.0.2",

# labgrid[mqtt]
"paho-mqtt>=2.0.0",

# labgrid[onewire]
"onewire>=0.2",

# labgrid[pyvisa]
"pyvisa>=1.11.3",
"PyVISA-py>=0.5.2",

# labgrid[snmp]
"pysnmp>=4.4.12, <6",
"pyasn1<0.6.1",

# labgrid[vxi11]
"python-vxi11>=0.9",
"labgrid[doc,docker,graph,kasa,modbus,modbusrtu,mqtt,onewire,pyvisa,snmp,vxi11]",

# additional dev dependencies
"psutil>=5.8.0",
Expand Down

0 comments on commit 15a9c3f

Please sign in to comment.