Compare commits
1 Commits
jack/loop-
...
zb/virtual
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1c6e2e26a |
@@ -1,5 +1,6 @@
|
|||||||
use glob::PatternError;
|
use glob::PatternError;
|
||||||
use ruff_notebook::{Notebook, NotebookError};
|
use ruff_notebook::{Notebook, NotebookError};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -20,18 +21,47 @@ use super::walk_directory::WalkDirectoryBuilder;
|
|||||||
///
|
///
|
||||||
/// ## Warning
|
/// ## Warning
|
||||||
/// Don't use this system for production code. It's intended for testing only.
|
/// Don't use this system for production code. It's intended for testing only.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct TestSystem {
|
pub struct TestSystem {
|
||||||
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
|
||||||
|
/// Environment variable overrides. If a key is present here, it takes precedence
|
||||||
|
/// over the inner system's environment variables.
|
||||||
|
env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for TestSystem {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
env_overrides: self.env_overrides.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestSystem {
|
impl TestSystem {
|
||||||
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(inner),
|
inner: Arc::new(inner),
|
||||||
|
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets an environment variable override. This takes precedence over the inner system.
|
||||||
|
pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
|
||||||
|
self.env_overrides
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(name.into(), Some(value.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an environment variable override, making it appear as not set.
|
||||||
|
pub fn remove_env_var(&self, name: impl Into<String>) {
|
||||||
|
self.env_overrides
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(name.into(), None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`InMemorySystem`].
|
/// Returns the [`InMemorySystem`].
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## Panics
|
||||||
@@ -147,6 +177,18 @@ impl System for TestSystem {
|
|||||||
self.system().case_sensitivity()
|
self.system().case_sensitivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
|
||||||
|
// Check overrides first
|
||||||
|
if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
|
||||||
|
return match override_value {
|
||||||
|
Some(value) => Ok(value.clone()),
|
||||||
|
None => Err(std::env::VarError::NotPresent),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Fall back to inner system
|
||||||
|
self.system().env_var(name)
|
||||||
|
}
|
||||||
|
|
||||||
fn dyn_clone(&self) -> Box<dyn System> {
|
fn dyn_clone(&self) -> Box<dyn System> {
|
||||||
Box::new(self.clone())
|
Box::new(self.clone())
|
||||||
}
|
}
|
||||||
@@ -156,6 +198,7 @@ impl Default for TestSystem {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(InMemorySystem::default()),
|
inner: Arc::new(InMemorySystem::default()),
|
||||||
|
env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2703,3 +2703,51 @@ fn pythonpath_multiple_dirs_is_respected() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test behavior when `VIRTUAL_ENV` is set but points to a non-existent path.
|
||||||
|
#[test]
|
||||||
|
fn missing_virtual_env() -> anyhow::Result<()> {
|
||||||
|
let working_venv_package1_path = if cfg!(windows) {
|
||||||
|
"project/.venv/Lib/site-packages/package1/__init__.py"
|
||||||
|
} else {
|
||||||
|
"project/.venv/lib/python3.13/site-packages/package1/__init__.py"
|
||||||
|
};
|
||||||
|
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
(
|
||||||
|
"project/test.py",
|
||||||
|
r#"
|
||||||
|
from package1 import WorkingVenv
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"project/.venv/pyvenv.cfg",
|
||||||
|
r#"
|
||||||
|
home = ./
|
||||||
|
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
working_venv_package1_path,
|
||||||
|
r#"
|
||||||
|
class WorkingVenv: ...
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command()
|
||||||
|
.current_dir(case.root().join("project"))
|
||||||
|
.env("VIRTUAL_ENV", case.root().join("nonexistent-venv")), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
ty failed
|
||||||
|
Cause: Failed to discover local Python environment
|
||||||
|
Cause: Invalid `VIRTUAL_ENV` environment variable `<temp_dir>/nonexistent-venv`: does not point to a directory on disk
|
||||||
|
Cause: No such file or directory (os error 2)
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use lsp_types::{Position, notification::ShowMessage, request::RegisterCapability};
|
use lsp_types::notification::ShowMessage;
|
||||||
|
use lsp_types::{Position, request::RegisterCapability};
|
||||||
use ruff_db::system::SystemPath;
|
use ruff_db::system::SystemPath;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use ty_server::{ClientOptions, DiagnosticMode};
|
use ty_server::{ClientOptions, DiagnosticMode};
|
||||||
@@ -474,3 +475,20 @@ fn register_multiple_capabilities() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests that the server doesn't panic when `VIRTUAL_ENV` points to a non-existent directory.
|
||||||
|
///
|
||||||
|
/// See: <https://github.com/astral-sh/ty/issues/2031>
|
||||||
|
#[test]
|
||||||
|
fn missing_virtual_env_does_not_panic() -> Result<()> {
|
||||||
|
let workspace_root = SystemPath::new("project");
|
||||||
|
|
||||||
|
// This should not panic even though VIRTUAL_ENV points to a non-existent path
|
||||||
|
let _server = TestServerBuilder::new()?
|
||||||
|
.with_workspace(workspace_root, None)?
|
||||||
|
.with_env_var("VIRTUAL_ENV", "/nonexistent/virtual/env/path")
|
||||||
|
.build()
|
||||||
|
.wait_until_workspaces_are_initialized();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ impl TestServer {
|
|||||||
test_context: TestContext,
|
test_context: TestContext,
|
||||||
capabilities: ClientCapabilities,
|
capabilities: ClientCapabilities,
|
||||||
initialization_options: Option<ClientOptions>,
|
initialization_options: Option<ClientOptions>,
|
||||||
|
env_vars: Vec<(String, String)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
setup_tracing();
|
setup_tracing();
|
||||||
|
|
||||||
@@ -219,11 +220,16 @@ impl TestServer {
|
|||||||
// Create OS system with the test directory as cwd
|
// Create OS system with the test directory as cwd
|
||||||
let os_system = OsSystem::new(test_context.root());
|
let os_system = OsSystem::new(test_context.root());
|
||||||
|
|
||||||
|
// Create test system and set environment variable overrides
|
||||||
|
let test_system = Arc::new(TestSystem::new(os_system));
|
||||||
|
for (name, value) in env_vars {
|
||||||
|
test_system.set_env_var(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
// Start the server in a separate thread
|
// Start the server in a separate thread
|
||||||
let server_thread = std::thread::spawn(move || {
|
let server_thread = std::thread::spawn(move || {
|
||||||
// TODO: This should probably be configurable to test concurrency issues
|
// TODO: This should probably be configurable to test concurrency issues
|
||||||
let worker_threads = NonZeroUsize::new(1).unwrap();
|
let worker_threads = NonZeroUsize::new(1).unwrap();
|
||||||
let test_system = Arc::new(TestSystem::new(os_system));
|
|
||||||
|
|
||||||
match Server::new(worker_threads, server_connection, test_system, true) {
|
match Server::new(worker_threads, server_connection, test_system, true) {
|
||||||
Ok(server) => {
|
Ok(server) => {
|
||||||
@@ -1052,6 +1058,7 @@ pub(crate) struct TestServerBuilder {
|
|||||||
workspaces: Vec<(WorkspaceFolder, Option<ClientOptions>)>,
|
workspaces: Vec<(WorkspaceFolder, Option<ClientOptions>)>,
|
||||||
initialization_options: Option<ClientOptions>,
|
initialization_options: Option<ClientOptions>,
|
||||||
client_capabilities: ClientCapabilities,
|
client_capabilities: ClientCapabilities,
|
||||||
|
env_vars: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestServerBuilder {
|
impl TestServerBuilder {
|
||||||
@@ -1082,6 +1089,7 @@ impl TestServerBuilder {
|
|||||||
test_context: TestContext::new()?,
|
test_context: TestContext::new()?,
|
||||||
initialization_options: None,
|
initialization_options: None,
|
||||||
client_capabilities,
|
client_capabilities,
|
||||||
|
env_vars: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1091,6 +1099,16 @@ impl TestServerBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set an environment variable for the test server's system.
|
||||||
|
pub(crate) fn with_env_var(
|
||||||
|
mut self,
|
||||||
|
name: impl Into<String>,
|
||||||
|
value: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
self.env_vars.push((name.into(), value.into()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a workspace to the test server with the given root path and options.
|
/// Add a workspace to the test server with the given root path and options.
|
||||||
///
|
///
|
||||||
/// This option will be used to respond to the `workspace/configuration` request that the
|
/// This option will be used to respond to the `workspace/configuration` request that the
|
||||||
@@ -1237,6 +1255,7 @@ impl TestServerBuilder {
|
|||||||
self.test_context,
|
self.test_context,
|
||||||
self.client_capabilities,
|
self.client_capabilities,
|
||||||
self.initialization_options,
|
self.initialization_options,
|
||||||
|
self.env_vars,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user