Skip to content

Commit 1b0c881

Browse files
authored
Conda and Known Python Tests (#23349)
1 parent 4b73208 commit 1b0c881

23 files changed

+456
-111
lines changed

.github/workflows/pr-check.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,30 @@ jobs:
311311
run: npm run test:functional
312312
if: matrix.test-suite == 'functional'
313313

314+
native-tests:
315+
name: Native Tests
316+
# The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded.
317+
runs-on: ${{ matrix.os }}
318+
defaults:
319+
run:
320+
working-directory: ${{ env.special-working-directory }}
321+
strategy:
322+
fail-fast: false
323+
matrix:
324+
# We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used,
325+
# macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case.
326+
os: [ubuntu-latest, windows-latest]
327+
328+
steps:
329+
- name: Checkout
330+
uses: actions/checkout@v4
331+
with:
332+
path: ${{ env.special-working-directory-relative }}
333+
334+
- name: Native Locator tests
335+
run: cargo test
336+
working-directory: ${{ env.special-working-directory }}/native_locator
337+
314338
smoke-tests:
315339
name: Smoke tests
316340
# 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+
environment: &impl known::Environment,
40+
) {
41+
if let Some(paths) = environment.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+
environment: &impl known::Environment,
57+
) {
58+
report_python_on_path(dispatcher, environment);
5259
}

native_locator/src/conda.rs

Lines changed: 47 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(environment: &impl known::Environment) -> Option<PathBuf> {
131+
let paths = environment.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,13 @@ 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(environment: &impl known::Environment) -> Vec<PathBuf> {
163+
let user_profile = environment.get_env_var("USERPROFILE".to_string()).unwrap();
164+
let program_data = environment.get_env_var("PROGRAMDATA".to_string()).unwrap();
165+
let all_user_profile = environment
166+
.get_env_var("ALLUSERSPROFILE".to_string())
167+
.unwrap();
168+
let home_drive = environment.get_env_var("HOMEDRIVE".to_string()).unwrap();
169169
let mut known_paths = vec![
170170
Path::new(&user_profile).join("Anaconda3\\Scripts"),
171171
Path::new(&program_data).join("Anaconda3\\Scripts"),
@@ -176,12 +176,12 @@ fn get_known_conda_locations() -> Vec<PathBuf> {
176176
Path::new(&all_user_profile).join("Miniconda3\\Scripts"),
177177
Path::new(&home_drive).join("Miniconda3\\Scripts"),
178178
];
179-
known_paths.append(&mut known::get_know_global_search_locations());
179+
known_paths.append(&mut environment.get_know_global_search_locations());
180180
known_paths
181181
}
182182

183183
#[cfg(unix)]
184-
fn get_known_conda_locations() -> Vec<PathBuf> {
184+
fn get_known_conda_locations(environment: &impl known::Environment) -> Vec<PathBuf> {
185185
let mut known_paths = vec![
186186
PathBuf::from("/opt/anaconda3/bin"),
187187
PathBuf::from("/opt/miniconda3/bin"),
@@ -202,14 +202,14 @@ fn get_known_conda_locations() -> Vec<PathBuf> {
202202
PathBuf::from("/anaconda3/bin"),
203203
PathBuf::from("/miniconda3/bin"),
204204
];
205-
known_paths.append(&mut known::get_know_global_search_locations());
205+
known_paths.append(&mut environment.get_know_global_search_locations());
206206
known_paths
207207
}
208208

209209
/// Find conda binary in known locations
210-
fn find_conda_binary_in_known_locations() -> Option<PathBuf> {
210+
fn find_conda_binary_in_known_locations(environment: &impl known::Environment) -> 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(environment);
213213
for location in known_locations {
214214
for bin in &conda_bin_names {
215215
let conda_path = location.join(bin);
@@ -223,17 +223,17 @@ 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(environment: &impl known::Environment) -> Option<PathBuf> {
227+
let conda_binary_on_path = find_conda_binary_on_path(environment);
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(environment),
231231
}
232232
}
233233

234-
fn get_conda_envs_from_environment_txt() -> Vec<String> {
234+
fn get_conda_envs_from_environment_txt(environment: &impl known::Environment) -> Vec<String> {
235235
let mut envs = vec![];
236-
let home = known::get_user_home();
236+
let home = environment.get_user_home();
237237
match home {
238238
Some(home) => {
239239
let home = Path::new(&home);
@@ -252,9 +252,12 @@ fn get_conda_envs_from_environment_txt() -> Vec<String> {
252252
envs
253253
}
254254

255-
fn get_known_env_locations(conda_bin: PathBuf) -> Vec<String> {
255+
fn get_known_env_locations(
256+
conda_bin: PathBuf,
257+
environment: &impl known::Environment,
258+
) -> Vec<String> {
256259
let mut paths = vec![];
257-
let home = known::get_user_home();
260+
let home = environment.get_user_home();
258261
match home {
259262
Some(home) => {
260263
let home = Path::new(&home);
@@ -284,9 +287,12 @@ fn get_known_env_locations(conda_bin: PathBuf) -> Vec<String> {
284287
paths
285288
}
286289

287-
fn get_conda_envs_from_known_env_locations(conda_bin: PathBuf) -> Vec<String> {
290+
fn get_conda_envs_from_known_env_locations(
291+
conda_bin: PathBuf,
292+
environment: &impl known::Environment,
293+
) -> Vec<String> {
288294
let mut envs = vec![];
289-
for location in get_known_env_locations(conda_bin) {
295+
for location in get_known_env_locations(conda_bin, environment) {
290296
if is_conda_environment(&Path::new(&location)) {
291297
envs.push(location.to_string());
292298
}
@@ -324,14 +330,18 @@ struct CondaEnv {
324330
path: PathBuf,
325331
}
326332

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());
333+
fn get_distinct_conda_envs(
334+
conda_bin: PathBuf,
335+
environment: &impl known::Environment,
336+
) -> Vec<CondaEnv> {
337+
let mut envs = get_conda_envs_from_environment_txt(environment);
338+
let mut known_envs =
339+
get_conda_envs_from_known_env_locations(conda_bin.to_path_buf(), environment);
330340
envs.append(&mut known_envs);
331341
envs.sort();
332342
envs.dedup();
333343

334-
let locations = get_known_env_locations(conda_bin);
344+
let locations = get_known_env_locations(conda_bin, environment);
335345
let mut conda_envs = vec![];
336346
for env in envs {
337347
let env = Path::new(&env);
@@ -367,16 +377,19 @@ fn get_distinct_conda_envs(conda_bin: PathBuf) -> Vec<CondaEnv> {
367377
conda_envs
368378
}
369379

370-
pub fn find_and_report() {
371-
let conda_binary = find_conda_binary();
380+
pub fn find_and_report(
381+
dispatcher: &mut impl messaging::MessageDispatcher,
382+
environment: &impl known::Environment,
383+
) {
384+
let conda_binary = find_conda_binary(environment);
372385
match conda_binary {
373386
Some(conda_binary) => {
374387
let params =
375388
messaging::EnvManager::new(vec![conda_binary.to_string_lossy().to_string()], None);
376389
let message = messaging::EnvManagerMessage::new(params);
377-
messaging::send_message(message);
390+
dispatcher.send_message(message);
378391

379-
let envs = get_distinct_conda_envs(conda_binary.to_path_buf());
392+
let envs = get_distinct_conda_envs(conda_binary.to_path_buf(), environment);
380393
for env in envs {
381394
let executable = find_python_binary_path(Path::new(&env.path));
382395
let params = messaging::PythonEnvironment::new(
@@ -407,7 +420,7 @@ pub fn find_and_report() {
407420
Some(env.path.to_string_lossy().to_string()),
408421
);
409422
let message = messaging::PythonEnvironmentMessage::new(params);
410-
messaging::send_message(message);
423+
dispatcher.send_message(message);
411424
}
412425
}
413426
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 Environment {
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 EnvironmentApi {}
12+
513
#[cfg(windows)]
6-
pub fn get_know_global_search_locations() -> Vec<PathBuf> {
7-
vec![]
14+
impl Environment for EnvironmentApi {
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 Environment for EnvironmentApi {
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+
}

native_locator/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
pub mod messaging;
5+
pub mod utils;
6+
pub mod common_python;
7+
pub mod logging;
8+
pub mod conda;
9+
pub mod known;

0 commit comments

Comments
 (0)