Skip to content

Commit 5553801

Browse files
committed
Add Native tests for the native locator
1 parent d619a45 commit 5553801

File tree

23 files changed

+469
-145
lines changed

23 files changed

+469
-145
lines changed

.github/workflows/pr-check.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ jobs:
188188
- name: Install test requirements
189189
run: python -m pip install --upgrade -r build/test-requirements.txt
190190

191+
- name: Native Locator (Rust) tests
192+
run: nox --session native_test
193+
191194
- name: Install functional test requirements
192195
run: python -m pip install --upgrade -r ./build/functional-test-requirements.txt
193196
if: matrix.test-suite == 'functional'
@@ -311,6 +314,30 @@ jobs:
311314
run: npm run test:functional
312315
if: matrix.test-suite == 'functional'
313316

317+
native-tests:
318+
name: Native Tests
319+
# The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded.
320+
runs-on: ${{ matrix.os }}
321+
defaults:
322+
run:
323+
working-directory: ${{ env.special-working-directory }}
324+
strategy:
325+
fail-fast: false
326+
matrix:
327+
# We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used,
328+
# macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case.
329+
os: [ubuntu-latest, windows-latest]
330+
331+
steps:
332+
- name: Checkout
333+
uses: actions/checkout@v4
334+
with:
335+
path: ${{ env.special-working-directory-relative }}
336+
337+
- name: Native Locator tests
338+
run: cargo test
339+
working-directory: ${{ env.special-working-directory }}/native_locator
340+
314341
smoke-tests:
315342
name: Smoke tests
316343
# The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded.

native_locator/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ edition = "2021"
77
winreg = "0.52.0"
88

99
[dependencies]
10-
serde = {version ="1.0.152", features = ["derive"]}
10+
serde = { version = "1.0.152", features = ["derive"] }
1111
serde_json = "1.0.93"
1212
serde_repr = "0.1.10"
1313
regex = "1.10.4"
14+
15+
[features]
16+
test = []
17+
18+
[lib]
19+
doctest = false
Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
use crate::known;
45
use crate::messaging;
56
use crate::utils;
67
use std::env;
@@ -20,10 +21,10 @@ fn get_env_path(path: &str) -> Option<String> {
2021
}
2122
}
2223

23-
fn report_path_python(path: &str) {
24+
fn report_path_python(dispatcher: &mut impl messaging::MessageDispatcher, path: &str) {
2425
let version = utils::get_version(path);
2526
let env_path = get_env_path(path);
26-
messaging::send_message(messaging::PythonEnvironment::new(
27+
dispatcher.send_message(messaging::PythonEnvironment::new(
2728
"Python".to_string(),
2829
vec![path.to_string()],
2930
"System".to_string(),
@@ -33,30 +34,26 @@ fn report_path_python(path: &str) {
3334
));
3435
}
3536

36-
fn report_python_on_path() {
37-
let paths = env::var("PATH");
38-
let bin = if cfg!(windows) {
39-
"python.exe"
40-
} else {
41-
"python"
42-
};
43-
match paths {
44-
Ok(paths) => {
45-
let paths = env::split_paths(&paths);
46-
for path in paths {
47-
let full_path = path.join(bin);
48-
if full_path.exists() {
49-
match full_path.to_str() {
50-
Some(full_path) => report_path_python(full_path),
51-
None => (),
52-
}
53-
}
54-
}
55-
}
56-
Err(_) => (),
37+
fn report_python_on_path(
38+
dispatcher: &mut impl messaging::MessageDispatcher,
39+
known_paths: &impl known::KnownPaths,
40+
) {
41+
if let Some(paths) = known_paths.get_env_var("PATH".to_string()) {
42+
let bin = if cfg!(windows) {
43+
"python.exe"
44+
} else {
45+
"python"
46+
};
47+
env::split_paths(&paths)
48+
.map(|p| p.join(bin))
49+
.filter(|p| p.exists())
50+
.for_each(|full_path| report_path_python(dispatcher, full_path.to_str().unwrap()));
5751
}
5852
}
5953

60-
pub fn find_and_report() {
61-
report_python_on_path();
54+
pub fn find_and_report(
55+
dispatcher: &mut impl messaging::MessageDispatcher,
56+
known_paths: &impl known::KnownPaths,
57+
) {
58+
report_python_on_path(dispatcher, known_paths);
6259
}

native_locator/src/conda.rs

Lines changed: 68 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ fn get_version_from_meta_json(json_file: &Path) -> Option<String> {
8484
fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option<PathBuf> {
8585
let package_name = format!("{}-", package);
8686
let conda_meta_path = get_conda_meta_path(any_path)?;
87-
8887
std::fs::read_dir(conda_meta_path).ok()?.find_map(|entry| {
8988
let path = entry.ok()?.path();
9089
let file_name = path.file_name()?.to_string_lossy();
@@ -97,6 +96,7 @@ fn get_conda_package_json_path(any_path: &Path, package: &str) -> Option<PathBuf
9796
}
9897

9998
/// Checks if the `python` package is installed in the conda environment
99+
#[allow(dead_code)]
100100
pub fn is_python_conda_env(any_path: &Path) -> bool {
101101
let conda_python_json_path = get_conda_package_json_path(any_path, "python");
102102
match conda_python_json_path {
@@ -127,11 +127,9 @@ fn get_conda_bin_names() -> Vec<&'static str> {
127127
}
128128

129129
/// Find the conda binary on the PATH environment variable
130-
fn find_conda_binary_on_path() -> Option<PathBuf> {
131-
let paths = env::var("PATH").ok()?;
132-
let paths = env::split_paths(&paths);
133-
for path in paths {
134-
let path = Path::new(&path);
130+
fn find_conda_binary_on_path(known_paths: &impl known::KnownPaths) -> Option<PathBuf> {
131+
let paths = known_paths.get_env_var("PATH".to_string())?;
132+
for path in env::split_paths(&paths) {
135133
for bin in get_conda_bin_names() {
136134
let conda_path = path.join(bin);
137135
match std::fs::metadata(&conda_path) {
@@ -161,11 +159,11 @@ fn find_python_binary_path(env_path: &Path) -> Option<PathBuf> {
161159
}
162160

163161
#[cfg(windows)]
164-
fn get_known_conda_locations() -> Vec<PathBuf> {
165-
let user_profile = env::var("USERPROFILE").unwrap();
166-
let program_data = env::var("PROGRAMDATA").unwrap();
167-
let all_user_profile = env::var("ALLUSERSPROFILE").unwrap();
168-
let home_drive = env::var("HOMEDRIVE").unwrap();
162+
fn get_known_conda_locations(known_paths_provider: &impl known::KnownPaths) -> Vec<PathBuf> {
163+
let user_profile = known_paths_provider.get_env_var("USERPROFILE".to_string()).unwrap();
164+
let program_data = known_paths_provider.get_env_var("PROGRAMDATA".to_string()).unwrap();
165+
let all_user_profile = known_paths_provider.get_env_var("ALLUSERSPROFILE".to_string()).unwrap();
166+
let home_drive = known_paths_provider.get_env_var("HOMEDRIVE".to_string()).unwrap();
169167
let mut known_paths = vec![
170168
Path::new(&user_profile).join("Anaconda3\\Scripts"),
171169
Path::new(&program_data).join("Anaconda3\\Scripts"),
@@ -176,40 +174,42 @@ fn get_known_conda_locations() -> Vec<PathBuf> {
176174
Path::new(&all_user_profile).join("Miniconda3\\Scripts"),
177175
Path::new(&home_drive).join("Miniconda3\\Scripts"),
178176
];
179-
known_paths.append(&mut known::get_know_global_search_locations());
177+
known_paths.append(&mut known_paths_provider.get_know_global_search_locations());
180178
known_paths
181179
}
182180

183181
#[cfg(unix)]
184-
fn get_known_conda_locations() -> Vec<PathBuf> {
182+
fn get_known_conda_locations(known_paths_provider: &impl known::KnownPaths) -> Vec<PathBuf> {
185183
let mut known_paths = vec![
186-
Path::new("/opt/anaconda3/bin").to_path_buf(),
187-
Path::new("/opt/miniconda3/bin").to_path_buf(),
188-
Path::new("/usr/local/anaconda3/bin").to_path_buf(),
189-
Path::new("/usr/local/miniconda3/bin").to_path_buf(),
190-
Path::new("/usr/anaconda3/bin").to_path_buf(),
191-
Path::new("/usr/miniconda3/bin").to_path_buf(),
192-
Path::new("/home/anaconda3/bin").to_path_buf(),
193-
Path::new("/home/miniconda3/bin").to_path_buf(),
194-
Path::new("/anaconda3/bin").to_path_buf(),
195-
Path::new("/miniconda3/bin").to_path_buf(),
196-
Path::new("/usr/local/anaconda3/bin").to_path_buf(),
197-
Path::new("/usr/local/miniconda3/bin").to_path_buf(),
198-
Path::new("/usr/anaconda3/bin").to_path_buf(),
199-
Path::new("/usr/miniconda3/bin").to_path_buf(),
200-
Path::new("/home/anaconda3/bin").to_path_buf(),
201-
Path::new("/home/miniconda3/bin").to_path_buf(),
202-
Path::new("/anaconda3/bin").to_path_buf(),
203-
Path::new("/miniconda3/bin").to_path_buf(),
184+
PathBuf::from("/opt/anaconda3/bin"),
185+
PathBuf::from("/opt/miniconda3/bin"),
186+
PathBuf::from("/usr/local/anaconda3/bin"),
187+
PathBuf::from("/usr/local/miniconda3/bin"),
188+
PathBuf::from("/usr/anaconda3/bin"),
189+
PathBuf::from("/usr/miniconda3/bin"),
190+
PathBuf::from("/home/anaconda3/bin"),
191+
PathBuf::from("/home/miniconda3/bin"),
192+
PathBuf::from("/anaconda3/bin"),
193+
PathBuf::from("/miniconda3/bin"),
194+
PathBuf::from("/usr/local/anaconda3/bin"),
195+
PathBuf::from("/usr/local/miniconda3/bin"),
196+
PathBuf::from("/usr/anaconda3/bin"),
197+
PathBuf::from("/usr/miniconda3/bin"),
198+
PathBuf::from("/home/anaconda3/bin"),
199+
PathBuf::from("/home/miniconda3/bin"),
200+
PathBuf::from("/anaconda3/bin"),
201+
PathBuf::from("/miniconda3/bin"),
204202
];
205-
known_paths.append(&mut known::get_know_global_search_locations());
203+
known_paths.append(&mut known_paths_provider.get_know_global_search_locations());
206204
known_paths
207205
}
208206

209207
/// Find conda binary in known locations
210-
fn find_conda_binary_in_known_locations() -> Option<PathBuf> {
208+
fn find_conda_binary_in_known_locations(
209+
known_paths_provider: &impl known::KnownPaths,
210+
) -> Option<PathBuf> {
211211
let conda_bin_names = get_conda_bin_names();
212-
let known_locations = get_known_conda_locations();
212+
let known_locations = get_known_conda_locations(known_paths_provider);
213213
for location in known_locations {
214214
for bin in &conda_bin_names {
215215
let conda_path = location.join(bin);
@@ -223,17 +223,19 @@ fn find_conda_binary_in_known_locations() -> Option<PathBuf> {
223223
}
224224

225225
/// Find the conda binary on the system
226-
pub fn find_conda_binary() -> Option<PathBuf> {
227-
let conda_binary_on_path = find_conda_binary_on_path();
226+
pub fn find_conda_binary(known_paths_provider: &impl known::KnownPaths) -> Option<PathBuf> {
227+
let conda_binary_on_path = find_conda_binary_on_path(known_paths_provider);
228228
match conda_binary_on_path {
229229
Some(conda_binary_on_path) => Some(conda_binary_on_path),
230-
None => find_conda_binary_in_known_locations(),
230+
None => find_conda_binary_in_known_locations(known_paths_provider),
231231
}
232232
}
233233

234-
fn get_conda_envs_from_environment_txt() -> Vec<String> {
234+
fn get_conda_envs_from_environment_txt(
235+
known_paths_provider: &impl known::KnownPaths,
236+
) -> Vec<String> {
235237
let mut envs = vec![];
236-
let home = known::get_user_home();
238+
let home = known_paths_provider.get_user_home();
237239
match home {
238240
Some(home) => {
239241
let home = Path::new(&home);
@@ -252,9 +254,12 @@ fn get_conda_envs_from_environment_txt() -> Vec<String> {
252254
envs
253255
}
254256

255-
fn get_known_env_locations(conda_bin: PathBuf) -> Vec<String> {
257+
fn get_known_env_locations(
258+
conda_bin: PathBuf,
259+
known_paths_provider: &impl known::KnownPaths,
260+
) -> Vec<String> {
256261
let mut paths = vec![];
257-
let home = known::get_user_home();
262+
let home = known_paths_provider.get_user_home();
258263
match home {
259264
Some(home) => {
260265
let home = Path::new(&home);
@@ -284,9 +289,12 @@ fn get_known_env_locations(conda_bin: PathBuf) -> Vec<String> {
284289
paths
285290
}
286291

287-
fn get_conda_envs_from_known_env_locations(conda_bin: PathBuf) -> Vec<String> {
292+
fn get_conda_envs_from_known_env_locations(
293+
conda_bin: PathBuf,
294+
known_paths_provider: &impl known::KnownPaths,
295+
) -> Vec<String> {
288296
let mut envs = vec![];
289-
for location in get_known_env_locations(conda_bin) {
297+
for location in get_known_env_locations(conda_bin, known_paths_provider) {
290298
if is_conda_environment(&Path::new(&location)) {
291299
envs.push(location.to_string());
292300
}
@@ -324,16 +332,20 @@ struct CondaEnv {
324332
path: PathBuf,
325333
}
326334

327-
fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec<CondaEnv> {
328-
let mut envs = get_conda_envs_from_environment_txt();
329-
let mut known_envs = get_conda_envs_from_known_env_locations(conda_bin.to_path_buf());
335+
fn get_distinct_conda_envs(
336+
conda_bin: PathBuf,
337+
known_paths_provider: &impl known::KnownPaths,
338+
) -> Vec<CondaEnv> {
339+
let mut envs = get_conda_envs_from_environment_txt(known_paths_provider);
340+
let mut known_envs =
341+
get_conda_envs_from_known_env_locations(conda_bin.to_path_buf(), known_paths_provider);
330342
envs.append(&mut known_envs);
331343
envs.sort();
332344
envs.dedup();
333345

334-
let locations = get_known_env_locations(conda_bin);
346+
let locations = get_known_env_locations(conda_bin, known_paths_provider);
335347
let mut conda_envs = vec![];
336-
for env in envs.clone() {
348+
for env in envs {
337349
let env = Path::new(&env);
338350
let mut named = false;
339351
let mut name = "".to_string();
@@ -367,16 +379,19 @@ fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec<CondaEnv> {
367379
conda_envs
368380
}
369381

370-
pub fn find_and_report() {
371-
let conda_binary = find_conda_binary();
382+
pub fn find_and_report(
383+
dispatcher: &mut impl messaging::MessageDispatcher,
384+
known_paths_provider: &impl known::KnownPaths,
385+
) {
386+
let conda_binary = find_conda_binary(known_paths_provider);
372387
match conda_binary {
373388
Some(conda_binary) => {
374389
let params =
375390
messaging::EnvManager::new(vec![conda_binary.to_string_lossy().to_string()], None);
376391
let message = messaging::EnvManagerMessage::new(params);
377-
messaging::send_message(message);
392+
dispatcher.send_message(message);
378393

379-
let envs = get_distinct_conda_envs(conda_binary.to_path_buf());
394+
let envs = get_distinct_conda_envs(conda_binary.to_path_buf(), known_paths_provider);
380395
for env in envs {
381396
let executable = find_python_binary_path(Path::new(&env.path));
382397
let params = messaging::PythonEnvironment::new(
@@ -407,7 +422,7 @@ pub fn find_and_report() {
407422
Some(env.path.to_string_lossy().to_string()),
408423
);
409424
let message = messaging::PythonEnvironmentMessage::new(params);
410-
messaging::send_message(message);
425+
dispatcher.send_message(message);
411426
}
412427
}
413428
None => (),

0 commit comments

Comments
 (0)