Regression tests for swadm - #350
Conversation
The comment mentions a time for deletion, and that time has come. These tests have been ignored in CI for at least a year and reference a CLI command that no longer exists.
The tests CI previously evaded were deleted below.
Decisions: - Use `std::process::Command` and string parsing instead of dpd dropshot endpoints or a --parsable flag. The goal of swadm regression tests is stability on the string typed interface. - But keep this in rust because bash tests would quickly get out of hand. - Keeping utils in the tests dir hopefully atones for putting hideous regexes in swadm.
It was a fair critique from agent review, but I'd rather not solve a problem that doesn't exist. Possible future footgun, so I left a comment.
Otherwise we build the same cmd library for every test, which is prone to erroneous dead code warnings.
swadm tests now mutate switch state, so they shouldn't precede dpd-client. A cleaner design might put swadm tests in their own job, but that's overkill until the suite is more expansive.
Some writes through DPD's reconciler propagate asynchronously, which makes write-then-read tests prone to race conditions (if unlikely). This adds a repeat timer in sensitive reads to avoid flakiness.
- Split consts out of `retry_with` in cmd - Add comments making parsing and validation more comprehensible
d4fba7d to
542ba2b
Compare
02998ee to
619462a
Compare
🤖 Claude reviewReview: dendrite#350 — swadm CLI integration testsNice cleanup — folding the loose Most of what follows is about when these tests run and how they 1.
|
The remaining items are indeed smaller things. Leaving as-is for now. |
45aa4d9 to
5d06da0
Compare
1809df9 to
ebdf4d0
Compare
🤖 Claude ReviewRe-reviewed at Verified on a live rig (helios
New:
|
| what | time |
|---|---|
swadm link ls, swadm link serdes get txeq |
30–35 ms |
link delete → link get returns 404 |
78 ms |
link apply → tx-eq visible in get txeq |
80 ms |
link serdes set txeq → new value visible, 3 runs |
109 ms, 539 ms, 122 ms |
That 539 ms is the problem. A single swadm invocation occasionally takes about
half a second on an idle machine, which is the entire budget. When that happens
retry silently degrades to one attempt and the test becomes exactly the
read-once race the helper exists to prevent — and it fails as an assertion
mismatch, which reads as a dpd bug rather than as a timeout.
It doesn't bite today because convergence is fast enough that the first read
almost always succeeds, so the retry path is nearly dead code. It gets exercised
precisely when CI is slow, which is when the budget is smallest.
Suggest seconds rather than milliseconds — from_secs(5) still fails fast at
roughly 50 attempts. The in-repo precedent for this same class of wait is
dpd-client/tests/integration_tests/counters.rs:42: 20 iterations at 100 ms,
2 s total, for SDE lag after a counter write.
Nit in the same block: the doc comment at cmd.rs:88 says "reasonable linear
backoff", but SLEEP is a constant 100 ms. Either drop the word or make it one.
Closed
1. from_secs(500) typo — fixed.
2. Setup before link apply — delete_link() is the right fix, and for a
slightly better reason than I gave. link apply is idempotent for settings,
but not for enabled: add_link sets link.config.enabled = true
(dpd/src/port_settings.rs:338), while modify_link can't — LinkSpec has no
enabled field at all. Both tx-eq push sites gate on link.config.enabled
(dpd/src/link.rs:1856, :1965). Deleting first forces the add branch every
time, so the tests no longer depend on who enabled rear0/0 last.
4. ANY separators — agreed, and SPACE fixes more than it looks like.
With mandatory \s+ between members, interior tokens are now self-bounding:
main\s+-1\s+ can't match -13, because 3 isn't whitespace. That was the
substance of my "10 matches 100" complaint and it's resolved. Only the first and
last members are still unbounded, which needs a pat! whose outermost member is
a bare number to matter — worth remembering, not worth code today.
For the record, the 404 failure you hit was the trailing ANY demanding
whitespace after Not Found where the output has ;. Dropping the ANYs was
right: expect_line uses unanchored is_match, so a leading or trailing ANY
can only ever be a no-op or a false negative.
5. swadm_exact escape hatch — the disclaimer at cmd.rs:54 is the right
call. Agreed the API quality is worth more than a problem nobody has.
6. Error::Proc — fine as is.
7. Fail-fast on dpd-client — agreed, and it's the better default. One
consequence worth holding in your head: a red packet-test job now means "swadm
untested", not "swadm fine".
8. README wording — fair, dropped.
Not a request, just a note
On teardown (3): agreed that setup is the contract and that both swadm suites
now honor it. The asymmetry to be aware of is that dpd-client does not —
Switch::init reads the MAC of every configured port and panics on failure
(dpd-client/tests/integration_tests/common.rs:493-505), with no setup of its
own, so it inherits whatever the last writer left behind.
In CI that's safe, because dpd-client now runs first. On a dev rig it means
swadm-then-packet-tests panics with failed to get mac for port rear0/0 while
packet-tests-then-swadm is fine. That's a legitimate constraint to accept — I'd
just rather it be a known one than a surprise someone debugs from scratch.
c7dc0ee to
f8869e0
Compare
f8869e0 to
995de63
Compare
| use anyhow::bail; | ||
| use regex::Regex; | ||
|
|
||
| const SWADM: &str = env!("CARGO_BIN_EXE_swadm"); |
There was a problem hiding this comment.
It looks like this is pre-existing, but do you have any idea why this is mixed case?
There was a problem hiding this comment.
This was new to me as well. Seems imposed by Cargo: https://doc.rust-lang.org/cargo/reference/environment-variables.html#:~:text=cleaned%20between%20builds.-,CARGO_BIN_EXE_,-%3Cname%3E%20%E2%80%94%20The
| // Modify these or make them configurable if a tested command | ||
| // ever requires longer than `TIMEOUT` to converge. This just | ||
| // avoids requiring more args if nobody cares. | ||
| const TIMEOUT: Duration = Duration::from_secs(2); | ||
| const SLEEP: Duration = Duration::from_millis(100); |
There was a problem hiding this comment.
You could always move sleep/timeout into args and expose a thin wrapper (fn or macro) that supplies the default. Not something I feel strongly about though, so feel free to ignore me if you disagree
There was a problem hiding this comment.
Haha I often write comments trying to exonerate suspicious code only to realize later the comment could just be replaced by less suspicious code. Seems the case here. Thx!
retry now just calls a function retry_with with these defaults.
| /// Anything up until the next match. | ||
| pub const ANY: Pattern = Pattern::regex(r".*?"); |
There was a problem hiding this comment.
This matches 0 or more characters, right? Is that worth calling out in the comment, since the others call out 1 being the minimum match length?
|
|
||
| impl AsRef<str> for Output { | ||
| fn as_ref(&self) -> &str { | ||
| let (Self::Stdout(txt) | Self::Stderr(txt)) = self; |
There was a problem hiding this comment.
this is slick, I didn't know you could do this inside of let destructuring
| const TXEQ_STDOUT: &str = " | ||
| lane 0 lane 1 lane 2 lane 3 | ||
| pre2 0 (111) 1 ( 11) 2 ( 1) 3 ( 11) | ||
| pre1 -1 ( 11) -2 ( 11) -3 ( 11) -4 ( 11) | ||
| main 19 ( 11) 20 ( 11) 21 ( 11) 22 ( 11) | ||
| post1 -2 ( 1) -13 ( -2) -9 (-11) -22 (-123) | ||
| post2 -123 ( 11) 456 ( 11) 0 ( 11) 0 ( 11) | ||
| "; |
There was a problem hiding this comment.
This feels like it would be a good use case for expectorate
There was a problem hiding this comment.
I agree that this particular case is a good use of expectorate. However since it's a test for the actual matcher, using that instead of expectorate is the whole point.
That begs whether expectorate is a better tool than expect_line. I'm putting a comment in the next commit, but my guess is not.
Tx eq is a good motivating case because it's arbitrarily structured and exposes uncontrolled state.
In this case, the values outside parentheses are what we have commanded, and afaik the values inside parentheses are what the hardware itself has decided to do. This occurs in the case of self optimizing tx eq. So we only want to assert the values outside parentheses controlled by swadm command. Hence the PARENS matches asserting that something exists there, but idk what's inside.
In my understanding, expectorate does exact matching, which makes it a poor fit for a fuzzy assert like this. Is that accurate?
There was a problem hiding this comment.
Here's another plausible design:
- Get swadm output into a machine readable format. Either by adding formatted output options or building parsing infra.
- Allow tests to restructure that output to extract unnecessary tokens.
- Store known good versions of that output in files, and use expectorate for diffing.
However, unless someone feels strongly for a solution of that form, I'm disinclined because it seems like more required code per each new test.
| cmd::retry(|| { | ||
| let tx_eq = cmd::swadm(format!("link serdes get txeq {LINK}"))?; | ||
| for label in ["pre2", "pre1", "main", "post1", "post2"] { | ||
| tx_eq.expect_line(pat![ | ||
| label, VAL, PARENS, VAL, PARENS, VAL, PARENS, VAL, PARENS | ||
| ])?; | ||
| } | ||
|
|
||
| Ok(()) | ||
| }) |
There was a problem hiding this comment.
Similar feedback here about expectorate. As someone unfamiliar with the output of swadm link serdes get txeq, I have no idea what this test is looking for. I think being able to look at some output that's checked into get as a comparison would make it much simpler to reason about the test.
There was a problem hiding this comment.
I'm not yet convinced about expectorate, but this is a good point. I'll add an EXAMPLE comment.
There was a problem hiding this comment.
Sure, no worries. I don't know expectorate super well, but I've seen it used for command output checking, both structured and unstructured, so I thought it would be worth evaluating. If you don't feel like it's a good fit, that's fine by me
There was a problem hiding this comment.
Sorry for the repeated pings on this. Looks like we use expectorate in a lot of other crates. If I want people to contribute tests, probably good to use familiar tooling. I'll look for a way to make it work conveniently. Thx for suggesting.
| /// Verifies that the `tx-eq` shorthand and explicit | ||
| /// tap flags are mutually exclusive. |
There was a problem hiding this comment.
what are tap flags? I'm not sure what this test is doing without digging into swadm to figure out what the tap flags are. Maybe a reference to a command, source file, or type name would be helpful?
There was a problem hiding this comment.
This is referring to the flags like --main or --pre1.
The term "tap" comes from signal processing, and refers to one shifted copy of the signal we're operating on. So --pre1 is the gain on a copy of the signal shifted backward in time by one sample. It's one of the gains in the filter we apply to the signal to try to improve quality.
There was a problem hiding this comment.
I'm generally against unwarranted jargon. But tap seems pretty well standardized in tx eq literature, and it's a super convenient name 😅
There was a problem hiding this comment.
Yeah, the term tap holds roughly the same position in signal processing as process does in software. If you're mucking with it, you know what it means :)
taspelund
left a comment
There was a problem hiding this comment.
Overall, the changes LGTM. I would say that the main bit of feedback I have is that it's hard to track what the tests are doing without already knowing that area of the code (what is a tap flag? what does the expected output look like?).
I'd like you to take a look at expectorate and see if that would be a good fit for some of these tests. If it doesn't make sense in this case, then maybe just a multi-line comment showing what swadm output the test is comparing against would be helpful.
528cef5 to
6bc96d2
Compare
swadmis a great tool, and I would like it to continuously improve. While working on #145, I observed how easy (and tempting!) it is to make breaking changes. However, a quick search suggests breaking changes would not be well received by scripts and docs. Such changes may be needed someday, but that should be a careful decision and certainly not an accident.This PR adds infrastructure for regression testing swadm changes in CI. These diffs focus on the tx eq settings I'll soon be modifying, but hopefully the structure is easy to extend as other swadm projects arise.