runtime: improvement/test struct for api mode watcher - #2022
runtime: improvement/test struct for api mode watcher#2022daemonfire300 wants to merge 19 commits into
Conversation
2c81908 to
ea52878
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2022 +/- ##
=======================================
+ Coverage 77.3% 78.1% +0.8%
=======================================
Files 89 94 +5
Lines 9016 9220 +204
=======================================
+ Hits 6967 7198 +231
+ Misses 2049 2022 -27
🚀 New features to boost your workflow:
|
|
cc @mateiidavid since you have been on the old PR. |
|
I ll try to do a proper fix of the clippy issues tomorrow. Caught me by surprise because afaik |
e9269b5 to
e932559
Compare
|
Don't worry about the cc) @clux should we consider CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS ? |
Signed-off-by: Julius Foitzik <info@accountr.eu>
Issue: kube-rs#1528 Signed-off-by: Julius Foitzik <info@accountr.eu>
Signed-off-by: Julius Foitzik <info@accountr.eu>
Signed-off-by: Julius Foitzik <info@accountr.eu>
Signed-off-by: Julius Foitzik <info@accountr.eu>
Signed-off-by: Julius Foitzik <info@accountr.eu>
e932559 to
b14d9f2
Compare
|
Any input on this so far? |
|
switched to draft as it technically is a DRAFT. |
Signed-off-by: Julius Foitzik <info@accountr.eu>
Signed-off-by: Julius Foitzik <info@accountr.eu>
There was a problem hiding this comment.
thanks a lot for trying this. some initial comments.
this is definitely on the complex side, but the watcher is also complex, so it's not unexpected. the streaming setup and bookeeping seems very sensible, my biggest source of confusion and hesitation is with the input structures (for fixtures and sequence), and curious if there are alternatives along those lines.
i've put down some comments. mostly thinking out loud for now. still need some time to think about this. don't feel like you have to reply to all of it.
| enum Recording { | ||
| List(ListParams), | ||
| Watch(WatchParams, String), | ||
| } |
There was a problem hiding this comment.
I like the recorder and the way you are storing them within the test mode.
Some documentation on how it works and and what the strings represents would be useful.
| pub struct TestStream<K> { | ||
| seq: RefCell<Vec<Sequence<K>>>, | ||
| waiting: Option<Pin<Box<tokio::time::Sleep>>>, | ||
| } |
There was a problem hiding this comment.
afaikt TestStream is exclusively dedicated for streaming things for .watch?
What happens in the case of errors and we need to return to an initial list?
There was a problem hiding this comment.
Thanks for the remark: I think TestStream is insufficient. I re-read my implementation mutliple times now and TestStream only allows to model the outer happy path, not the outer Err case and internally it allows for both.
I.e., any call to watch will succeed if we would use it as is, which might not be desired/missing functionality. Will put that on my list of things to fix.
The part of "return an initial list" is, however, not clear to me. Either it is an error or we return a list, this should be this section https://github.com/daemonfire300/kube/blob/817092633873d7d84d41c3a8c10e64fd02acf07f/kube-runtime/src/watcher.rs#L674-L687 unless I am missing something
flowchart TD
testMode["TestMode"]
watch["watch"]
sequences["inner Vec Sequence<...>"]
error["Err(...)"]
success["Ok(TestStream)"]
testMode --- watch
watch --> sequences
watch -->|missing| error
sequences -->|pop Vec Seq and put into Stream| success
style error stroke:#ff3333,color:#ff3333,stroke-width:2px
style success stroke:#20a83a,color:#20a83a,stroke-width:2px
There was a problem hiding this comment.
The behaviour of cosntructing the TestStream on a call to watch is broken. I have Vec<Seq<..>> and pop from it, and then I think mistakenly create a single element Vec<Seq<..>> off of that and put it into the stream which is (a) confusing and (b) not what I intended.
There was a problem hiding this comment.
I also need to fix the behaviour of poll_next, Poll::Ready(None) terminates the stream instead of returning an empty result (the case for Sequence::Empty), which is confusing and "wrong".
There was a problem hiding this comment.
maybe it helps to write some failing tests for the last cases for what you want first so you can hammer on the complex (sounding) behavior.
if the internals in the stream impl ends up being a bit ugly to accommodate the various test scenarios, that would still be a good tradeoff.
Thank you very(!) much for the thorough review. Yes the tests are the weakest part. Will go over your points and (a) try to address them and (b) make things easier to understand. |
| self.recorder | ||
| .borrow_mut() | ||
| .push(Recording::Watch(wp.clone(), version.into())); | ||
| let seq = self.watch_sequence.borrow_mut().pop().into_iter().collect(); |
There was a problem hiding this comment.
### NOTE: This still needs to addressed, because it's not necessarily proper behavior.
Signed-off-by: Julius Foitzik <info@accountr.eu>
Signed-off-by: Julius Foitzik <info@accountr.eu>
* intentionally did not put it into the util dir/mod to stop this becoming sink for everything unrelated * refactor to have ergonomic ResultPage struct for tests * Removed Sequence::Empty because it made no sense and replaced it with Sequence::Terminate Signed-off-by: Julius Foitzik <info@accountr.eu>
"terminated" streams should continue to return None `fuse()` is a helper to achive this see [https://docs.rs/futures/0.3.33/futures/stream/trait.StreamExt.html#method.fuse] Signed-off-by: Julius Foitzik <info@accountr.eu>
Signed-off-by: Julius Foitzik <info@accountr.eu>
a682304 to
6f5702d
Compare
|
I think this looks a lot better now. The tests read better, and are mostly understandable from reading the tests alone without having to dig deep into the implementation. You've addressed a lot of my concerns. I don't have many more bikesheds on the actual code bits themselves, but I do think it would be interesting to try to add 2 more test cases to see if the abstraction can handle reality. Particularly around error handling and initial lists;
|
clux
left a comment
There was a problem hiding this comment.
tiny nits. mostly about documentation.
| /// Arguments are mut because we reverse the order internally because we pop off the end | ||
| /// which means the first element to be returned would the last which would be unexpected. |
There was a problem hiding this comment.
if that's the reason you could maybe use a VecDeque slightly more ergonomically. given perf isn't really a concern here.
There was a problem hiding this comment.
It was VecDeque initially, then I thought technically we only need to remove (pop) elements and to not need the whole "baggage" VecDeque comes with. I have little experience w.r.t. to VecDeque vs Vec with small data but a lot of objects. I guess I am still paranoid because I got stung very much by HashMap in a private project. But you suggestion sounds sensible. 👍
There was a problem hiding this comment.
end result looks a little cleaner 👍
There was a problem hiding this comment.
doc comment outdated now though (no mut, we don't reverse anymore)
its waker Signed-off-by: Julius Foitzik <info@accountr.eu>
Built a reference test, and I think this is broken by default by design, when I "re-use" my list case but port it to watch instead.
I must say, this is exciting 👍 (to be read unironically) |
|
There was a bug in TestMode watch, fixed it, I was returning only the first element of a SequenceStep::List and discarding the rest. As for the infinite loop on stream end: I am going to choose an error and return that so that any test using this does not hang. The alternative is having a specific error/code path which is guarded by #[cfg(test)] but I would not like to put test specific code mixed into the heart of the |
…List and only every returning the first entry discarding the rest Signed-off-by: Julius Foitzik <info@accountr.eu>
7b4b739 to
ee71e06
Compare
|
most likely some helper/marker/tombstone error like this fn exhausted_watch_sequence() -> kube_client::Error {
kube_client::Error::ReadEvents(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"TestMode watch sequence exhausted",
))
} |
Signed-off-by: Julius Foitzik <info@accountr.eu>
| let (event, _) = tokio::time::timeout(Duration::from_millis(100), step(&api, &config, state)) | ||
| .await | ||
| .expect("step should return when the TestMode sequence is exhausted"); | ||
| assert!(matches!( | ||
| event, | ||
| Err(Error::WatchFailed(kube_client::Error::ReadEvents(err))) | ||
| if err.kind() == std::io::ErrorKind::UnexpectedEof | ||
| )); |
There was a problem hiding this comment.
this error matching to handle the stream is "over" could perhaps be encasulated a bit better so the tests are more readable (since it's a magic value in the error).
| #[tokio::test] | ||
| async fn streaming_list_init() { |
There was a problem hiding this comment.
I think we need one final test here that verifies the working -> error -> resync setup. ie.
#[tokio::test]
async fn streaming_list_resync() {
where (pseudocode)
for expected in [
"InitApply(a)",
"InitApply(b)",
"InitDone",
"Apply(a)",
"Error(WatchFailed)",
"InitApply(a),
]
so we can simulate the internal step machinery resuming properly in the case of errors.
Motivation
Motivation is #1528
Instead of reviving the stale PR I attempted a new approach from scratch.
Solution
IMPORTANT NOTE: Contains unrelated files that are currently tracked in git because they have to be otherwise my devShell does not work (flake, nix, direnv). So feel free to ignore the flake.nix+flake.lock, if you want me to and like the nix devShell I can put that on a dedicated second PR. Also added a helper and git hooks for myself to have a Cargo.lock on hand, because I prefer having one when developing within a nix devShell, makes it more reproducible. I.e., I am going to remove those files from the PR later.It's not much and the change is NOT complete, before continuing I wanted to discuss the "interface" and behaviour of test struct(s). For example does the Duration i.e., ability to sleep/wait between calls to watch make sense, are we missing something to model a certain test. (Also I am not 100% sure whether the location of where I put the tests is the desired location for using that test struct.)
Happy to receive feedback.