Skip to content

Commit ff46475

Browse files
committed
Add Native tests for the native locator
1 parent 4b73208 commit ff46475

File tree

23 files changed

+448
-111
lines changed

23 files changed

+448
-111
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

native_locator/src/common_python.rs

Lines changed: 19 additions & 12 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,20 +34,26 @@ fn report_path_python(path: &str) {
3334
));
3435
}
3536

36-
fn report_python_on_path() {
37-
let bin = if cfg!(windows) {
38-
"python.exe"
39-
} else {
40-
"python"
41-
};
42-
if let Ok(paths) = env::var("PATH") {
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+
};
4347
env::split_paths(&paths)
4448
.map(|p| p.join(bin))
4549
.filter(|p| p.exists())
46-
.for_each(|full_path| report_path_python(full_path.to_str().unwrap()));
50+
.for_each(|full_path| report_path_python(dispatcher, full_path.to_str().unwrap()));
4751
}
4852
}
4953

50-
pub fn find_and_report() {
51-
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);
5259
}

native_locator/src/conda.rs

Lines changed: 49 additions & 34 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,12 +174,12 @@ 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![
186184
PathBuf::from("/opt/anaconda3/bin"),
187185
PathBuf::from("/opt/miniconda3/bin"),
@@ -202,14 +200,16 @@ fn get_known_conda_locations() -> Vec<PathBuf> {
202200
PathBuf::from("/anaconda3/bin"),
203201
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,14 +332,18 @@ 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![];
336348
for env in envs {
337349
let env = Path::new(&env);
@@ -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 => (),

native_locator/src/known.rs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,64 @@
22
// Licensed under the MIT License.
33
use std::{env, path::PathBuf};
44

5+
pub trait KnownPaths {
6+
fn get_user_home(&self) -> Option<String>;
7+
fn get_env_var(&self, key: String) -> Option<String>;
8+
fn get_know_global_search_locations(&self) -> Vec<PathBuf>;
9+
}
10+
11+
pub struct KnownPathsImpl {}
12+
513
#[cfg(windows)]
6-
pub fn get_know_global_search_locations() -> Vec<PathBuf> {
7-
vec![]
14+
impl KnownPaths for KnownPathsImpl {
15+
fn get_user_home(&self) -> Option<String> {
16+
get_user_home()
17+
}
18+
fn get_env_var(&self, key: string) -> Option<String> {
19+
get_env_var(key)
20+
}
21+
fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
22+
vec![]
23+
}
824
}
925

1026
#[cfg(unix)]
11-
pub fn get_know_global_search_locations() -> Vec<PathBuf> {
12-
vec![
13-
PathBuf::from("/usr/bin"),
14-
PathBuf::from("/usr/local/bin"),
15-
PathBuf::from("/bin"),
16-
PathBuf::from("/home/bin"),
17-
PathBuf::from("/sbin"),
18-
PathBuf::from("/usr/sbin"),
19-
PathBuf::from("/usr/local/sbin"),
20-
PathBuf::from("/home/sbin"),
21-
PathBuf::from("/opt"),
22-
PathBuf::from("/opt/bin"),
23-
PathBuf::from("/opt/sbin"),
24-
PathBuf::from("/opt/homebrew/bin"),
25-
]
27+
impl KnownPaths for KnownPathsImpl {
28+
fn get_user_home(&self) -> Option<String> {
29+
get_user_home()
30+
}
31+
fn get_env_var(&self, key: String) -> Option<String> {
32+
get_env_var(key)
33+
}
34+
fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
35+
vec![
36+
PathBuf::from("/usr/bin"),
37+
PathBuf::from("/usr/local/bin"),
38+
PathBuf::from("/bin"),
39+
PathBuf::from("/home/bin"),
40+
PathBuf::from("/sbin"),
41+
PathBuf::from("/usr/sbin"),
42+
PathBuf::from("/usr/local/sbin"),
43+
PathBuf::from("/home/sbin"),
44+
PathBuf::from("/opt"),
45+
PathBuf::from("/opt/bin"),
46+
PathBuf::from("/opt/sbin"),
47+
PathBuf::from("/opt/homebrew/bin"),
48+
]
49+
}
2650
}
2751

28-
pub fn get_user_home() -> Option<String> {
52+
fn get_user_home() -> Option<String> {
2953
let home = env::var("HOME").or_else(|_| env::var("USERPROFILE"));
3054
match home {
3155
Ok(home) => Some(home),
3256
Err(_) => None,
3357
}
3458
}
59+
60+
fn get_env_var(key: String) -> Option<String> {
61+
match env::var(key) {
62+
Ok(path) => Some(path),
63+
Err(_) => None,
64+
}
65+
}

0 commit comments

Comments
 (0)