mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-23 21:28:16 -04:00
Merge branch 'selftests-drv-net-convert-so_txtime-to-drv-net'
Willem de Bruijn says: ==================== selftests: drv-net: convert so_txtime to drv-net In preparation for extending to pacing hardware offload, convert the so_txtime.sh test to a drv-net test that can be run against netdevsim and real hardware. Two preparatory patches 1. support negative tests, where tests are expected to fail 2. add a tc helper See individual patches for details and detailed changelog ==================== Link: https://patch.msgid.link/20260504174056.565319-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
napi_id_helper
|
||||
psp_responder
|
||||
so_txtime
|
||||
|
||||
@@ -7,6 +7,7 @@ TEST_INCLUDES := $(wildcard lib/py/*.py) \
|
||||
|
||||
TEST_GEN_FILES := \
|
||||
napi_id_helper \
|
||||
so_txtime \
|
||||
# end of TEST_GEN_FILES
|
||||
|
||||
TEST_PROGS := \
|
||||
@@ -21,6 +22,7 @@ TEST_PROGS := \
|
||||
queues.py \
|
||||
ring_reconfig.py \
|
||||
shaper.py \
|
||||
so_txtime.py \
|
||||
stats.py \
|
||||
xdp.py \
|
||||
# end of TEST_PROGS
|
||||
|
||||
@@ -8,5 +8,7 @@ CONFIG_NETCONSOLE=m
|
||||
CONFIG_NETCONSOLE_DYNAMIC=y
|
||||
CONFIG_NETCONSOLE_EXTENDED_LOG=y
|
||||
CONFIG_NETDEVSIM=m
|
||||
CONFIG_NET_SCH_ETF=m
|
||||
CONFIG_NET_SCH_FQ=m
|
||||
CONFIG_VLAN_8021Q=m
|
||||
CONFIG_XDP_SOCKETS=y
|
||||
|
||||
@@ -23,7 +23,8 @@ try:
|
||||
NlError, RtnlFamily, DevlinkFamily, PSPFamily, Netlink
|
||||
from net.lib.py import CmdExitFailure
|
||||
from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
|
||||
fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file
|
||||
fd_read_timeout, ip, rand_port, rand_ports, tc, wait_port_listen, \
|
||||
wait_file
|
||||
from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
|
||||
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
|
||||
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
|
||||
@@ -36,7 +37,7 @@ try:
|
||||
"NlError", "RtnlFamily", "DevlinkFamily", "PSPFamily", "Netlink",
|
||||
"CmdExitFailure",
|
||||
"bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
|
||||
"fd_read_timeout", "ip", "rand_port", "rand_ports",
|
||||
"fd_read_timeout", "ip", "rand_port", "rand_ports", "tc",
|
||||
"wait_port_listen", "wait_file",
|
||||
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
|
||||
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "kselftest.h"
|
||||
|
||||
static int cfg_clockid = CLOCK_TAI;
|
||||
static uint16_t cfg_port = 8000;
|
||||
static int cfg_variance_us = 4000;
|
||||
@@ -43,6 +45,8 @@ static bool cfg_rx;
|
||||
static uint64_t glob_tstart;
|
||||
static uint64_t tdeliver_max;
|
||||
|
||||
static int errors;
|
||||
|
||||
/* encode one timed transmission (of a 1B payload) */
|
||||
struct timed_send {
|
||||
char data;
|
||||
@@ -131,13 +135,15 @@ static void do_recv_one(int fdr, struct timed_send *ts)
|
||||
fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n",
|
||||
rbuf[0], (long long)tstop, (long long)texpect);
|
||||
|
||||
if (rbuf[0] != ts->data)
|
||||
error(1, 0, "payload mismatch. expected %c", ts->data);
|
||||
if (rbuf[0] != ts->data) {
|
||||
fprintf(stderr, "payload mismatch. expected %c\n", ts->data);
|
||||
errors++;
|
||||
}
|
||||
|
||||
if (llabs(tstop - texpect) > cfg_variance_us) {
|
||||
fprintf(stderr, "exceeds variance (%d us)\n", cfg_variance_us);
|
||||
if (!getenv("KSFT_MACHINE_SLOW"))
|
||||
exit(1);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,8 +261,12 @@ static void start_time_wait(void)
|
||||
return;
|
||||
|
||||
now = gettime_ns(CLOCK_REALTIME);
|
||||
if (cfg_start_time_ns < now)
|
||||
if (cfg_start_time_ns < now) {
|
||||
fprintf(stderr, "FAIL: start time already passed\n");
|
||||
if (!getenv("KSFT_MACHINE_SLOW"))
|
||||
errors++;
|
||||
return;
|
||||
}
|
||||
|
||||
err = usleep((cfg_start_time_ns - now) / 1000);
|
||||
if (err)
|
||||
@@ -513,5 +523,10 @@ int main(int argc, char **argv)
|
||||
else
|
||||
do_test_tx((void *)&cfg_src_addr, cfg_alen);
|
||||
|
||||
return 0;
|
||||
if (errors) {
|
||||
fprintf(stderr, "FAIL: %d errors\n", errors);
|
||||
return KSFT_FAIL;
|
||||
}
|
||||
|
||||
return KSFT_PASS;
|
||||
}
|
||||
96
tools/testing/selftests/drivers/net/so_txtime.py
Executable file
96
tools/testing/selftests/drivers/net/so_txtime.py
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
"""Regression tests for the SO_TXTIME interface.
|
||||
|
||||
Test delivery time in FQ and ETF qdiscs.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from lib.py import ksft_exit, ksft_run, ksft_variants
|
||||
from lib.py import KsftNamedVariant, KsftSkipEx
|
||||
from lib.py import NetDrvEpEnv, bkg, cmd, defer, tc
|
||||
|
||||
|
||||
def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success):
|
||||
"""Main function. Run so_txtime as sender and receiver."""
|
||||
bin_path = cfg.test_dir / "so_txtime"
|
||||
|
||||
tstart = time.time_ns() + 200_000_000
|
||||
|
||||
cmd_addr = f"-S {cfg.addr_v[ipver]} -D {cfg.remote_addr_v[ipver]}"
|
||||
cmd_base = f"{bin_path} -{ipver} -c {clockid} -t {tstart} {cmd_addr}"
|
||||
cmd_rx = f"{cmd_base} {args_rx} -r"
|
||||
cmd_tx = f"{cmd_base} {args_tx}"
|
||||
|
||||
with bkg(cmd_rx, host=cfg.remote, fail=expect_success,
|
||||
expect_fail=(not expect_success), exit_wait=True):
|
||||
cmd(cmd_tx)
|
||||
|
||||
|
||||
def _qdisc_setup(ifname, qdisc, optargs=""):
|
||||
"""Replace root qdisc. Restore the original after the test.
|
||||
|
||||
If the original is mq, children will be of type default_qdisc.
|
||||
"""
|
||||
orig = tc(f"qdisc show dev {ifname} root", json=True)[0].get("kind", None)
|
||||
defer(tc, f"qdisc replace dev {ifname} root {orig}")
|
||||
tc(f"qdisc replace dev {ifname} root {qdisc} {optargs}")
|
||||
|
||||
|
||||
def _test_variants_mono():
|
||||
for ipver in ["4", "6"]:
|
||||
for testcase in [
|
||||
["no_delay", "a,-1", "a,-1"],
|
||||
["zero_delay", "a,0", "a,0"],
|
||||
["one_pkt", "a,10", "a,10"],
|
||||
["in_order", "a,10,b,20", "a,10,b,20"],
|
||||
["reverse_order", "a,20,b,10", "b,20,a,20"],
|
||||
]:
|
||||
name = f"v{ipver}_{testcase[0]}"
|
||||
yield KsftNamedVariant(name, ipver, testcase[1], testcase[2])
|
||||
|
||||
|
||||
@ksft_variants(_test_variants_mono())
|
||||
def test_so_txtime_mono(cfg, ipver, args_tx, args_rx):
|
||||
"""Run all variants of monotonic (fq) tests."""
|
||||
_qdisc_setup(cfg.ifname, "fq")
|
||||
test_so_txtime(cfg, "mono", ipver, args_tx, args_rx, True)
|
||||
|
||||
|
||||
def _test_variants_etf():
|
||||
for ipver in ["4", "6"]:
|
||||
for testcase in [
|
||||
["no_delay", "a,-1", "a,-1", False],
|
||||
["zero_delay", "a,0", "a,0", False],
|
||||
["one_pkt", "a,10", "a,10", True],
|
||||
["in_order", "a,10,b,20", "a,10,b,20", True],
|
||||
["reverse_order", "a,20,b,10", "b,10,a,20", True],
|
||||
]:
|
||||
name = f"v{ipver}_{testcase[0]}"
|
||||
yield KsftNamedVariant(
|
||||
name, ipver, testcase[1], testcase[2], testcase[3]
|
||||
)
|
||||
|
||||
|
||||
@ksft_variants(_test_variants_etf())
|
||||
def test_so_txtime_etf(cfg, ipver, args_tx, args_rx, expect_fail):
|
||||
"""Run all variants of etf tests."""
|
||||
try:
|
||||
_qdisc_setup(cfg.ifname, "etf", "clockid CLOCK_TAI delta 400000")
|
||||
except Exception as e:
|
||||
raise KsftSkipEx("tc does not support qdisc etf. skipping") from e
|
||||
|
||||
test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, expect_fail)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Boilerplate ksft main."""
|
||||
with NetDrvEpEnv(__file__) as cfg:
|
||||
ksft_run([test_so_txtime_mono, test_so_txtime_etf], args=(cfg,))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
tools/testing/selftests/net/.gitignore
vendored
1
tools/testing/selftests/net/.gitignore
vendored
@@ -40,7 +40,6 @@ skf_net_off
|
||||
socket
|
||||
so_incoming_cpu
|
||||
so_netns_cookie
|
||||
so_txtime
|
||||
so_rcv_listener
|
||||
stress_reuseport_listen
|
||||
tap
|
||||
|
||||
@@ -83,7 +83,6 @@ TEST_PROGS := \
|
||||
rxtimestamp.sh \
|
||||
sctp_vrf.sh \
|
||||
skf_net_off.sh \
|
||||
so_txtime.sh \
|
||||
srv6_end_dt46_l3vpn_test.sh \
|
||||
srv6_end_dt4_l3vpn_test.sh \
|
||||
srv6_end_dt6_l3vpn_test.sh \
|
||||
@@ -157,7 +156,6 @@ TEST_GEN_FILES := \
|
||||
skf_net_off \
|
||||
so_netns_cookie \
|
||||
so_rcv_listener \
|
||||
so_txtime \
|
||||
socket \
|
||||
stress_reuseport_listen \
|
||||
tcp_fastopen_backup_key \
|
||||
|
||||
@@ -14,7 +14,7 @@ from .netns import NetNS, NetNSEnter
|
||||
from .nsim import NetdevSim, NetdevSimDev
|
||||
from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
|
||||
bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \
|
||||
wait_file, tool
|
||||
wait_file, tool, tc
|
||||
from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
|
||||
from .ynl import NlError, NlctrlFamily, YnlFamily, \
|
||||
EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily
|
||||
@@ -29,7 +29,7 @@ __all__ = ["KSRC",
|
||||
"NetNS", "NetNSEnter",
|
||||
"CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer",
|
||||
"bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports",
|
||||
"wait_port_listen", "wait_file", "tool",
|
||||
"wait_port_listen", "wait_file", "tool", "tc",
|
||||
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
|
||||
"NetdevSim", "NetdevSimDev",
|
||||
"NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
|
||||
|
||||
@@ -23,6 +23,10 @@ class CmdExitFailure(Exception):
|
||||
self.cmd = cmd_obj
|
||||
|
||||
|
||||
class CmdExitZeroFailure(CmdExitFailure):
|
||||
""" Command succeeded (returned zero exit code), but expected failure. """
|
||||
|
||||
|
||||
def fd_read_timeout(fd, timeout):
|
||||
rlist, _, _ = select.select([fd], [], [], timeout)
|
||||
if rlist:
|
||||
@@ -39,8 +43,9 @@ class cmd:
|
||||
|
||||
Use bkg() instead to run a command in the background.
|
||||
"""
|
||||
def __init__(self, comm, shell=None, fail=True, ns=None, background=False,
|
||||
host=None, timeout=5, ksft_ready=None, ksft_wait=None):
|
||||
def __init__(self, comm, shell=None, fail=True, expect_fail=False, ns=None,
|
||||
background=False, host=None, timeout=5, ksft_ready=None,
|
||||
ksft_wait=None):
|
||||
if ns:
|
||||
comm = f'ip netns exec {ns} ' + comm
|
||||
|
||||
@@ -88,7 +93,8 @@ class cmd:
|
||||
self._process_terminate(terminate=terminate, timeout=1)
|
||||
raise CmdInitFailure("Did not receive ready message", self)
|
||||
if not background:
|
||||
self.process(terminate=False, fail=fail, timeout=timeout)
|
||||
self.process(terminate=False, fail=fail, expect_fail=expect_fail,
|
||||
timeout=timeout)
|
||||
|
||||
def _process_terminate(self, terminate, timeout):
|
||||
if terminate:
|
||||
@@ -102,7 +108,7 @@ class cmd:
|
||||
|
||||
return stdout, stderr
|
||||
|
||||
def process(self, terminate=True, fail=None, timeout=5):
|
||||
def process(self, terminate=True, fail=None, expect_fail=False, timeout=5):
|
||||
if fail is None:
|
||||
fail = not terminate
|
||||
|
||||
@@ -111,10 +117,19 @@ class cmd:
|
||||
|
||||
stdout, stderr = self._process_terminate(terminate=terminate,
|
||||
timeout=timeout)
|
||||
if self.proc.returncode != 0 and fail:
|
||||
|
||||
# Fail on unexpected test failure if fail.
|
||||
# Fail on unexpected test success if expect_fail.
|
||||
# Fail on negative returncode if either:
|
||||
# Set by subprocess on crash or signal, this is never expected failure.
|
||||
if (self.proc.returncode != 0 and fail or
|
||||
(self.proc.returncode < 0 and expect_fail)):
|
||||
if len(stderr) > 0 and stderr[-1] == "\n":
|
||||
stderr = stderr[:-1]
|
||||
raise CmdExitFailure("Command failed", self)
|
||||
elif self.proc.returncode == 0 and expect_fail:
|
||||
raise CmdExitZeroFailure("Command succeeded (expected fail)", self)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
def str_fmt(name, s):
|
||||
@@ -157,14 +172,17 @@ class bkg(cmd):
|
||||
|
||||
with bkg("my_binary", ksft_wait=5):
|
||||
"""
|
||||
def __init__(self, comm, shell=None, fail=None, ns=None, host=None,
|
||||
exit_wait=False, ksft_ready=None, ksft_wait=None):
|
||||
def __init__(self, comm, shell=None, fail=None, expect_fail=None,
|
||||
ns=None, host=None, exit_wait=False, ksft_ready=None,
|
||||
ksft_wait=None):
|
||||
super().__init__(comm, background=True,
|
||||
shell=shell, fail=fail, ns=ns, host=host,
|
||||
ksft_ready=ksft_ready, ksft_wait=ksft_wait)
|
||||
shell=shell, fail=fail, expect_fail=expect_fail,
|
||||
ns=ns, host=host, ksft_ready=ksft_ready,
|
||||
ksft_wait=ksft_wait)
|
||||
self.terminate = not exit_wait and not ksft_wait
|
||||
self._exit_wait = exit_wait
|
||||
self.check_fail = fail
|
||||
self.expect_fail = expect_fail
|
||||
|
||||
if shell and self.terminate:
|
||||
print("# Warning: combining shell and terminate is risky!")
|
||||
@@ -179,7 +197,8 @@ class bkg(cmd):
|
||||
# since forcing termination silences failures with fail=None
|
||||
if self.proc.poll() is None:
|
||||
terminate = terminate or (self._exit_wait and ex_type is not None)
|
||||
return self.process(terminate=terminate, fail=self.check_fail)
|
||||
return self.process(terminate=terminate, fail=self.check_fail,
|
||||
expect_fail=self.expect_fail)
|
||||
|
||||
|
||||
GLOBAL_DEFER_QUEUE = []
|
||||
@@ -220,7 +239,10 @@ class defer:
|
||||
def tool(name, args, json=None, ns=None, host=None):
|
||||
cmd_str = name + ' '
|
||||
if json:
|
||||
cmd_str += '--json '
|
||||
if name == 'tc':
|
||||
cmd_str += '-json '
|
||||
else:
|
||||
cmd_str += '--json '
|
||||
cmd_str += args
|
||||
cmd_obj = cmd(cmd_str, ns=ns, host=host)
|
||||
if json:
|
||||
@@ -238,6 +260,13 @@ def ip(args, json=None, ns=None, host=None):
|
||||
return tool('ip', args, json=json, host=host)
|
||||
|
||||
|
||||
def tc(args, json=None, ns=None, host=None):
|
||||
""" Helper to call tc with standard set of optional args. """
|
||||
if ns:
|
||||
args = f'-netns {ns} ' + args
|
||||
return tool('tc', args, json=json, host=host)
|
||||
|
||||
|
||||
def ethtool(args, json=None, ns=None, host=None):
|
||||
return tool('ethtool', args, json=json, ns=ns, host=host)
|
||||
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Regression tests for the SO_TXTIME interface
|
||||
|
||||
set -e
|
||||
|
||||
readonly ksft_skip=4
|
||||
readonly DEV="veth0"
|
||||
readonly BIN="./so_txtime"
|
||||
|
||||
readonly RAND="$(mktemp -u XXXXXX)"
|
||||
readonly NSPREFIX="ns-${RAND}"
|
||||
readonly NS1="${NSPREFIX}1"
|
||||
readonly NS2="${NSPREFIX}2"
|
||||
|
||||
readonly SADDR4='192.168.1.1'
|
||||
readonly DADDR4='192.168.1.2'
|
||||
readonly SADDR6='fd::1'
|
||||
readonly DADDR6='fd::2'
|
||||
|
||||
cleanup() {
|
||||
ip netns del "${NS2}"
|
||||
ip netns del "${NS1}"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# Create virtual ethernet pair between network namespaces
|
||||
ip netns add "${NS1}"
|
||||
ip netns add "${NS2}"
|
||||
|
||||
ip link add "${DEV}" netns "${NS1}" type veth \
|
||||
peer name "${DEV}" netns "${NS2}"
|
||||
|
||||
# Bring the devices up
|
||||
ip -netns "${NS1}" link set "${DEV}" up
|
||||
ip -netns "${NS2}" link set "${DEV}" up
|
||||
|
||||
# Set fixed MAC addresses on the devices
|
||||
ip -netns "${NS1}" link set dev "${DEV}" address 02:02:02:02:02:02
|
||||
ip -netns "${NS2}" link set dev "${DEV}" address 06:06:06:06:06:06
|
||||
|
||||
# Add fixed IP addresses to the devices
|
||||
ip -netns "${NS1}" addr add 192.168.1.1/24 dev "${DEV}"
|
||||
ip -netns "${NS2}" addr add 192.168.1.2/24 dev "${DEV}"
|
||||
ip -netns "${NS1}" addr add fd::1/64 dev "${DEV}" nodad
|
||||
ip -netns "${NS2}" addr add fd::2/64 dev "${DEV}" nodad
|
||||
|
||||
run_test() {
|
||||
local readonly IP="$1"
|
||||
local readonly CLOCK="$2"
|
||||
local readonly TXARGS="$3"
|
||||
local readonly RXARGS="$4"
|
||||
|
||||
if [[ "${IP}" == "4" ]]; then
|
||||
local readonly SADDR="${SADDR4}"
|
||||
local readonly DADDR="${DADDR4}"
|
||||
elif [[ "${IP}" == "6" ]]; then
|
||||
local readonly SADDR="${SADDR6}"
|
||||
local readonly DADDR="${DADDR6}"
|
||||
else
|
||||
echo "Invalid IP version ${IP}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local readonly START="$(date +%s%N --date="+ 0.1 seconds")"
|
||||
|
||||
ip netns exec "${NS2}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${RXARGS}" -r &
|
||||
ip netns exec "${NS1}" "${BIN}" -"${IP}" -c "${CLOCK}" -t "${START}" -S "${SADDR}" -D "${DADDR}" "${TXARGS}"
|
||||
wait "$!"
|
||||
}
|
||||
|
||||
do_test() {
|
||||
run_test $@
|
||||
[ $? -ne 0 ] && ret=1
|
||||
}
|
||||
|
||||
do_fail_test() {
|
||||
run_test $@
|
||||
[ $? -eq 0 ] && ret=1
|
||||
}
|
||||
|
||||
ip netns exec "${NS1}" tc qdisc add dev "${DEV}" root fq
|
||||
set +e
|
||||
ret=0
|
||||
do_test 4 mono a,-1 a,-1
|
||||
do_test 6 mono a,0 a,0
|
||||
do_test 6 mono a,10 a,10
|
||||
do_test 4 mono a,10,b,20 a,10,b,20
|
||||
do_test 6 mono a,20,b,10 b,20,a,20
|
||||
|
||||
if ip netns exec "${NS1}" tc qdisc replace dev "${DEV}" root etf clockid CLOCK_TAI delta 400000; then
|
||||
do_fail_test 4 tai a,-1 a,-1
|
||||
do_fail_test 6 tai a,0 a,0
|
||||
do_test 6 tai a,10 a,10
|
||||
do_test 4 tai a,10,b,20 a,10,b,20
|
||||
do_test 6 tai a,20,b,10 b,10,a,20
|
||||
else
|
||||
echo "tc ($(tc -V)) does not support qdisc etf. skipping"
|
||||
[ $ret -eq 0 ] && ret=$ksft_skip
|
||||
fi
|
||||
|
||||
if [ $ret -eq 0 ]; then
|
||||
echo OK. All tests passed
|
||||
elif [[ $ret -ne $ksft_skip && -n "$KSFT_MACHINE_SLOW" ]]; then
|
||||
echo "Ignoring errors due to slow environment" 1>&2
|
||||
ret=0
|
||||
fi
|
||||
exit $ret
|
||||
Reference in New Issue
Block a user