Repository navigation
Avoid running uv format in untrusted workspaces - #28873
Conversation
Summary -- This PR adds the `--untrusted-workspace` flag to the server CLI, which informs the server that it's running in an untrusted workspace. When this is the case, we fall back on the internal formatting backend instead of using `uv format` to avoid reading uv configuration and potentially installing packages based on it. Test Plan -- New server tests
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 98.24%. The percentage of expected errors that received a diagnostic held steady at 98.24%. The number of fully passing files held steady at 134/146. |
Memory usage reportMemory usage unchanged ✅ |
|
|
| match Server::new( | ||
| worker_threads, | ||
| server_connection, | ||
| None, | ||
| WorkspaceTrust::Trusted, | ||
| true, | ||
| ) { |
There was a problem hiding this comment.
Could we add a TestServer::untrusted factory method with an internal, shared new_impl? Or are there other reasons why it can't be a e2e test?
There was a problem hiding this comment.
My understanding is that the main issue is determining what to assert at the end of the test, not setting up the test server initially. There's no user-visible output that we're using one backend or the other (except the new logging call). So it makes it hard to assert something useful in the e2e tests. But you'd know better about our e2e test capabilities than me, I could certainly be missing something.
Codex started producing something like this when I asked for an e2e test, which is what I thought looked too janky to include:
fn without_uv(body: impl FnOnce() -> Result<()>) -> Result<()> {
const CHILD: &str = "RUFF_TEST_ISOLATED_CHILD";
let thread = std::thread::current();
let name = thread.name().context("missing test name")?;
if std::env::var(CHILD).as_deref() == Ok(name) {
return body();
}
let empty_path = tempfile::tempdir()?;
let output = Command::new(std::env::current_exe()?)
.args(["--exact", name, "--nocapture"])
.env(CHILD, name)
.env("PATH", empty_path.path())
.current_dir(empty_path.path())
.output()?;
ensure!(output.status.success(), "{}\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
println!("{}", String::from_utf8_lossy(&output.stdout));
Ok(())
}plus another ~50 lines of helpers.
There was a problem hiding this comment.
We can of course assert that the code gets formatted somehow, which Codex also tried initially, but that didn't seem very useful, as both backends format code.
There was a problem hiding this comment.
Can you override path and have uv format fail?
There was a problem hiding this comment.
Yes, that's what Codex's snippet above was trying to do. I'll just push its patch so you can see what you think. Maybe it's not as bad as I thought.
There was a problem hiding this comment.
Ah I see, ty's server tests have more indirection here, through TestServerBuilder::with_env_var and the system stuff. Here, we end up calling straight into Command::new("uv"), which makes it much harder to inject environment variables at the right point, or without modifying other tests, which is what Codex is trying to avoid.
Anyway, I've included Codex's e2e test scheme. We could also do something more like ty, although it feels like that probably belongs in a separate PR.
There was a problem hiding this comment.
Ahh, I forgot that we're in Ruff's server where we don't have the ability to easily override commands. Hmm, I don't see a great solution other than plumbing through the argument somehow (e.g. by storing a CommandExecutor on Server). I'm not sure this is worth it for this change, or, at least, might be better as a follow up. Thanks for giving it a try
There was a problem hiding this comment.
No problem! I'll revert this last commit and merge then :)
MichaReiser
left a comment
There was a problem hiding this comment.
Sorry. It looks like I forgot to submit my feedback on Friday.
| /// Isolate PATH from other tests, which run their servers in the same process. | ||
| /// Without uv, formatting only succeeds if the internal backend is used. | ||
| fn without_uv(body: impl FnOnce() -> Result<()>) -> Result<()> { | ||
| const CHILD: &str = "RUFF_TEST_ISOLATED_CHILD"; | ||
| let thread = std::thread::current(); | ||
| let name = thread.name().context("missing test name")?; | ||
| if std::env::var(CHILD).as_deref() == Ok(name) { | ||
| return body(); | ||
| } | ||
|
|
||
| let empty_path = tempfile::tempdir()?; | ||
| let output = Command::new(std::env::current_exe()?) | ||
| .args(["--exact", name, "--nocapture"]) | ||
| .env(CHILD, name) | ||
| .env("PATH", empty_path.path()) | ||
| .current_dir(empty_path.path()) | ||
| .output()?; | ||
| let stdout = String::from_utf8_lossy(&output.stdout); | ||
| // An unmatched `--exact` filter exits successfully without running any tests. | ||
| ensure!( | ||
| output.status.success() && stdout.contains("test result: ok. 1 passed;"), | ||
| "{stdout}\n{}", | ||
| String::from_utf8_lossy(&output.stderr), | ||
| ); | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
This feels a bit crazy to me. Seems a bit annoying to debug and keep compatibility.
| match workspace_trust { | ||
| WorkspaceTrust::Trusted => { | ||
| assert_matches!( | ||
| document, | ||
| Err(AwaitResponseError::RequestFailed(error)) | ||
| if error.message.contains("uv was not found"), | ||
| ); | ||
| assert_matches!( | ||
| range, | ||
| Err(AwaitResponseError::RequestFailed(error)) | ||
| if error.message.contains("uv was not found"), | ||
| ); | ||
| server.await_notification::<ShowMessageNotification>(); | ||
| server.await_notification::<ShowMessageNotification>(); | ||
| } | ||
| WorkspaceTrust::Untrusted => { | ||
| let expected = Some(vec![TextEdit { | ||
| range: Range::new(Position::new(0, 0), Position::new(1, 0)), | ||
| new_text: "x = 1\n".to_string(), | ||
| }]); | ||
| assert_eq!(document?, expected); | ||
| assert_eq!(range?, expected); | ||
| } | ||
| } |
There was a problem hiding this comment.
I think I'd prefer a helper function that sets up the test case and then have two separate tests over test_case + a match here
| match Server::new( | ||
| worker_threads, | ||
| server_connection, | ||
| None, | ||
| WorkspaceTrust::Trusted, | ||
| true, | ||
| ) { |
There was a problem hiding this comment.
Ahh, I forgot that we're in Ruff's server where we don't have the ability to easily override commands. Hmm, I don't see a great solution other than plumbing through the argument somehow (e.g. by storing a CommandExecutor on Server). I'm not sure this is worth it for this change, or, at least, might be better as a follow up. Thanks for giving it a try
This reverts commit f18ceda.
Summary
This PR adds the
--untrusted-workspaceflag to the server CLI, which informs the server that it's running in an untrusted workspace. When this is the case, we fall back on the internal formatting backend instead of usinguv formatto avoid reading uv configuration and potentially installing packages based on it.Test Plan
New server tests