Skip to content

Commit

Permalink
Add basic platform support for Android. (#653)
Browse files Browse the repository at this point in the history
This PR seeks out all the nooks and crannies where we have
platform-specific code or logic and adds Android. In most cases, it's as
simple as changing `os(Linux)` to `os(Linux) || os(Android)` but there
are a few spots where they diverge. The PR should be _mostly_
self-explanatory.

### Checklist:

- [x] Code and documentation should follow the style of the [Style
Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md).
- [x] If public symbols are renamed or modified, DocC references should
be updated.


Co-authored-by: Saleem Abdulrasool <[email protected]>
  • Loading branch information
grynspan and compnerd authored Aug 30, 2024
1 parent 4c1286e commit f2c8ee1
Show file tree
Hide file tree
Showing 19 changed files with 70 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])),
]
Expand Down
6 changes: 3 additions & 3 deletions Sources/Testing/ABI/EntryPoints/EntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/Events/Recorder/Event.Symbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Sources/Testing/SourceAttribution/Backtrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Testing/Support/Additions/CommandLineAdditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Testing/Support/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions Sources/Testing/Support/FileHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ struct FileHandle: ~Copyable, Sendable {
/// descriptor, `nil` is passed to `body`.
borrowing func withUnsafePOSIXFileDescriptor<R>(_ 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)
Expand Down Expand Up @@ -215,7 +215,7 @@ struct FileHandle: ~Copyable, Sendable {
/// other threads.
borrowing func withLock<R>(_ 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)
Expand Down Expand Up @@ -250,7 +250,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) {
Expand Down Expand Up @@ -388,7 +388,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 {
Expand All @@ -414,7 +414,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
Expand Down
8 changes: 5 additions & 3 deletions Sources/Testing/Support/GetSymbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions Sources/Testing/Support/Locked.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct Locked<T>: 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
Expand All @@ -52,7 +52,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
private final class _Storage: ManagedBuffer<T, _Lock> {
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.
Expand All @@ -71,7 +71,7 @@ struct Locked<T>: 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)
Expand Down Expand Up @@ -101,7 +101,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
/// concurrency tools.
nonmutating func withLock<R>(_ 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)
Expand All @@ -121,7 +121,7 @@ struct Locked<T>: 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.
///
Expand Down
23 changes: 23 additions & 0 deletions Sources/Testing/Support/Versions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion Sources/Testing/Traits/Tags/Tag.Color+Loading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Sources/_TestingInternals/Discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions Sources/_TestingInternals/include/Includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,9 @@
#include <Psapi.h>
#endif

#if defined(__ANDROID__)
#pragma clang module import posix_filesystem.linux_stat
#include <sys/system_properties.h>
#endif

#endif
4 changes: 3 additions & 1 deletion Sources/_TestingInternals/include/Stubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(<signal.h>) && defined(si_pid)
/// Get the value of the `si_pid` field of a `siginfo_t` structure.
///
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Tests/TestingTests/ABIEntryPointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Tests/TestingTests/Support/EnvironmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 3 additions & 1 deletion Tests/TestingTests/Support/FileHandleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion cmake/modules/shared/CompilerSettings.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit f2c8ee1

Please sign in to comment.