Skip to content

Commit edc1351

Browse files
committed
Start on unit test for ALTERNATIVE_LOCATIONS
1 parent 6af59ca commit edc1351

File tree

2 files changed

+189
-1
lines changed

2 files changed

+189
-1
lines changed

gix-path/src/env/git.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ mod tests {
9898
assert_eq!(super::config_to_base_path(Path::new(input)), Path::new(expected));
9999
}
100100
}
101+
101102
#[test]
102103
fn first_file_from_config_with_origin() {
103104
let macos = "file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig credential.helper=osxkeychain\nfile:/Users/byron/.gitconfig push.default=simple\n";
@@ -127,4 +128,191 @@ mod tests {
127128
);
128129
}
129130
}
131+
132+
#[cfg(windows)]
133+
use {
134+
known_folders::{get_known_folder_path, KnownFolder},
135+
std::ffi::{OsStr, OsString},
136+
std::path::PathBuf,
137+
windows::core::Result as WindowsResult,
138+
windows::Win32::Foundation::BOOL,
139+
windows::Win32::System::Threading::{GetCurrentProcess, IsWow64Process},
140+
winreg::enums::{HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE},
141+
winreg::RegKey,
142+
};
143+
144+
#[cfg(windows)]
145+
trait Current: Sized {
146+
fn current() -> WindowsResult<Self>;
147+
}
148+
149+
#[cfg(windows)]
150+
enum PlatformArchitecture {
151+
Is32on32,
152+
Is32on64,
153+
Is64on64,
154+
}
155+
156+
#[cfg(windows)]
157+
impl Current for PlatformArchitecture {
158+
fn current() -> WindowsResult<Self> {
159+
// Ordinarily, we would check the target pointer width first to avoid doing extra work,
160+
// because if this is a 64-bit executable then the operating system is 64-bit. But this
161+
// is for the test suite, and doing it this way allows problems to be caught earlier if
162+
// a change made on a 64-bit development machine breaks the IsWow64Process() call.
163+
let mut wow64process = BOOL::default();
164+
unsafe { IsWow64Process(GetCurrentProcess(), &mut wow64process)? };
165+
166+
let platform_architecture = if wow64process.as_bool() {
167+
Self::Is32on64
168+
} else if cfg!(target_pointer_width = "32") {
169+
Self::Is32on32
170+
} else {
171+
assert!(cfg!(target_pointer_width = "64"));
172+
Self::Is64on64
173+
};
174+
Ok(platform_architecture)
175+
}
176+
}
177+
178+
#[cfg(windows)]
179+
fn ends_with_case_insensitive(text: &OsStr, suffix: &str) -> Option<bool> {
180+
Some(text.to_str()?.to_lowercase().ends_with(&suffix.to_lowercase()))
181+
}
182+
183+
#[cfg(windows)]
184+
struct PathsByRole {
185+
/// The program files directory relative to what architecture this program was built for.
186+
pf_current: PathBuf,
187+
188+
/// The x86 program files directory regardless of the architecture of the program.
189+
///
190+
/// If Rust gains Windows targets like ARMv7 where this is unavailable, this could fail.
191+
pf_x86: PathBuf,
192+
193+
/// The 64-bit program files directory if there is one.
194+
///
195+
/// This is present on x64 and also ARM64 systems. On an ARM64 system, ARM64 and AMD64
196+
/// programs use the same program files directory while 32-bit x86 and ARM programs use two
197+
/// others. Only a 32-bit has no 64-bit program files directory.
198+
maybe_pf_64bit: Result<PathBuf, std::io::Error>,
199+
// While
200+
// KnownFolder::ProgramFilesX64 exists, it's unfortunately unavailable to 32-bit
201+
// processes; see
202+
// https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#remarks.
203+
}
204+
205+
impl PathsByRole {
206+
/// Gets the three common kinds of global program files paths without environment variables.
207+
///
208+
/// The idea here is to obtain this information, which the `alternative_locations()` unit
209+
/// test uses to learn the expected alternative locations, without duplicating *any* of the
210+
/// approach used for `ALTERNATIVE_LOCATIONS`, so it can be used to test that. The approach
211+
/// here is also be more reliable than using environment variables, but it is a bit more
212+
/// complex, and it requires either additional dependencies or the use of unsafe code.
213+
///
214+
/// This obtains `pf_current` and `pf_x86` by the [known folders][known-folders] system,
215+
/// and `maybe_pf_64bit` from the registry since the corresponding known folder is not
216+
/// available to 32-bit processes, per the [KNOWNFOLDDERID][knownfolderid] documentation.
217+
///
218+
/// If in the future the implementation of `ALTERNATIVE_LOCATIONS` uses these techniques,
219+
/// then this function can be changed to use environment variables and renamed accordingly.
220+
///
221+
/// [known-folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders
222+
/// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#remarks
223+
fn obtain_envlessly() -> Self {
224+
let pf_current = get_known_folder_path(KnownFolder::ProgramFiles)
225+
.expect("The process architecture specific program files folder is always available.");
226+
227+
let pf_x86 = get_known_folder_path(KnownFolder::ProgramFilesX86)
228+
.expect("The x86 program files folder will in practice always be available.");
229+
230+
let maybe_pf_64bit = RegKey::predef(HKEY_LOCAL_MACHINE)
231+
.open_subkey_with_flags(r#"SOFTWARE\Microsoft\Windows\CurrentVersion"#, KEY_QUERY_VALUE)
232+
.expect("The `CurrentVersion` key exists and allows reading.")
233+
.get_value::<OsString, _>("ProgramW6432Dir")
234+
.map(PathBuf::from);
235+
236+
Self {
237+
pf_current,
238+
pf_x86,
239+
maybe_pf_64bit,
240+
}
241+
}
242+
243+
/// Checks that the paths we got for testing are reasonable.
244+
///
245+
/// This checks that `obtain_envlessly()` returned paths that are likely to be correct and
246+
/// that satisfy the most important properties based on the current system and process.
247+
fn validate(self) -> Self {
248+
let PathsByRole {
249+
pf_current,
250+
pf_x86,
251+
maybe_pf_64bit,
252+
} = self;
253+
254+
match PlatformArchitecture::current().expect("Process and system 'bitness' should be available.") {
255+
PlatformArchitecture::Is32on32 => {
256+
assert_eq!(
257+
pf_current.as_os_str(),
258+
pf_x86.as_os_str(),
259+
"Ours is identical to 32-bit path."
260+
);
261+
for arch_suffix in [" (x86)", " (Arm)"] {
262+
let has_arch_suffix = ends_with_case_insensitive(pf_current.as_os_str(), arch_suffix)
263+
.expect("Assume the test systems key dirs are valid Unicode.");
264+
assert!(
265+
!has_arch_suffix,
266+
"32-bit program files on 32-bit system has unadorned name."
267+
);
268+
}
269+
maybe_pf_64bit
270+
.as_ref()
271+
.expect_err("A 32-bit system has no 64-bit program files directory.");
272+
}
273+
PlatformArchitecture::Is32on64 => {
274+
assert_eq!(
275+
pf_current.as_os_str(),
276+
pf_x86.as_os_str(),
277+
"Ours is identical to 32-bit path."
278+
);
279+
let pf_64bit = maybe_pf_64bit.as_ref().expect("64-bit program files exists.");
280+
assert_ne!(&pf_x86, pf_64bit, "32-bit and 64-bit program files directories differ.");
281+
}
282+
PlatformArchitecture::Is64on64 => {
283+
let pf_64bit = maybe_pf_64bit.as_ref().expect("64-bit program files exists.");
284+
assert_eq!(
285+
pf_current.as_os_str(),
286+
pf_64bit.as_os_str(),
287+
"Ours is identical to 64-bit path."
288+
);
289+
assert_ne!(&pf_x86, pf_64bit, "32-bit and 64-bit program files directories differ.");
290+
}
291+
}
292+
293+
Self {
294+
pf_current,
295+
pf_x86,
296+
maybe_pf_64bit,
297+
}
298+
}
299+
}
300+
301+
#[test]
302+
#[cfg(windows)]
303+
fn alternative_locations() {
304+
let PathsByRole {
305+
pf_current,
306+
pf_x86,
307+
maybe_pf_64bit,
308+
} = PathsByRole::obtain_envlessly().validate();
309+
310+
// FIXME: Assert the relationships between the above values and ALTERNATIVE_LOCATIONS contents!
311+
}
312+
313+
#[test]
314+
#[cfg(not(windows))]
315+
fn alternative_locations() {
316+
assert!(super::ALTERNATIVE_LOCATIONS.is_empty());
317+
}
130318
}

gix-path/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
//! ever get into a code-path which does panic though.
4848
//! </details>
4949
#![deny(missing_docs, rust_2018_idioms)]
50-
#![forbid(unsafe_code)]
50+
#![cfg_attr(not(test), forbid(unsafe_code))]
5151

5252
/// A dummy type to represent path specs and help finding all spots that take path specs once it is implemented.
5353
mod convert;

0 commit comments

Comments
 (0)