From 47e015ecfa29b00a24ba1346d0cda07116b4fa8f Mon Sep 17 00:00:00 2001 From: Charles Hu Date: Thu, 28 Aug 2025 11:22:26 -0700 Subject: [PATCH 1/2] Introduce .swift-format and enable linting requirement on PR requests --- .github/workflows/pull_request.yml | 5 +- .swift-format | 75 ++++++++ Package.swift | 6 +- Sources/Subprocess/API.swift | 3 +- Sources/Subprocess/AsyncBufferSequence.swift | 26 +-- Sources/Subprocess/Buffer.swift | 5 +- Sources/Subprocess/Configuration.swift | 21 +-- Sources/Subprocess/IO/AsyncIO+Dispatch.swift | 6 +- Sources/Subprocess/IO/AsyncIO+Linux.swift | 43 +++-- Sources/Subprocess/IO/AsyncIO+Windows.swift | 172 +++++++++--------- Sources/Subprocess/IO/Input.swift | 2 +- Sources/Subprocess/IO/Output.swift | 4 +- .../Subprocess/Platforms/Subprocess+BSD.swift | 23 +-- .../Platforms/Subprocess+Darwin.swift | 6 +- .../Platforms/Subprocess+Linux.swift | 31 ++-- .../Platforms/Subprocess+Unix.swift | 4 +- .../Platforms/Subprocess+Windows.swift | 60 +++--- Sources/Subprocess/Result.swift | 1 - .../Input+Foundation.swift | 7 +- .../Output+Foundation.swift | 2 +- .../Span+SubprocessFoundation.swift | 4 +- Sources/Subprocess/Teardown.swift | 4 +- Tests/SubprocessTests/AsyncIOTests.swift | 19 +- Tests/SubprocessTests/DarwinTests.swift | 2 +- Tests/SubprocessTests/IntegrationTests.swift | 137 +++++++------- Tests/SubprocessTests/LinterTests.swift | 2 +- Tests/SubprocessTests/LinuxTests.swift | 4 +- .../SubprocessTests/PlatformConformance.swift | 2 +- Tests/SubprocessTests/TestSupport.swift | 4 +- Tests/SubprocessTests/UnixTests.swift | 45 +++-- Tests/SubprocessTests/WindowsTests.swift | 6 +- 31 files changed, 408 insertions(+), 323 deletions(-) create mode 100644 .swift-format diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d1bf7009..738582e3 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -28,14 +28,15 @@ jobs: # Test dependencies yum install -y procps fi - linux_build_command: 'swift test && swift test --disable-default-traits' + linux_build_command: 'swift-format lint -s -r --configuration ./.swift-format . && swift test && swift test --disable-default-traits' windows_swift_versions: '["6.1", "nightly-main"]' windows_build_command: | + Invoke-Program swift-format lint -s -r --configuration .\.swift-format . Invoke-Program swift test Invoke-Program swift test --disable-default-traits enable_macos_checks: true macos_xcode_versions: '["16.3"]' - macos_build_command: 'xcrun swift test && xcrun swift test --disable-default-traits' + macos_build_command: 'xcrun swift-format lint -s -r --configuration ./.swift-format . && xcrun swift test && xcrun swift test --disable-default-traits' enable_linux_static_sdk_build: true linux_static_sdk_versions: '["6.1", "nightly-6.2"]' linux_static_sdk_build_command: | diff --git a/.swift-format b/.swift-format new file mode 100644 index 00000000..81456dfc --- /dev/null +++ b/.swift-format @@ -0,0 +1,75 @@ +{ + "fileScopedDeclarationPrivacy": { + "accessLevel": "private" + }, + "indentConditionalCompilationBlocks": false, + "indentSwitchCaseLabels": false, + "indentation": { + "spaces": 4 + }, + "lineBreakAroundMultilineExpressionChainComponents": false, + "lineBreakBeforeControlFlowKeywords": false, + "lineBreakBeforeEachArgument": false, + "lineBreakBeforeEachGenericRequirement": false, + "lineBreakBetweenDeclarationAttributes": false, + "lineLength": 9999, + "maximumBlankLines": 1, + "multiElementCollectionTrailingCommas": true, + "noAssignmentInExpressions": { + "allowedFunctions": [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether": false, + "reflowMultilineStringLiterals": "never", + "respectsExistingLineBreaks": true, + "rules": { + "AllPublicDeclarationsHaveDocumentation": false, + "AlwaysUseLiteralForEmptyCollectionInit": false, + "AlwaysUseLowerCamelCase": false, + "AmbiguousTrailingClosureOverload": false, + "AvoidRetroactiveConformances": false, + "BeginDocumentationCommentWithOneLineSummary": false, + "DoNotUseSemicolons": false, + "DontRepeatTypeInStaticProperties": false, + "FileScopedDeclarationPrivacy": false, + "FullyIndirectEnum": false, + "GroupNumericLiterals": false, + "IdentifiersMustBeASCII": false, + "NeverForceUnwrap": false, + "NeverUseForceTry": false, + "NeverUseImplicitlyUnwrappedOptionals": false, + "NoAccessLevelOnExtensionDeclaration": false, + "NoAssignmentInExpressions": false, + "NoBlockComments": false, + "NoCasesWithOnlyFallthrough": false, + "NoEmptyLinesOpeningClosingBraces": false, + "NoEmptyTrailingClosureParentheses": false, + "NoLabelsInCasePatterns": false, + "NoLeadingUnderscores": false, + "NoParensAroundConditions": false, + "NoPlaygroundLiterals": false, + "NoVoidReturnOnFunctionSignature": false, + "OmitExplicitReturns": false, + "OneCasePerLine": false, + "OneVariableDeclarationPerLine": false, + "OnlyOneTrailingClosureArgument": false, + "OrderedImports": false, + "ReplaceForEachWithForLoop": false, + "ReturnVoidInsteadOfEmptyTuple": false, + "TypeNamesShouldBeCapitalized": false, + "UseEarlyExits": false, + "UseExplicitNilCheckInConditions": false, + "UseLetInEveryBoundCaseVariable": false, + "UseShorthandTypeNames": false, + "UseSingleLinePropertyGetter": false, + "UseSynthesizedInitializer": false, + "UseTripleSlashForDocumentationComments": false, + "UseWhereClausesInForLoops": false, + "ValidateDocumentationComments": false + }, + "spacesAroundRangeFormationOperators": true, + "spacesBeforeEndOfLineComments": 1, + "tabWidth": 4, + "version": 1 +} diff --git a/Package.swift b/Package.swift index ebf38b22..935b93ae 100644 --- a/Package.swift +++ b/Package.swift @@ -56,7 +56,7 @@ let package = Package( .product(name: "SystemPackage", package: "swift-system"), ], path: "Sources/Subprocess", - exclude: [ "CMakeLists.txt" ], + exclude: ["CMakeLists.txt"], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), .enableExperimentalFeature("NonescapableTypes"), @@ -73,7 +73,7 @@ let package = Package( .product(name: "SystemPackage", package: "swift-system"), ], swiftSettings: [ - .enableExperimentalFeature("Span"), + .enableExperimentalFeature("Span") ] + packageSwiftSettings ), @@ -91,7 +91,7 @@ let package = Package( .target( name: "_SubprocessCShims", path: "Sources/_SubprocessCShims", - exclude: [ "CMakeLists.txt" ] + exclude: ["CMakeLists.txt"] ), ] ) diff --git a/Sources/Subprocess/API.swift b/Sources/Subprocess/API.swift index ee7ac0f5..62553091 100644 --- a/Sources/Subprocess/API.swift +++ b/Sources/Subprocess/API.swift @@ -133,8 +133,7 @@ public func run< standardError: result.value.standardError ) } -#endif // SubprocessSpan - +#endif // SubprocessSpan // MARK: - Custom Execution Body diff --git a/Sources/Subprocess/AsyncBufferSequence.swift b/Sources/Subprocess/AsyncBufferSequence.swift index 8fe78f1f..04fb3a8d 100644 --- a/Sources/Subprocess/AsyncBufferSequence.swift +++ b/Sources/Subprocess/AsyncBufferSequence.swift @@ -192,10 +192,10 @@ extension AsyncBufferSequence { } // https://en.wikipedia.org/wiki/Newline#Unicode - let lineFeed = Encoding.CodeUnit(0x0A) + let lineFeed = Encoding.CodeUnit(0x0A) /// let verticalTab = Encoding.CodeUnit(0x0B) /// let formFeed = Encoding.CodeUnit(0x0C) - let carriageReturn = Encoding.CodeUnit(0x0D) + let carriageReturn = Encoding.CodeUnit(0x0D) // carriageReturn + lineFeed let newLine1: Encoding.CodeUnit let newLine2: Encoding.CodeUnit @@ -207,24 +207,24 @@ extension AsyncBufferSequence { let paragraphSeparator3: Encoding.CodeUnit switch Encoding.CodeUnit.self { case is UInt8.Type: - newLine1 = Encoding.CodeUnit(0xC2) - newLine2 = Encoding.CodeUnit(0x85) + newLine1 = Encoding.CodeUnit(0xC2) + newLine2 = Encoding.CodeUnit(0x85) - lineSeparator1 = Encoding.CodeUnit(0xE2) - lineSeparator2 = Encoding.CodeUnit(0x80) - lineSeparator3 = Encoding.CodeUnit(0xA8) + lineSeparator1 = Encoding.CodeUnit(0xE2) + lineSeparator2 = Encoding.CodeUnit(0x80) + lineSeparator3 = Encoding.CodeUnit(0xA8) paragraphSeparator1 = Encoding.CodeUnit(0xE2) paragraphSeparator2 = Encoding.CodeUnit(0x80) paragraphSeparator3 = Encoding.CodeUnit(0xA9) case is UInt16.Type, is UInt32.Type: // UTF16 and UTF32 use one byte for all - newLine1 = Encoding.CodeUnit(0x0085) - newLine2 = Encoding.CodeUnit(0x0085) + newLine1 = Encoding.CodeUnit(0x0085) + newLine2 = Encoding.CodeUnit(0x0085) - lineSeparator1 = Encoding.CodeUnit(0x2028) - lineSeparator2 = Encoding.CodeUnit(0x2028) - lineSeparator3 = Encoding.CodeUnit(0x2028) + lineSeparator1 = Encoding.CodeUnit(0x2028) + lineSeparator2 = Encoding.CodeUnit(0x2028) + lineSeparator3 = Encoding.CodeUnit(0x2028) paragraphSeparator1 = Encoding.CodeUnit(0x2029) paragraphSeparator2 = Encoding.CodeUnit(0x2029) @@ -361,7 +361,7 @@ private let _pageSize: Int = Int(getpagesize()) private let _pageSize: Int = Int(getpagesize()) #elseif canImport(C) private let _pageSize: Int = Int(getpagesize()) -#endif // canImport(Darwin) +#endif // canImport(Darwin) @inline(__always) internal var readBufferSize: Int { diff --git a/Sources/Subprocess/Buffer.swift b/Sources/Subprocess/Buffer.swift index 74216445..a595505d 100644 --- a/Sources/Subprocess/Buffer.swift +++ b/Sources/Subprocess/Buffer.swift @@ -13,7 +13,6 @@ @preconcurrency internal import Dispatch #endif - extension AsyncBufferSequence { /// A immutable collection of bytes public struct Buffer: Sendable { @@ -33,7 +32,7 @@ extension AsyncBufferSequence { if _fastPath(slices.count == 1) { return [.init(data: slices[0], backingData: data)] } - return slices.map{ .init(data: $0, backingData: data) } + return slices.map { .init(data: $0, backingData: data) } } #else internal let data: [UInt8] @@ -87,7 +86,7 @@ extension AsyncBufferSequence.Buffer { return _overrideLifetime(of: bytes, to: self) } } - #endif // SubprocessSpan + #endif // SubprocessSpan } // MARK: - Hashable, Equatable diff --git a/Sources/Subprocess/Configuration.swift b/Sources/Subprocess/Configuration.swift index ed506e69..9e66edbc 100644 --- a/Sources/Subprocess/Configuration.swift +++ b/Sources/Subprocess/Configuration.swift @@ -404,15 +404,15 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible { if environmentString.utf8.first == Character("=").utf8.first { continue } - #endif // os(Windows) + #endif // os(Windows) guard let delimiter = environmentString.firstIndex(of: "=") else { continue } - let key = String(environmentString[environmentString.startIndex.. UnsafeMutablePointer { switch self { case .string(let string): -#if os(Windows) + #if os(Windows) return _strdup(string) -#else + #else return strdup(string) -#endif + #endif case .rawBytes(let rawBytes): -#if os(Windows) + #if os(Windows) return _strdup(rawBytes) -#else + #else return strdup(rawBytes) -#endif + #endif } } @@ -1026,11 +1026,10 @@ internal struct _OrderedSet: Hashable, Sendable { } } -extension _OrderedSet : Sequence { +extension _OrderedSet: Sequence { typealias Iterator = Array.Iterator internal func makeIterator() -> Iterator { return self.elements.makeIterator() } } - diff --git a/Sources/Subprocess/IO/AsyncIO+Dispatch.swift b/Sources/Subprocess/IO/AsyncIO+Dispatch.swift index 280fee91..3bebe122 100644 --- a/Sources/Subprocess/IO/AsyncIO+Dispatch.swift +++ b/Sources/Subprocess/IO/AsyncIO+Dispatch.swift @@ -27,7 +27,7 @@ final class AsyncIO: Sendable { internal init() {} - internal func shutdown() { /* noop on Darwin */ } + internal func shutdown() { /* noop on Darwin */ } internal func read( from diskIO: borrowing IOChannel, @@ -104,7 +104,7 @@ final class AsyncIO: Sendable { } } } - #endif // SubprocessSpan + #endif // SubprocessSpan internal func write( _ array: [UInt8], @@ -167,7 +167,7 @@ final class AsyncIO: Sendable { } #if !canImport(Darwin) -extension DispatchData: @retroactive @unchecked Sendable { } +extension DispatchData: @retroactive @unchecked Sendable {} #endif #endif diff --git a/Sources/Subprocess/IO/AsyncIO+Linux.swift b/Sources/Subprocess/IO/AsyncIO+Linux.swift index b04b747e..9f2114bd 100644 --- a/Sources/Subprocess/IO/AsyncIO+Linux.swift +++ b/Sources/Subprocess/IO/AsyncIO+Linux.swift @@ -33,9 +33,10 @@ import Synchronization private typealias SignalStream = AsyncThrowingStream private let _epollEventSize = 256 -private let _registration: Mutex< - [PlatformFileDescriptor : SignalStream.Continuation] -> = Mutex([:]) +private let _registration: + Mutex< + [PlatformFileDescriptor: SignalStream.Continuation] + > = Mutex([:]) final class AsyncIO: Sendable { @@ -105,8 +106,9 @@ final class AsyncIO: Sendable { ) guard rc == 0 else { let error = SubprocessError( - code: .init(.asyncIOFailed( - "failed to add shutdown fd \(shutdownFileDescriptor) to epoll list") + code: .init( + .asyncIOFailed( + "failed to add shutdown fd \(shutdownFileDescriptor) to epoll list") ), underlyingError: .init(rawValue: errno) ) @@ -149,8 +151,9 @@ final class AsyncIO: Sendable { } // Report other errors let error = SubprocessError( - code: .init(.asyncIOFailed( - "epoll_wait failed") + code: .init( + .asyncIOFailed( + "epoll_wait failed") ), underlyingError: .init(rawValue: errno) ) @@ -239,8 +242,9 @@ final class AsyncIO: Sendable { let flags = fcntl(fileDescriptor.rawValue, F_GETFD) guard flags != -1 else { let error = SubprocessError( - code: .init(.asyncIOFailed( - "failed to get flags for \(fileDescriptor.rawValue)") + code: .init( + .asyncIOFailed( + "failed to get flags for \(fileDescriptor.rawValue)") ), underlyingError: .init(rawValue: errno) ) @@ -249,8 +253,9 @@ final class AsyncIO: Sendable { } guard fcntl(fileDescriptor.rawValue, F_SETFL, flags | O_NONBLOCK) != -1 else { let error = SubprocessError( - code: .init(.asyncIOFailed( - "failed to set \(fileDescriptor.rawValue) to be non-blocking") + code: .init( + .asyncIOFailed( + "failed to set \(fileDescriptor.rawValue) to be non-blocking") ), underlyingError: .init(rawValue: errno) ) @@ -288,8 +293,9 @@ final class AsyncIO: Sendable { let capturedError = errno let error = SubprocessError( - code: .init(.asyncIOFailed( - "failed to add \(fileDescriptor.rawValue) to epoll list") + code: .init( + .asyncIOFailed( + "failed to add \(fileDescriptor.rawValue) to epoll list") ), underlyingError: .init(rawValue: capturedError) ) @@ -314,8 +320,9 @@ final class AsyncIO: Sendable { ) guard rc == 0 else { throw SubprocessError( - code: .init(.asyncIOFailed( - "failed to remove \(fileDescriptor.rawValue) to epoll list") + code: .init( + .asyncIOFailed( + "failed to remove \(fileDescriptor.rawValue) to epoll list") ), underlyingError: .init(rawValue: errno) ) @@ -503,7 +510,7 @@ extension AsyncIO { return 0 } -#if SubprocessSpan + #if SubprocessSpan func write( _ span: borrowing RawSpan, to diskIO: borrowing IOChannel @@ -563,7 +570,7 @@ extension AsyncIO { } return 0 } -#endif + #endif @inline(__always) private func shouldWaitForNextSignal(with error: CInt) -> Bool { @@ -571,6 +578,6 @@ extension AsyncIO { } } -extension Array : AsyncIO._ContiguousBytes where Element == UInt8 {} +extension Array: AsyncIO._ContiguousBytes where Element == UInt8 {} #endif // canImport(Glibc) || canImport(Android) || canImport(Musl) diff --git a/Sources/Subprocess/IO/AsyncIO+Windows.swift b/Sources/Subprocess/IO/AsyncIO+Windows.swift index 031126b3..c86279f8 100644 --- a/Sources/Subprocess/IO/AsyncIO+Windows.swift +++ b/Sources/Subprocess/IO/AsyncIO+Windows.swift @@ -26,9 +26,10 @@ internal import Dispatch private typealias SignalStream = AsyncThrowingStream private let shutdownPort: UInt64 = .max -private let _registration: Mutex< - [UInt64 : SignalStream.Continuation] -> = Mutex([:]) +private let _registration: + Mutex< + [UInt64: SignalStream.Continuation] + > = Mutex([:]) final class AsyncIO: @unchecked Sendable { @@ -36,8 +37,10 @@ final class AsyncIO: @unchecked Sendable { var count: Int { get } func withUnsafeBytes( - _ body: (UnsafeRawBufferPointer - ) throws -> ResultType) rethrows -> ResultType + _ body: ( + UnsafeRawBufferPointer + ) throws -> ResultType + ) rethrows -> ResultType } private final class MonitorThreadContext { @@ -57,9 +60,11 @@ final class AsyncIO: @unchecked Sendable { internal init() { var maybeSetupError: SubprocessError? = nil // Create the the completion port - guard let port = CreateIoCompletionPort( - INVALID_HANDLE_VALUE, nil, 0, 0 - ), port != INVALID_HANDLE_VALUE else { + guard + let port = CreateIoCompletionPort( + INVALID_HANDLE_VALUE, nil, 0, 0 + ), port != INVALID_HANDLE_VALUE + else { let error = SubprocessError( code: .init(.asyncIOFailed("CreateIoCompletionPort failed")), underlyingError: .init(rawValue: GetLastError()) @@ -76,76 +81,79 @@ final class AsyncIO: @unchecked Sendable { /// > A thread in an executable that calls the C run-time library (CRT) /// > should use the _beginthreadex and _endthreadex functions for /// > thread management rather than CreateThread and ExitThread - let threadHandleValue = _beginthreadex(nil, 0, { args in - func reportError(_ error: SubprocessError) { - let continuations = _registration.withLock { store in - return store.values - } - for continuation in continuations { - continuation.finish(throwing: error) + let threadHandleValue = _beginthreadex( + nil, 0, + { args in + func reportError(_ error: SubprocessError) { + let continuations = _registration.withLock { store in + return store.values + } + for continuation in continuations { + continuation.finish(throwing: error) + } } - } - let unmanaged = Unmanaged.fromOpaque(args!) - let context = unmanaged.takeRetainedValue() - - // Monitor loop - while true { - var bytesTransferred: DWORD = 0 - var targetFileDescriptor: UInt64 = 0 - var overlapped: LPOVERLAPPED? = nil - - let monitorResult = GetQueuedCompletionStatus( - context.ioCompletionPort, - &bytesTransferred, - &targetFileDescriptor, - &overlapped, - INFINITE - ) - if !monitorResult { - let lastError = GetLastError() - if lastError == ERROR_BROKEN_PIPE { - // We finished reading the handle. Signal EOF by - // finishing the stream. - // NOTE: here we deliberately leave now unused continuation - // in the store. Windows does not offer an API to remove a - // HANDLE from an IOCP port, therefore we leave the registration - // to signify the HANDLE has already been resisted. - let continuation = _registration.withLock { store -> SignalStream.Continuation? in - if let continuation = store[targetFileDescriptor] { - return continuation + let unmanaged = Unmanaged.fromOpaque(args!) + let context = unmanaged.takeRetainedValue() + + // Monitor loop + while true { + var bytesTransferred: DWORD = 0 + var targetFileDescriptor: UInt64 = 0 + var overlapped: LPOVERLAPPED? = nil + + let monitorResult = GetQueuedCompletionStatus( + context.ioCompletionPort, + &bytesTransferred, + &targetFileDescriptor, + &overlapped, + INFINITE + ) + if !monitorResult { + let lastError = GetLastError() + if lastError == ERROR_BROKEN_PIPE { + // We finished reading the handle. Signal EOF by + // finishing the stream. + // NOTE: here we deliberately leave now unused continuation + // in the store. Windows does not offer an API to remove a + // HANDLE from an IOCP port, therefore we leave the registration + // to signify the HANDLE has already been resisted. + let continuation = _registration.withLock { store -> SignalStream.Continuation? in + if let continuation = store[targetFileDescriptor] { + return continuation + } + return nil } - return nil + continuation?.finish() + continue + } else { + let error = SubprocessError( + code: .init(.asyncIOFailed("GetQueuedCompletionStatus failed")), + underlyingError: .init(rawValue: lastError) + ) + reportError(error) + break } - continuation?.finish() - continue - } else { - let error = SubprocessError( - code: .init(.asyncIOFailed("GetQueuedCompletionStatus failed")), - underlyingError: .init(rawValue: lastError) - ) - reportError(error) - break } - } - // Breakout the monitor loop if we received shutdown from the shutdownFD - if targetFileDescriptor == shutdownPort { - break - } - // Notify the continuations - let continuation = _registration.withLock { store -> SignalStream.Continuation? in - if let continuation = store[targetFileDescriptor] { - return continuation + // Breakout the monitor loop if we received shutdown from the shutdownFD + if targetFileDescriptor == shutdownPort { + break } - return nil + // Notify the continuations + let continuation = _registration.withLock { store -> SignalStream.Continuation? in + if let continuation = store[targetFileDescriptor] { + return continuation + } + return nil + } + continuation?.yield(bytesTransferred) } - continuation?.yield(bytesTransferred) - } - return 0 - }, threadContextPtr.toOpaque(), 0, nil) + return 0 + }, threadContextPtr.toOpaque(), 0, nil) guard threadHandleValue > 0, - let threadHandle = HANDLE(bitPattern: threadHandleValue) else { + let threadHandle = HANDLE(bitPattern: threadHandleValue) + else { // _beginthreadex uses errno instead of GetLastError() let capturedError = _subprocess_windows_get_errno() let error = SubprocessError( @@ -164,7 +172,8 @@ final class AsyncIO: @unchecked Sendable { internal func shutdown() { guard case .success(let ioPort) = ioCompletionPort, - case .success(let monitorThreadHandle) = monitorThread else { + case .success(let monitorThreadHandle) = monitorThread + else { return } // Make sure we don't shutdown the same instance twice @@ -174,10 +183,10 @@ final class AsyncIO: @unchecked Sendable { } // Post status to shutdown HANDLE PostQueuedCompletionStatus( - ioPort, // CompletionPort - 0, // Number of bytes transferred. - shutdownPort, // Completion key to post status - nil // Overlapped + ioPort, // CompletionPort + 0, // Number of bytes transferred. + shutdownPort, // Completion key to post status + nil // Overlapped ) // Wait for monitor thread to exit WaitForSingleObject(monitorThreadHandle, INFINITE) @@ -215,9 +224,11 @@ final class AsyncIO: @unchecked Sendable { // Windows Documentation: The function returns the handle // of the existing I/O completion port if successful - guard CreateIoCompletionPort( - handle, ioPort, completionKey, 0 - ) == ioPort else { + guard + CreateIoCompletionPort( + handle, ioPort, completionKey, 0 + ) == ioPort + else { let error = SubprocessError( code: .init(.asyncIOFailed("CreateIoCompletionPort failed")), underlyingError: .init(rawValue: GetLastError()) @@ -347,7 +358,7 @@ final class AsyncIO: @unchecked Sendable { return try await self._write(array, to: diskIO) } -#if SubprocessSpan + #if SubprocessSpan func write( _ span: borrowing RawSpan, to diskIO: borrowing IOChannel @@ -401,7 +412,7 @@ final class AsyncIO: @unchecked Sendable { } } } -#endif // SubprocessSpan + #endif // SubprocessSpan func _write( _ bytes: Bytes, @@ -469,7 +480,6 @@ final class AsyncIO: @unchecked Sendable { } } -extension Array : AsyncIO._ContiguousBytes where Element == UInt8 {} +extension Array: AsyncIO._ContiguousBytes where Element == UInt8 {} #endif - diff --git a/Sources/Subprocess/IO/Input.swift b/Sources/Subprocess/IO/Input.swift index 55db67d9..72ae534d 100644 --- a/Sources/Subprocess/IO/Input.swift +++ b/Sources/Subprocess/IO/Input.swift @@ -29,7 +29,7 @@ import Foundation import FoundationEssentials #endif -#endif // SubprocessFoundation +#endif // SubprocessFoundation // MARK: - Input diff --git a/Sources/Subprocess/IO/Output.swift b/Sources/Subprocess/IO/Output.swift index 131d2840..ae174b6b 100644 --- a/Sources/Subprocess/IO/Output.swift +++ b/Sources/Subprocess/IO/Output.swift @@ -121,7 +121,7 @@ public struct StringOutput: OutputProtocol { public func output(from span: RawSpan) throws -> String? { // FIXME: Span to String var array: [UInt8] = [] - for index in 0.. TerminationStatus in - // NOTE_EXIT may be delivered slightly before the process becomes reapable, - // so we must call waitid without WNOHANG to avoid a narrow possibility of a race condition. - // If waitid does block, it won't do so for very long at all. - try processIdentifier.blockingReap() - }).mapError { underlyingError in - SubprocessError( - code: .init(.failedToMonitorProcess), - underlyingError: underlyingError - ) - }) + continuation.resume( + with: Result(catching: { () throws(SubprocessError.UnderlyingError) -> TerminationStatus in + // NOTE_EXIT may be delivered slightly before the process becomes reapable, + // so we must call waitid without WNOHANG to avoid a narrow possibility of a race condition. + // If waitid does block, it won't do so for very long at all. + try processIdentifier.blockingReap() + }).mapError { underlyingError in + SubprocessError( + code: .init(.failedToMonitorProcess), + underlyingError: underlyingError + ) + }) } source.resume() } diff --git a/Sources/Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/Subprocess/Platforms/Subprocess+Darwin.swift index cfaa6149..c1aa76c4 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Darwin.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Darwin.swift @@ -31,7 +31,7 @@ import Foundation import FoundationEssentials #endif -#endif // SubprocessFoundation +#endif // SubprocessFoundation // MARK: - PlatformOptions @@ -488,7 +488,7 @@ public struct ProcessIdentifier: Sendable, Hashable { self.value = value } - internal func close() { /* No-op on Darwin */ } + internal func close() { /* No-op on Darwin */ } } extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { @@ -497,4 +497,4 @@ extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertib public var debugDescription: String { "\(self.value)" } } -#endif // canImport(Darwin) +#endif // canImport(Darwin) diff --git a/Sources/Subprocess/Platforms/Subprocess+Linux.swift b/Sources/Subprocess/Platforms/Subprocess+Linux.swift index 444a4c01..05f11ac9 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Linux.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Linux.swift @@ -153,7 +153,7 @@ internal func monitorProcessTermination( } #if canImport(Musl) -extension pthread_t: @retroactive @unchecked Sendable { } +extension pthread_t: @retroactive @unchecked Sendable {} #endif private enum ProcessMonitorState { @@ -161,7 +161,7 @@ private enum ProcessMonitorState { let epollFileDescriptor: CInt let shutdownFileDescriptor: CInt let monitorThread: pthread_t - var continuations: [PlatformFileDescriptor : CheckedContinuation] + var continuations: [PlatformFileDescriptor: CheckedContinuation] } case notStarted @@ -206,7 +206,6 @@ private func shutdown() { pthread_join(storage.monitorThread, nil) } - /// See the following page for the complete list of `async-signal-safe` functions /// https://man7.org/linux/man-pages/man7/signal-safety.7.html /// Only these functions can be used in the signal handler below @@ -385,10 +384,11 @@ private let setup: () = { thread = t case let .failure(error): _processMonitorState.withLock { state in - state = .failed(SubprocessError( - code: .init(.failedToMonitorProcess), - underlyingError: error - )) + state = .failed( + SubprocessError( + code: .init(.failedToMonitorProcess), + underlyingError: error + )) } return } @@ -409,7 +409,6 @@ private let setup: () = { } }() - internal func _setupMonitorSignalHandler() { // Only executed once setup @@ -434,15 +433,17 @@ private func _blockAndWaitForProcessDescriptor(_ pidfd: CInt, context: MonitorTh ) if rc != 0 { let epollErrno = errno - terminationStatus = .failure(SubprocessError( - code: .init(.failedToMonitorProcess), - underlyingError: .init(rawValue: epollErrno) - )) + terminationStatus = .failure( + SubprocessError( + code: .init(.failedToMonitorProcess), + underlyingError: .init(rawValue: epollErrno) + )) } // Notify the continuation let continuation = _processMonitorState.withLock { state -> CheckedContinuation? in guard case .started(let storage) = state, - let continuation = storage.continuations[pidfd] else { + let continuation = storage.continuations[pidfd] + else { return nil } // Remove registration @@ -466,7 +467,7 @@ private func _reapAllKnownChildProcesses(_ signalFd: CInt, context: MonitorThrea // Drain the signalFd var buffer: UInt8 = 0 - while _subprocess_read(signalFd, &buffer, 1) > 0 { /* noop, drain the pipe */ } + while _subprocess_read(signalFd, &buffer, 1) > 0 { /* noop, drain the pipe */ } let resumingContinuations: [ResultContinuation] = _processMonitorState.withLock { state in guard case .started(let storage) = state else { @@ -528,4 +529,4 @@ internal func _isWaitprocessDescriptorSupported() -> Bool { return errno == ECHILD } -#endif // canImport(Glibc) || canImport(Android) || canImport(Musl) +#endif // canImport(Glibc) || canImport(Android) || canImport(Musl) diff --git a/Sources/Subprocess/Platforms/Subprocess+Unix.swift b/Sources/Subprocess/Platforms/Subprocess+Unix.swift index b513dbc3..17d5b32b 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Unix.swift @@ -699,7 +699,7 @@ internal func pthread_create(_ body: @Sendable @escaping () -> ()) throws(Subpro #endif } -#endif // !canImport(Darwin) +#endif // !canImport(Darwin) extension ProcessIdentifier { /// Reaps the zombie for the exited process. This function may block. @@ -780,4 +780,4 @@ internal extension siginfo_t { } #endif -#endif // canImport(Darwin) || canImport(Glibc) || canImport(Android) || canImport(Musl) +#endif // canImport(Darwin) || canImport(Glibc) || canImport(Android) || canImport(Musl) diff --git a/Sources/Subprocess/Platforms/Subprocess+Windows.swift b/Sources/Subprocess/Platforms/Subprocess+Windows.swift index 38827835..ba4824aa 100644 --- a/Sources/Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/Subprocess/Platforms/Subprocess+Windows.swift @@ -132,9 +132,9 @@ extension Configuration { CreateProcessW( applicationNameW, UnsafeMutablePointer(mutating: commandAndArgsW), - nil, // lpProcessAttributes - nil, // lpThreadAttributes - true, // bInheritHandles + nil, // lpProcessAttributes + nil, // lpThreadAttributes + true, // bInheritHandles createProcessFlags, UnsafeMutableRawPointer(mutating: environmentW), intendedWorkingDirW, @@ -279,10 +279,12 @@ extension Configuration { intendedWorkingDir ): (String?, String, String, String?) do { - (applicationName, - commandAndArgs, - environment, - intendedWorkingDir) = try self.preSpawn(withPossibleExecutablePath: executablePath) + ( + applicationName, + commandAndArgs, + environment, + intendedWorkingDir + ) = try self.preSpawn(withPossibleExecutablePath: executablePath) } catch { try self.safelyCloseMultiple( inputRead: inputReadFileDescriptor, @@ -351,7 +353,6 @@ extension Configuration { } } - guard created else { let windowsError = GetLastError() @@ -992,7 +993,7 @@ extension Configuration { // On Windows, the PATH is required in order to locate dlls needed by // the process so we should also pass that to the child if env.pathValue() == nil, - let parentPath = Environment.currentEnvironmentValues().pathValue() + let parentPath = Environment.currentEnvironmentValues().pathValue() { env["Path"] = parentPath } @@ -1357,7 +1358,7 @@ extension String { // not add the \\?\ prefix required by other functions under these conditions). let dwLength: DWORD = GetFullPathNameW(pwszPath, 0, nil, nil) return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { pwszFullPath in - guard (1.. String { var bufferCount = max(1, min(initialSize, maxSize)) while bufferCount <= maxSize { - if let result = try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(bufferCount), { buffer in - let count = try body(buffer) - switch count { - case 0: - throw SubprocessError.UnderlyingError(rawValue: GetLastError()) - case 1.. 0 { - continuation.yield(expected[currentStart..&2", ] ) -#else + #else let setup = TestSetup( executable: .path("/bin/sh"), arguments: ["-c", "cat 1>&2"] ) -#endif + #endif let expected: Data = try Data( contentsOf: URL(filePath: theMysteriousIsland.string) ) @@ -1556,7 +1551,7 @@ extension SubprocessIntegrationTests { $data = New-Object byte[] $size [Console]::OpenStandardOutput().Write($data, 0, $data.Length) [Console]::OpenStandardError().Write($data, 0, $data.Length) - """ + """, ] ) #else @@ -1564,7 +1559,7 @@ extension SubprocessIntegrationTests { executable: .path("/bin/sh"), arguments: [ "-c", - "/bin/dd if=/dev/zero bs=\(1024*1024) count=1; /bin/dd >&2 if=/dev/zero bs=\(1024*1024) count=1;" + "/bin/dd if=/dev/zero bs=\(1024*1024) count=1; /bin/dd >&2 if=/dev/zero bs=\(1024*1024) count=1;", ] ) #endif @@ -1643,20 +1638,20 @@ extension SubprocessIntegrationTests { @Test func testLineSequence() async throws { typealias TestCase = (value: String, count: Int, newLine: String) enum TestCaseSize: CaseIterable { - case large // (1.0 ~ 2.0) * buffer size - case medium // (0.2 ~ 1.0) * buffer size - case small // Less than 16 characters + case large // (1.0 ~ 2.0) * buffer size + case medium // (0.2 ~ 1.0) * buffer size + case small // Less than 16 characters } let newLineCharacters: [[UInt8]] = [ - [0x0A], // Line feed - [0x0B], // Vertical tab - [0x0C], // Form feed - [0x0D], // Carriage return - [0x0D, 0x0A], // Carriage return + Line feed - [0xC2, 0x85], // New line + [0x0A], // Line feed + [0x0B], // Vertical tab + [0x0C], // Form feed + [0x0D], // Carriage return + [0x0D, 0x0A], // Carriage return + Line feed + [0xC2, 0x85], // New line [0xE2, 0x80, 0xA8], // Line Separator - [0xE2, 0x80, 0xA9] // Paragraph separator + [0xE2, 0x80, 0xA9], // Paragraph separator ] // Generate test cases @@ -1703,11 +1698,12 @@ extension SubprocessIntegrationTests { // Choose a random new line let newLine = try #require(newLineCharacters.randomElement()) let string = String(decoding: components + newLine, as: UTF8.self) - testCases.append(( - value: string, - count: components.count + newLine.count, - newLine: String(decoding: newLine, as: UTF8.self) - )) + testCases.append( + ( + value: string, + count: components.count + newLine.count, + newLine: String(decoding: newLine, as: UTF8.self) + )) } return testCases } @@ -1783,7 +1779,7 @@ extension SubprocessIntegrationTests { executable: .name("powershell.exe"), arguments: [ "-Command", - "while (($c = [Console]::In.Read()) -ne -1) { [Console]::Out.Write([char]$c); [Console]::Error.Write([char]$c); [Console]::Out.Flush(); [Console]::Error.Flush() }" + "while (($c = [Console]::In.Read()) -ne -1) { [Console]::Out.Write([char]$c); [Console]::Error.Write([char]$c); [Console]::Out.Flush(); [Console]::Error.Flush() }", ] ) #else @@ -1843,7 +1839,7 @@ extension SubprocessIntegrationTests { ).terminationStatus } group.addTask { - let waitNS = UInt64.random(in: 0..<10_000_000) + let waitNS = UInt64.random(in: 0 ..< 10_000_000) try? await Task.sleep(nanoseconds: waitNS) return nil } @@ -1975,10 +1971,10 @@ extension SubprocessIntegrationTests { var readHandle: HANDLE? = nil var writeHandle: HANDLE? = nil guard CreatePipe(&readHandle, &writeHandle, nil, 0), - readHandle != INVALID_HANDLE_VALUE, - writeHandle != INVALID_HANDLE_VALUE, - let readHandle: HANDLE = readHandle, - let writeHandle: HANDLE = writeHandle + readHandle != INVALID_HANDLE_VALUE, + writeHandle != INVALID_HANDLE_VALUE, + let readHandle: HANDLE = readHandle, + let writeHandle: HANDLE = writeHandle else { throw SubprocessError( code: .init(.failedToCreatePipe), @@ -2001,13 +1997,15 @@ extension SubprocessIntegrationTests { ) // Set write handle to be inheritable only var writeEndHandle: HANDLE? = nil - guard DuplicateHandle( - GetCurrentProcess(), - pipe.writeEnd, - GetCurrentProcess(), - &writeEndHandle, - 0, true, DWORD(DUPLICATE_SAME_ACCESS) - ) else { + guard + DuplicateHandle( + GetCurrentProcess(), + pipe.writeEnd, + GetCurrentProcess(), + &writeEndHandle, + 0, true, DWORD(DUPLICATE_SAME_ACCESS) + ) + else { throw SubprocessError( code: .init(.failedToCreatePipe), underlyingError: .init(rawValue: GetLastError()) @@ -2052,13 +2050,15 @@ extension SubprocessIntegrationTests { ) // Set read handle to be inheritable only var readEndHandle: HANDLE? = nil - guard DuplicateHandle( - GetCurrentProcess(), - pipe.readEnd, - GetCurrentProcess(), - &readEndHandle, - 0, true, DWORD(DUPLICATE_SAME_ACCESS) - ) else { + guard + DuplicateHandle( + GetCurrentProcess(), + pipe.readEnd, + GetCurrentProcess(), + &readEndHandle, + 0, true, DWORD(DUPLICATE_SAME_ACCESS) + ) + else { throw SubprocessError( code: .init(.failedToCreatePipe), underlyingError: .init(rawValue: GetLastError()) @@ -2309,4 +2309,3 @@ extension FileDescriptor { return result } } - diff --git a/Tests/SubprocessTests/LinterTests.swift b/Tests/SubprocessTests/LinterTests.swift index 8be35f8e..0ecd3547 100644 --- a/Tests/SubprocessTests/LinterTests.swift +++ b/Tests/SubprocessTests/LinterTests.swift @@ -40,7 +40,7 @@ private func enableLintingTest() -> Bool { struct SubprocessLintingTest { @Test( .enabled( - if: false/* enableLintingTest() */, + if: false /* enableLintingTest() */, "Skipped until we decide on the rules" ) ) diff --git a/Tests/SubprocessTests/LinuxTests.swift b/Tests/SubprocessTests/LinuxTests.swift index 98f975bf..f0b0f7d7 100644 --- a/Tests/SubprocessTests/LinuxTests.swift +++ b/Tests/SubprocessTests/LinuxTests.swift @@ -103,11 +103,11 @@ struct SubprocessLinuxTests { if subprocess.processIdentifier.processDescriptor > 0 { var statinfo = stat() try #require(fstat(subprocess.processIdentifier.processDescriptor, &statinfo) == 0) - + // In kernel 6.9+, st_ino globally uniquely identifies the process #expect(statinfo.st_ino > 0) } } } } -#endif // canImport(Glibc) || canImport(Bionic) || canImport(Musl) +#endif // canImport(Glibc) || canImport(Bionic) || canImport(Musl) diff --git a/Tests/SubprocessTests/PlatformConformance.swift b/Tests/SubprocessTests/PlatformConformance.swift index 04bd2705..a7268f62 100644 --- a/Tests/SubprocessTests/PlatformConformance.swift +++ b/Tests/SubprocessTests/PlatformConformance.swift @@ -46,4 +46,4 @@ protocol ProcessIdentifierProtocol: Sendable, Hashable, CustomStringConvertible, #endif } -extension ProcessIdentifier : ProcessIdentifierProtocol {} +extension ProcessIdentifier: ProcessIdentifierProtocol {} diff --git a/Tests/SubprocessTests/TestSupport.swift b/Tests/SubprocessTests/TestSupport.swift index b7351202..4cea08dd 100644 --- a/Tests/SubprocessTests/TestSupport.swift +++ b/Tests/SubprocessTests/TestSupport.swift @@ -35,13 +35,13 @@ internal func randomString(length: Int, lettersOnly: Bool = false) -> String { } else { letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" } - return String((0.. [UInt8] { return Array(unsafeUninitializedCapacity: count) { buffer, initializedCount in for i in 0 ..< count { - buffer[i] = UInt8.random(in: 0...255) + buffer[i] = UInt8.random(in: 0 ... 255) } initializedCount = count } diff --git a/Tests/SubprocessTests/UnixTests.swift b/Tests/SubprocessTests/UnixTests.swift index 20ed2459..ee7e9b6a 100644 --- a/Tests/SubprocessTests/UnixTests.swift +++ b/Tests/SubprocessTests/UnixTests.swift @@ -53,7 +53,7 @@ extension SubprocessUnixTests { ) ) func testSubprocessPlatformOptionsUserID() async throws { - let expectedUserID = uid_t(Int.random(in: 1000...2000)) + let expectedUserID = uid_t(Int.random(in: 1000 ... 2000)) var platformOptions = PlatformOptions() platformOptions.userID = expectedUserID try await self.assertID( @@ -71,7 +71,7 @@ extension SubprocessUnixTests { ) ) func testSubprocessPlatformOptionsGroupID() async throws { - let expectedGroupID = gid_t(Int.random(in: 1000...2000)) + let expectedGroupID = gid_t(Int.random(in: 1000 ... 2000)) var platformOptions = PlatformOptions() platformOptions.groupID = expectedGroupID try await self.assertID( @@ -90,8 +90,8 @@ extension SubprocessUnixTests { ) func testSubprocessPlatformOptionsSupplementaryGroups() async throws { var expectedGroups: Set = Set() - for _ in 0.. /dev/null & - child_pid=$! # Retrieve the grand child yes pid - # When SIGINT is sent to the script, kill grand child now - trap "echo >&2 'child: received signal, killing grand child ($child_pid)'; kill -s KILL $child_pid; exit 0" INT - echo "$child_pid" # communicate the child pid to our parent - echo "child: waiting for grand child, pid: $child_pid" >&2 - wait $child_pid # wait for runaway child to exit - """ + """ + set -e + # The following /usr/bin/yes is the runaway grand child. + # It runs in the background forever until this script kills it + /usr/bin/yes "Runaway process from \(#function), please file a SwiftSubprocess bug." > /dev/null & + child_pid=$! # Retrieve the grand child yes pid + # When SIGINT is sent to the script, kill grand child now + trap "echo >&2 'child: received signal, killing grand child ($child_pid)'; kill -s KILL $child_pid; exit 0" INT + echo "$child_pid" # communicate the child pid to our parent + echo "child: waiting for grand child, pid: $child_pid" >&2 + wait $child_pid # wait for runaway child to exit + """, ], platformOptions: platformOptions, output: .string(limit: .max), @@ -394,7 +394,7 @@ extension SubprocessUnixTests { """ echo this string should be discarded >&\(pipe.writeEnd.rawValue); echo wrote into \(pipe.writeEnd.rawValue), echo exit code $?; - """ + """, ], input: .none, output: .string(limit: 64), @@ -410,8 +410,7 @@ extension SubprocessUnixTests { } #expect(readCount == 0) #expect( - result.standardOutput?.trimmingNewLineAndQuotes() == - "wrote into \(pipe.writeEnd.rawValue), echo exit code 1" + result.standardOutput?.trimmingNewLineAndQuotes() == "wrote into \(pipe.writeEnd.rawValue), echo exit code 1" ) } } @@ -440,8 +439,8 @@ extension SubprocessUnixTests { internal func assertNewSessionCreated( with result: CollectedResult< - StringOutput, - Output + StringOutput, + Output > ) throws { try assertNewSessionCreated( @@ -497,7 +496,7 @@ extension SubprocessUnixTests { try await withThrowingTaskGroup(of: Void.self) { group in var running = 0 let byteCount = 1000 - for _ in 0...size))) + #expect(SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &info, DWORD(MemoryLayout.size))) var platformOptions = PlatformOptions() platformOptions.preSpawnProcessConfigurator = { (createProcessFlags, startupInfo) in @@ -375,7 +375,7 @@ extension SubprocessWindowsTests { guard AllocateAndInitializeSid( &netAuthority, - 2, // nSubAuthorityCount + 2, // nSubAuthorityCount DWORD(SECURITY_BUILTIN_DOMAIN_RID), DWORD(DOMAIN_ALIAS_RID_ADMINS), 0, @@ -470,4 +470,4 @@ extension FileDescriptor { } } -#endif // canImport(WinSDK) +#endif // canImport(WinSDK) From 7cbb611ed3d13fca57d1bd421702ac882aeff6f4 Mon Sep 17 00:00:00 2001 From: Charles Hu Date: Thu, 28 Aug 2025 13:23:24 -0700 Subject: [PATCH 2/2] Update spacesAroundRangeFormationOperators to false --- .github/workflows/pull_request.yml | 1 - .swift-format | 3 +-- Sources/Subprocess/AsyncBufferSequence.swift | 2 +- Sources/Subprocess/Buffer.swift | 1 + Sources/Subprocess/Configuration.swift | 4 +-- Sources/Subprocess/IO/AsyncIO+Linux.swift | 2 +- Sources/Subprocess/IO/AsyncIO+Windows.swift | 7 ++--- Sources/Subprocess/IO/Output.swift | 2 +- .../Platforms/Subprocess+Linux.swift | 2 +- .../Platforms/Subprocess+Windows.swift | 6 ++--- Tests/SubprocessTests/AsyncIOTests.swift | 8 +++--- Tests/SubprocessTests/IntegrationTests.swift | 26 +++++++++---------- .../ProcessMonitoringTests.swift | 6 ++--- Tests/SubprocessTests/TestSupport.swift | 6 ++--- Tests/SubprocessTests/UnixTests.swift | 12 ++++----- 15 files changed, 44 insertions(+), 44 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 738582e3..f885682e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -31,7 +31,6 @@ jobs: linux_build_command: 'swift-format lint -s -r --configuration ./.swift-format . && swift test && swift test --disable-default-traits' windows_swift_versions: '["6.1", "nightly-main"]' windows_build_command: | - Invoke-Program swift-format lint -s -r --configuration .\.swift-format . Invoke-Program swift test Invoke-Program swift test --disable-default-traits enable_macos_checks: true diff --git a/.swift-format b/.swift-format index 81456dfc..84d3719d 100644 --- a/.swift-format +++ b/.swift-format @@ -21,7 +21,6 @@ ] }, "prioritizeKeepingFunctionOutputTogether": false, - "reflowMultilineStringLiterals": "never", "respectsExistingLineBreaks": true, "rules": { "AllPublicDeclarationsHaveDocumentation": false, @@ -68,7 +67,7 @@ "UseWhereClausesInForLoops": false, "ValidateDocumentationComments": false }, - "spacesAroundRangeFormationOperators": true, + "spacesAroundRangeFormationOperators": false, "spacesBeforeEndOfLineComments": 1, "tabWidth": 4, "version": 1 diff --git a/Sources/Subprocess/AsyncBufferSequence.swift b/Sources/Subprocess/AsyncBufferSequence.swift index 04fb3a8d..f365f718 100644 --- a/Sources/Subprocess/AsyncBufferSequence.swift +++ b/Sources/Subprocess/AsyncBufferSequence.swift @@ -285,7 +285,7 @@ extension AsyncBufferSequence { continue } return yield() - case lineFeed ..< carriageReturn, newLine1, lineSeparator1, paragraphSeparator1: + case lineFeed..: OutputProtocol { public func output(from span: RawSpan) throws -> String? { // FIXME: Span to String var array: [UInt8] = [] - for index in 0 ..< span.byteCount { + for index in 0.. 0 { - continuation.yield(expected[currentStart ..< expected.count]) + continuation.yield(expected[currentStart.. [UInt8] { // Basic Latin has the range U+0020 ... U+007E - let range: ClosedRange = 0x20 ... 0x7E + let range: ClosedRange = 0x20...0x7E let length: Int switch size { case .large: - length = Int(Double.random(in: 1.0 ..< 2.0) * Double(readBufferSize)) + 1 + length = Int(Double.random(in: 1.0..<2.0) * Double(readBufferSize)) + 1 case .medium: - length = Int(Double.random(in: 0.2 ..< 1.0) * Double(readBufferSize)) + 1 + length = Int(Double.random(in: 0.2..<1.0) * Double(readBufferSize)) + 1 case .small: - length = Int.random(in: 1 ..< 16) + length = Int.random(in: 1..<16) } var buffer: [UInt8] = Array(repeating: 0, count: length) - for index in 0 ..< length { + for index in 0.. String { } else { letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" } - return String((0 ..< length).map { _ in letters.randomElement()! }) + return String((0.. [UInt8] { return Array(unsafeUninitializedCapacity: count) { buffer, initializedCount in - for i in 0 ..< count { - buffer[i] = UInt8.random(in: 0 ... 255) + for i in 0.. = Set() - for _ in 0 ..< Int.random(in: 5 ... 10) { - expectedGroups.insert(gid_t(Int.random(in: 1000 ... 2000))) + for _ in 0..