Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make testing more reliable and realistic #87

Closed
wants to merge 2 commits into from

Conversation

DavisVaughan
Copy link
Collaborator

Closes #79, alternative to #80 until we can fully move to log:: as our LSP log handler.

  • Use OnceLock for AUXILIARY_EVENT_TX because it can't be set more than once in real air usage. This also has the benefit of removing the static mut rustc yells at us about.

  • Move LSP client unit tests to integration tests. This ensures they run sequentially and in their own process. This more accurately represents real usage and avoids writing to AUXILIARY_EVENT_TX multiple times (or overwriting it mid test) at the cost of more expensive test setup, but I'm ok with that tradeoff because of how closely this mimics real usage.

  • Add TESTING global to turn off logs during integration testing. Emitting log notifications to the Client during testing is problematic because we use recv_response() to track request/response lifecycle and log requests end up getting in the way. Ideally I think we'd find a way to filter out the logs and keep "recv()"-ing instead of using a global like this to avoid them entirely. Remember we can't use cfg(test) during integration tests (only unit tests), so this was my next best alternative.

- Use `OnceLock` for `AUXILIARY_EVENT_TX` because it can't be set more than once in real air usage. This also has the benefit of removing the `static mut` rustc yells at us about.

- Move LSP client tests to integration tests. This ensures they run sequentially and in their own process. This more accurately represents real usage and avoids writing to `AUXILIARY_EVENT_TX` multiple times (or overwriting it mid test).

- Add `TESTING` global to turn off logs during testing. Emitting log notifications to the Client during testing is problematic because we use `recv_response()` to track request/response lifecycle and log requests end up getting in the way. Ideally I think we'd find a way to filter out the logs and keep "recv()"-ing instead of using a global like this to avoid them entirely.
lsp::start_lsp(tokio::io::stdin(), tokio::io::stdout()).await;
lsp::start_server(tokio::io::stdin(), tokio::io::stdout()).await;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to rename this back but i got a little confused seeing TestClient accept a lambda function that called start_lsp(). I thought start_lsp() did something client related when I saw that.

Comment on lines -381 to +383
unsafe {
#[allow(static_mut_refs)]
if let Some(val) = AUXILIARY_EVENT_TX.get_mut() {
// Reset channel if already set. Happens e.g. on reconnection after a refresh.
*val = auxiliary_event_tx;
} else {
// Set channel for the first time
AUXILIARY_EVENT_TX.set(auxiliary_event_tx).unwrap();
}
}
AUXILIARY_EVENT_TX
.set(auxiliary_event_tx)
.expect("Auxiliary event channel can't be set more than once.");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the very important realization that unlike in the current ark setup, we won't ever go through here twice in air. There is no "reconnect" lifecycle to manage, our whole process is just going to get dropped and reestablished, so we don't have to worry about this anymore.

This was being triggered in our tests though - even if we ran the tests sequentially we'd come through here multiple times as we "start up" a new LSP within the same process. I've fixed that by running the LSP tests as integration tests with 1 client per process, truly ensuring we never run through here again.

Comment on lines -381 to +383
unsafe {
#[allow(static_mut_refs)]
if let Some(val) = AUXILIARY_EVENT_TX.get_mut() {
// Reset channel if already set. Happens e.g. on reconnection after a refresh.
*val = auxiliary_event_tx;
} else {
// Set channel for the first time
AUXILIARY_EVENT_TX.set(auxiliary_event_tx).unwrap();
}
}
AUXILIARY_EVENT_TX
.set(auxiliary_event_tx)
.expect("Auxiliary event channel can't be set more than once.");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notably you only see the expect() error when running with -- --nocapture.

This is because if the auxiliary or main loop threads panic, then we don't propagate the panic up to the main process at all. Tokio will return a JoinError if we actually await the JoinSet of the aux and main loops, we just need to do so in start_server() while we also do server.serve(service).await;. I couldn't easily figure that out here so I'm saving that for another PR.

But note that with this PR cargo test -- --nocapture no longer shows any panics, but did reliably before.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the "old" style mod.rs here in fixtures/ is the recommended way to have integration test helpers
https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests

@DavisVaughan DavisVaughan requested a review from lionel- December 9, 2024 22:46
Nicely isolates the global
Copy link
Collaborator

@lionel- lionel- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've decided to keep tests as unit tests because that is much more ergonomic for developing them.

To solve the global state issues we're going to:

  • Move the thread spawner to local state
  • Move the transmission channel for log messages to local state in the tracing crate configuration. This will be for non test builds. For test builds we'll just log to stderr.

This way we keep the possibility of writing tests as part of development files while still allowing tests to run in parallel. And we keep logging as free functions/macros that don't require passing state around.

@DavisVaughan
Copy link
Collaborator Author

Replaced by #80 and #93

@DavisVaughan DavisVaughan deleted the feature/once-lock branch December 11, 2024 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

test_format_minimal_diff is not reliable
2 participants