@@ -98,6 +98,7 @@ mod tests {
98
98
assert_eq ! ( super :: config_to_base_path( Path :: new( input) ) , Path :: new( expected) ) ;
99
99
}
100
100
}
101
+
101
102
#[ test]
102
103
fn first_file_from_config_with_origin ( ) {
103
104
let macos = "file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig credential.helper=osxkeychain\n file:/Users/byron/.gitconfig push.default=simple\n " ;
@@ -127,4 +128,191 @@ mod tests {
127
128
) ;
128
129
}
129
130
}
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
+ }
130
318
}
0 commit comments