@@ -284,25 +284,47 @@ func spawnExecutable(
284284 let commandLine = _escapeCommandLine ( CollectionOfOne ( executablePath) + arguments)
285285 let environ = environment. map { " \( $0. key) = \( $0. value) " } . joined ( separator: " \0 " ) + " \0 \0 "
286286
287+ // CreateProcessW() may modify the command line argument, so we must make
288+ // a mutable copy of it. (environ is also passed as a mutable raw pointer,
289+ // but it is not documented as actually being mutated.)
290+ let commandLineCopy = commandLine. withCString ( encodedAs: UTF16 . self) { _wcsdup ( $0) }
291+ defer {
292+ free ( commandLineCopy)
293+ }
294+
295+ // On Windows, a process holds a reference to its current working
296+ // directory, which prevents other processes from deleting it. This causes
297+ // code to fail if it tries to set the working directory to a temporary
298+ // path. SEE: https://github.com/swiftlang/swift-testing/issues/1209
299+ //
300+ // This problem manifests for us when we spawn a child process without
301+ // setting its working directory, which causes it to default to that of
302+ // the parent process. To avoid this problem, we set the working directory
303+ // of the new process to the root directory of the boot volume (which is
304+ // unlikely to be deleted, one hopes).
305+ //
306+ // SEE: https://devblogs.microsoft.com/oldnewthing/20101109-00/?p=12323
307+ let workingDirectoryPath = rootDirectoryPath
308+
287309 var flags = DWORD ( CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT)
288310#if DEBUG
289311 // Start the process suspended so we can attach a debugger if needed.
290312 flags |= DWORD ( CREATE_SUSPENDED)
291313#endif
292314
293- return try commandLine . withCString ( encodedAs: UTF16 . self) { commandLine in
294- try environ . withCString ( encodedAs: UTF16 . self) { environ in
315+ return try environ . withCString ( encodedAs: UTF16 . self) { environ in
316+ try workingDirectoryPath . withCString ( encodedAs: UTF16 . self) { workingDirectoryPath in
295317 var processInfo = PROCESS_INFORMATION ( )
296318
297319 guard CreateProcessW (
298320 nil ,
299- . init ( mutating : commandLine ) ,
321+ commandLineCopy ,
300322 nil ,
301323 nil ,
302324 true , // bInheritHandles
303325 flags,
304326 . init( mutating: environ) ,
305- nil ,
327+ workingDirectoryPath ,
306328 startupInfo. pointer ( to: \. StartupInfo) !,
307329 & processInfo
308330 ) else {
0 commit comments