diff --git a/Package.swift b/Package.swift index 5bf6c100d..535345b3c 100644 --- a/Package.swift +++ b/Package.swift @@ -130,7 +130,7 @@ extension Array where Element == PackageDescription.SwiftSetting { .define("SWT_TARGET_OS_APPLE", .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])), - .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi])), + .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .windows, .wasi])), .define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])), ] diff --git a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift index 2ebcd7fad..46084a1e0 100644 --- a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift @@ -651,7 +651,7 @@ extension Event.ConsoleOutputRecorder.Options { /// Whether or not the system terminal claims to support 16-color ANSI escape /// codes. private static var _terminalSupports16ColorANSIEscapeCodes: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) if let termVariable = Environment.variable(named: "TERM") { return termVariable != "dumb" } @@ -673,7 +673,7 @@ extension Event.ConsoleOutputRecorder.Options { /// Whether or not the system terminal claims to support 256-color ANSI escape /// codes. private static var _terminalSupports256ColorANSIEscapeCodes: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) if let termVariable = Environment.variable(named: "TERM") { return strstr(termVariable, "256") != nil } @@ -695,7 +695,7 @@ extension Event.ConsoleOutputRecorder.Options { /// Whether or not the system terminal claims to support true-color ANSI /// escape codes. private static var _terminalSupportsTrueColorANSIEscapeCodes: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) if let colortermVariable = Environment.variable(named: "COLORTERM") { return strstr(colortermVariable, "truecolor") != nil } diff --git a/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift b/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift index 4f1952211..772caf5ac 100644 --- a/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift @@ -24,7 +24,7 @@ private import _TestingInternals /// /// This constant is not part of the public interface of the testing library. var EXIT_NO_TESTS_FOUND: CInt { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) EX_UNAVAILABLE #elseif os(Windows) CInt(ERROR_NOT_FOUND) diff --git a/Sources/Testing/Events/Recorder/Event.Symbol.swift b/Sources/Testing/Events/Recorder/Event.Symbol.swift index 10f0fbf93..53ed83de0 100644 --- a/Sources/Testing/Events/Recorder/Event.Symbol.swift +++ b/Sources/Testing/Events/Recorder/Event.Symbol.swift @@ -100,7 +100,7 @@ extension Event.Symbol { /// be used to represent it in text-based output. The value of this property /// is platform-dependent. public var unicodeCharacter: Character { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) switch self { case .default: // Unicode: WHITE DIAMOND diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index e625bcfce..a37916f71 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -71,6 +71,10 @@ public struct Backtrace: Sendable { } #elseif os(Linux) initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) +#elseif os(Android) + addresses.withMemoryRebound(to: UnsafeMutableRawPointer.self) { addresses in + initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) + } #elseif os(Windows) initializedCount = Int(RtlCaptureStackBackTrace(0, ULONG(addresses.count), addresses.baseAddress!, nil)) #elseif os(WASI) diff --git a/Sources/Testing/Support/Additions/CommandLineAdditions.swift b/Sources/Testing/Support/Additions/CommandLineAdditions.swift index cc5b02905..ccfce6e31 100644 --- a/Sources/Testing/Support/Additions/CommandLineAdditions.swift +++ b/Sources/Testing/Support/Additions/CommandLineAdditions.swift @@ -29,9 +29,9 @@ extension CommandLine { } } return result! -#elseif os(Linux) +#elseif os(Linux) || os(Android) return try withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(PATH_MAX) * 2) { buffer in - let readCount = readlink("/proc/\(getpid())/exe", buffer.baseAddress!, buffer.count - 1) + let readCount = readlink("/proc/self/exe", buffer.baseAddress!, buffer.count - 1) guard readCount >= 0 else { throw CError(rawValue: swt_errno()) } diff --git a/Sources/Testing/Support/Environment.swift b/Sources/Testing/Support/Environment.swift index f94d662fe..4d835d53c 100644 --- a/Sources/Testing/Support/Environment.swift +++ b/Sources/Testing/Support/Environment.swift @@ -42,7 +42,7 @@ enum Environment { } } -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) /// Get all environment variables from a POSIX environment block. /// /// - Parameters: @@ -103,7 +103,7 @@ enum Environment { } #endif return _get(fromEnviron: _NSGetEnviron()!.pointee!) -#elseif os(Linux) +#elseif os(Linux) || os(Android) _get(fromEnviron: swt_environ()) #elseif os(WASI) _get(fromEnviron: __wasilibc_get_environ()) @@ -170,7 +170,7 @@ enum Environment { } return nil } -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) getenv(name).flatMap { String(validatingCString: $0) } #elseif os(Windows) name.withCString(encodedAs: UTF16.self) { name in diff --git a/Sources/Testing/Support/FileHandle.swift b/Sources/Testing/Support/FileHandle.swift index 453cedcd0..dca2d73b6 100644 --- a/Sources/Testing/Support/FileHandle.swift +++ b/Sources/Testing/Support/FileHandle.swift @@ -141,7 +141,7 @@ struct FileHandle: ~Copyable, Sendable { /// descriptor, `nil` is passed to `body`. borrowing func withUnsafePOSIXFileDescriptor(_ body: (CInt?) throws -> R) rethrows -> R { try withUnsafeCFILEHandle { handle in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) let fd = fileno(handle) #elseif os(Windows) let fd = _fileno(handle) @@ -200,7 +200,7 @@ struct FileHandle: ~Copyable, Sendable { /// other threads. borrowing func withLock(_ body: () throws -> R) rethrows -> R { try withUnsafeCFILEHandle { handle in -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) flockfile(handle) defer { funlockfile(handle) @@ -235,7 +235,7 @@ extension FileHandle { // If possible, reserve enough space in the resulting buffer to contain // the contents of the file being read. var size: Int? -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) withUnsafePOSIXFileDescriptor { fd in var s = stat() if let fd, 0 == fstat(fd, &s) { @@ -373,7 +373,7 @@ extension FileHandle { extension FileHandle { /// Is this file handle a TTY or PTY? var isTTY: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) // If stderr is a TTY and TERM is set, that's good enough for us. withUnsafePOSIXFileDescriptor { fd in if let fd, 0 != isatty(fd), let term = Environment.variable(named: "TERM"), !term.isEmpty { @@ -399,7 +399,7 @@ extension FileHandle { /// Is this file handle a pipe or FIFO? var isPipe: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) withUnsafePOSIXFileDescriptor { fd in guard let fd else { return false diff --git a/Sources/Testing/Support/GetSymbol.swift b/Sources/Testing/Support/GetSymbol.swift index 0c6e99f3f..d61b0b4d0 100644 --- a/Sources/Testing/Support/GetSymbol.swift +++ b/Sources/Testing/Support/GetSymbol.swift @@ -13,7 +13,7 @@ internal import _TestingInternals #if !SWT_NO_DYNAMIC_LINKING /// The platform-specific type of a loaded image handle. -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) typealias ImageAddress = UnsafeMutableRawPointer #elseif os(Windows) typealias ImageAddress = HMODULE @@ -30,7 +30,9 @@ typealias ImageAddress = Never /// declare a wrapper function in the internal module's Stubs.h file. #if SWT_TARGET_OS_APPLE private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: -2) -#elseif os(Linux) +#elseif os(Android) && _pointerBitWidth(_32) +private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: 0xFFFFFFFF) +#elseif os(Linux) || os(Android) private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: 0) #endif @@ -57,7 +59,7 @@ private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: 0) /// calling `EnumProcessModules()` and iterating over the returned handles /// looking for one containing the given function. func symbol(in handle: ImageAddress? = nil, named symbolName: String) -> UnsafeRawPointer? { -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) dlsym(handle ?? RTLD_DEFAULT, symbolName).map(UnsafeRawPointer.init) #elseif os(Windows) symbolName.withCString { symbolName in diff --git a/Sources/Testing/Support/Locked.swift b/Sources/Testing/Support/Locked.swift index eed966eb7..7be1fba22 100644 --- a/Sources/Testing/Support/Locked.swift +++ b/Sources/Testing/Support/Locked.swift @@ -36,7 +36,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// To keep the implementation of this type as simple as possible, /// `pthread_mutex_t` is used on Apple platforms instead of `os_unfair_lock` /// or `OSAllocatedUnfairLock`. -#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) private typealias _Lock = pthread_mutex_t #elseif os(Windows) private typealias _Lock = SRWLOCK @@ -52,7 +52,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { private final class _Storage: ManagedBuffer { deinit { withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) _ = pthread_mutex_destroy(lock) #elseif os(Windows) // No deinitialization needed. @@ -71,7 +71,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { init(rawValue: T) { _storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue }) _storage.withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) _ = pthread_mutex_init(lock, nil) #elseif os(Windows) InitializeSRWLock(lock) @@ -101,7 +101,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// concurrency tools. nonmutating func withLock(_ body: (inout T) throws -> R) rethrows -> R { try _storage.withUnsafeMutablePointers { rawValue, lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) _ = pthread_mutex_lock(lock) defer { _ = pthread_mutex_unlock(lock) @@ -121,7 +121,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { } } -#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) /// Acquire the lock and invoke a function while it is held, yielding both the /// protected value and a reference to the lock itself. /// diff --git a/Sources/Testing/Support/Versions.swift b/Sources/Testing/Support/Versions.swift index 75bb4b88f..238a03b8f 100644 --- a/Sources/Testing/Support/Versions.swift +++ b/Sources/Testing/Support/Versions.swift @@ -55,6 +55,10 @@ let operatingSystemVersion: String = { return "\(release) (\(version))" } } +#elseif os(Android) + if let version = systemProperty(named: "ro.build.version.release") { + return "Android \(version)" + } #elseif os(Windows) // See if we can query the kernel directly, bypassing the fake-out logic added // in Windows 10 and later that misreports the OS version. GetVersionExW() @@ -170,3 +174,22 @@ func sysctlbyname(_ name: String, as _: String.Type) -> String? { } } #endif + +#if os(Android) +/// Get the Android system property with the given name. +/// +/// - Parameters: +/// - name: The name of the system property to get. +/// +/// - Returns: The value of the requested system property, or `nil` if it could +/// not be read or could not be converted to a string. +func systemProperty(named name: String) -> String? { + withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(PROP_VALUE_MAX)) { buffer in + let length = __system_property_get(name, buffer.baseAddress!) + if length > 0 { + return String(validatingCString: buffer.baseAddress!) + } + return nil + } +} +#endif diff --git a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift index ccbf62454..899eaa031 100644 --- a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift +++ b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift @@ -61,10 +61,11 @@ var swiftTestingDirectoryPath: String? { if let homeDirectoryPath = _homeDirectoryPath { return appendPathComponent(swiftTestingDirectoryName, to: homeDirectoryPath) } -#elseif SWT_TARGET_OS_APPLE +#elseif SWT_TARGET_OS_APPLE || os(Android) // Other Apple/Darwin platforms do not support the concept of a home // directory. One exists for the current user, but it's not something that // actually contains user-configurable data like a .swift-testing directory. + // Android also does not support per-user home directories (does it?) #elseif os(Windows) if let appDataDirectoryPath = _appDataDirectoryPath { return appendPathComponent(swiftTestingDirectoryName, to: appDataDirectoryPath) diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp index ca89ac8f9..bd8f4a114 100644 --- a/Sources/_TestingInternals/Discovery.cpp +++ b/Sources/_TestingInternals/Discovery.cpp @@ -306,7 +306,7 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) { } } -#elif defined(__linux__) || defined(_WIN32) || defined(__wasi__) +#elif defined(__linux__) || defined(_WIN32) || defined(__wasi__) || defined(__ANDROID__) #pragma mark - Linux/Windows implementation /// Specifies the address range corresponding to a section. diff --git a/Sources/_TestingInternals/include/Includes.h b/Sources/_TestingInternals/include/Includes.h index 684f23c29..d05250258 100644 --- a/Sources/_TestingInternals/include/Includes.h +++ b/Sources/_TestingInternals/include/Includes.h @@ -120,4 +120,9 @@ #include #endif +#if defined(__ANDROID__) +#pragma clang module import posix_filesystem.linux_stat +#include +#endif + #endif diff --git a/Sources/_TestingInternals/include/Stubs.h b/Sources/_TestingInternals/include/Stubs.h index 95e9c5913..80fc52319 100644 --- a/Sources/_TestingInternals/include/Stubs.h +++ b/Sources/_TestingInternals/include/Stubs.h @@ -74,7 +74,7 @@ static LANGID swt_MAKELANGID(int p, int s) { } #endif -#if defined(__linux__) +#if defined(__linux__) || defined(__ANDROID__) /// The environment block. /// /// By POSIX convention, the environment block variable is declared in client @@ -97,6 +97,7 @@ static char *_Nullable *_Null_unspecified swt_environ(void) { SWT_IMPORT_FROM_STDLIB int pthread_setname_np(pthread_t, const char *); #endif +#if !defined(__ANDROID__) #if __has_include() && defined(si_pid) /// Get the value of the `si_pid` field of a `siginfo_t` structure. /// @@ -120,6 +121,7 @@ static int swt_siginfo_t_si_status(const siginfo_t *siginfo) { return siginfo->si_status; } #endif +#endif #if defined(__wasi__) /// Get the version of the C standard library and runtime used by WASI, if diff --git a/Tests/TestingTests/ABIEntryPointTests.swift b/Tests/TestingTests/ABIEntryPointTests.swift index a2606727f..c1dd6494b 100644 --- a/Tests/TestingTests/ABIEntryPointTests.swift +++ b/Tests/TestingTests/ABIEntryPointTests.swift @@ -127,7 +127,7 @@ struct ABIEntryPointTests { passing arguments: __CommandLineArguments_v0, recordHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void = { _ in } ) async throws -> Bool { -#if !os(Linux) && !SWT_NO_DYNAMIC_LINKING +#if !os(Linux) && !os(Android) && !SWT_NO_DYNAMIC_LINKING // Get the ABI entry point by dynamically looking it up at runtime. // // NOTE: The standard Linux linker does not allow exporting symbols from diff --git a/Tests/TestingTests/Support/EnvironmentTests.swift b/Tests/TestingTests/Support/EnvironmentTests.swift index 7e111e922..0735da7e9 100644 --- a/Tests/TestingTests/Support/EnvironmentTests.swift +++ b/Tests/TestingTests/Support/EnvironmentTests.swift @@ -90,7 +90,7 @@ extension Environment { environment[name] = value } return true -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || os(WASI) if let value { return 0 == setenv(name, value, 1) } diff --git a/Tests/TestingTests/Support/FileHandleTests.swift b/Tests/TestingTests/Support/FileHandleTests.swift index 89a3d246f..7d9d5545a 100644 --- a/Tests/TestingTests/Support/FileHandleTests.swift +++ b/Tests/TestingTests/Support/FileHandleTests.swift @@ -14,7 +14,7 @@ private import _TestingInternals #if !SWT_NO_FILE_IO // NOTE: we don't run these tests on iOS (etc.) because processes on those // platforms are sandboxed and do not have arbitrary filesystem access. -#if os(macOS) || os(Linux) || os(Windows) +#if os(macOS) || os(Linux) || os(Android) || os(Windows) @Suite("FileHandle Tests") struct FileHandleTests { // FileHandle is non-copyable, so it cannot yet be used as a test parameter. @@ -226,6 +226,8 @@ func temporaryDirectory() throws -> String { } #elseif os(Linux) "/tmp" +#elseif os(Android) + Environment.variable(named: "TMPDIR") ?? "/data/local/tmp" #elseif os(Windows) try withUnsafeTemporaryAllocation(of: wchar_t.self, capacity: Int(MAX_PATH + 1)) { buffer in // NOTE: GetTempPath2W() was introduced in Windows 10 Build 20348. diff --git a/cmake/modules/shared/CompilerSettings.cmake b/cmake/modules/shared/CompilerSettings.cmake index f37a58dd4..183d73c04 100644 --- a/cmake/modules/shared/CompilerSettings.cmake +++ b/cmake/modules/shared/CompilerSettings.cmake @@ -20,7 +20,7 @@ add_compile_options( if(APPLE) add_compile_definitions("SWT_TARGET_OS_APPLE") endif() -set(SWT_NO_EXIT_TESTS_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI") +set(SWT_NO_EXIT_TESTS_LIST "iOS" "watchOS" "tvOS" "visionOS" "WASI" "Android") if(CMAKE_SYSTEM_NAME IN_LIST SWT_NO_EXIT_TESTS_LIST) add_compile_definitions("SWT_NO_EXIT_TESTS") endif()