@@ -371,6 +371,33 @@ extension Executable: CustomStringConvertible, CustomDebugStringConvertible {
371371 }
372372}
373373
374+ // MARK: - Executable Introspection
375+
376+ extension Executable {
377+ /// The public representation of an executable's contents.
378+ ///
379+ /// Use this to introspect how an ``Executable`` was constructed (for
380+ /// example, to verify in tests that a configuration builder produced the
381+ /// expected executable reference without spawning a subprocess).
382+ @frozen
383+ public enum Representation : Sendable , Hashable {
384+ /// The executable is referenced by name and resolved against `PATH`.
385+ case name( String )
386+ /// The executable is referenced by an absolute or relative file path.
387+ case path( FilePath )
388+ }
389+
390+ /// The contents of this executable.
391+ public var representation : Representation {
392+ switch self . storage {
393+ case . executable( let name) :
394+ return . name( name)
395+ case . path( let path) :
396+ return . path( path)
397+ }
398+ }
399+ }
400+
374401// MARK: - Arguments
375402
376403/// A collection of arguments to pass to the subprocess.
@@ -379,17 +406,17 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable {
379406 public typealias ArrayLiteralElement = String
380407
381408 internal let storage : [ StringOrRawBytes ]
382- internal let executablePathOverride : StringOrRawBytes ?
409+ internal let _executablePathOverride : StringOrRawBytes ?
383410
384411 /// Creates an arguments value from the given literal values.
385412 public init ( arrayLiteral elements: String ... ) {
386413 self . storage = elements. map { . string( $0) }
387- self . executablePathOverride = nil
414+ self . _executablePathOverride = nil
388415 }
389416 /// Creates an arguments value from the given array.
390417 public init ( _ array: [ String ] ) {
391418 self . storage = array. map { . string( $0) }
392- self . executablePathOverride = nil
419+ self . _executablePathOverride = nil
393420 }
394421
395422 /// Creates an ``Arguments`` value using the given values, but
@@ -403,9 +430,9 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable {
403430 public init ( executablePathOverride: String ? , remainingValues: [ String ] ) {
404431 self . storage = remainingValues. map { . string( $0) }
405432 if let executablePathOverride = executablePathOverride {
406- self . executablePathOverride = . string( executablePathOverride)
433+ self . _executablePathOverride = . string( executablePathOverride)
407434 } else {
408- self . executablePathOverride = nil
435+ self . _executablePathOverride = nil
409436 }
410437 }
411438 #if !os(Windows) // Windows does not support non-unicode arguments
@@ -420,15 +447,15 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable {
420447 public init ( executablePathOverride: [ UInt8 ] ? , remainingValues: [ [ UInt8 ] ] ) {
421448 self . storage = remainingValues. map { . rawBytes( $0) }
422449 if let override = executablePathOverride {
423- self . executablePathOverride = . rawBytes( override)
450+ self . _executablePathOverride = . rawBytes( override)
424451 } else {
425- self . executablePathOverride = nil
452+ self . _executablePathOverride = nil
426453 }
427454 }
428455 /// Creates an arguments value from the array you provide.
429456 public init ( _ array: [ [ UInt8 ] ] ) {
430457 self . storage = array. map { . rawBytes( $0) }
431- self . executablePathOverride = nil
458+ self . _executablePathOverride = nil
432459 }
433460 #endif
434461}
@@ -438,7 +465,7 @@ extension Arguments: CustomStringConvertible, CustomDebugStringConvertible {
438465 public var description : String {
439466 var result : [ String ] = self . storage. map ( \. description)
440467
441- if let override = self . executablePathOverride {
468+ if let override = self . _executablePathOverride {
442469 result. insert ( " override \( override. description) " , at: 0 )
443470 }
444471 return result. description
@@ -448,6 +475,64 @@ extension Arguments: CustomStringConvertible, CustomDebugStringConvertible {
448475 public var debugDescription : String { return self . description }
449476}
450477
478+ // MARK: - Arguments Introspection
479+
480+ extension Arguments {
481+ /// A single argument value, preserving the form in which it was supplied.
482+ ///
483+ /// On POSIX platforms, arguments may be constructed from non-Unicode
484+ /// raw bytes; on Windows, only `String` values are representable.
485+ @frozen
486+ public enum Value : Sendable , Hashable {
487+ /// A string argument.
488+ case string( String )
489+ #if !os(Windows)
490+ /// A raw-bytes argument.
491+ ///
492+ /// - Note: This case is only available on POSIX platforms.
493+ case rawBytes( [ UInt8 ] )
494+ #endif
495+ }
496+
497+ /// The argument that overrides the executable path as `argv[0]`, or `nil`
498+ /// if the executable path is used unchanged.
499+ ///
500+ /// This corresponds to the `executablePathOverride` parameter passed to
501+ /// the initializer, and is useful for verifying configuration in tests
502+ /// without spawning a subprocess.
503+ public var executablePathOverride : Value ? {
504+ self . _executablePathOverride. map ( Value . init)
505+ }
506+ }
507+
508+ extension Arguments : RandomAccessCollection {
509+ public typealias Element = Value
510+ public typealias Index = Int
511+
512+ public var startIndex : Int { self . storage. startIndex }
513+ public var endIndex : Int { self . storage. endIndex }
514+
515+ public subscript( position: Int ) -> Value {
516+ Value ( self . storage [ position] )
517+ }
518+ }
519+
520+ extension Arguments . Value {
521+ internal init ( _ storage: StringOrRawBytes ) {
522+ switch storage {
523+ case . string( let s) :
524+ self = . string( s)
525+ case . rawBytes( let b) :
526+ #if os(Windows)
527+ // Unreachable: The Windows public API cannot construct rawBytes arguments.
528+ fatalError ( " Internal inconsistency: rawBytes argument on Windows " )
529+ #else
530+ self = . rawBytes( b)
531+ #endif
532+ }
533+ }
534+ }
535+
451536// MARK: - Environment
452537
453538/// A set of environment variables to use when running the subprocess.
@@ -586,7 +671,8 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible {
586671}
587672
588673extension Environment . Key {
589- package static let path : Self = " PATH "
674+ /// The well-known key for the `PATH` environment variable.
675+ public static let path : Self = " PATH "
590676}
591677
592678extension Environment . Key : CodingKeyRepresentable { }
@@ -648,6 +734,50 @@ extension Environment.Key: RawRepresentable {
648734
649735extension Environment . Key : Sendable { }
650736
737+ // MARK: - Environment Introspection
738+
739+ extension Environment {
740+ /// The public representation of an environment's contents.
741+ ///
742+ /// Use this to introspect how an ``Environment`` was constructed (for
743+ /// example, to verify in tests that a configuration builder produced
744+ /// the expected inherited overrides or custom values without spawning a
745+ /// subprocess).
746+ @nonexhaustive
747+ public enum Representation : Sendable , Hashable {
748+ /// The environment inherits from the current process, with the given
749+ /// updates applied.
750+ ///
751+ /// A `nil` value for a key indicates that the key is unset relative to
752+ /// the inherited environment, rather than being set to an empty value.
753+ case inherited( updates: [ Key : String ? ] )
754+ /// The environment uses the given custom values, with no inheritance
755+ /// from the current process.
756+ case custom( [ Key : String ] )
757+ #if !os(Windows)
758+ /// The environment uses the given raw bytes, with no inheritance from
759+ /// the current process.
760+ ///
761+ /// - Note: This case is only available on POSIX platforms.
762+ case rawBytes( [ [ UInt8 ] ] )
763+ #endif
764+ }
765+
766+ /// The contents of this environment.
767+ public var representation : Representation {
768+ switch self . config {
769+ case . inherit( let updates) :
770+ return . inherited( updates: updates)
771+ case . custom( let values) :
772+ return . custom( values)
773+ #if !os(Windows)
774+ case . rawBytes( let bytes) :
775+ return . rawBytes( bytes)
776+ #endif
777+ }
778+ }
779+ }
780+
651781// MARK: - TerminationStatus
652782
653783/// The exit status of a subprocess.
0 commit comments