Skip to content

Commit

Permalink
Introduce a new SPM-based test harness
Browse files Browse the repository at this point in the history
Using the Swift Package Manager instead of xcode allows for developing
on Linux and Windows, and could potentially make life easier.

Ref. chinedufn#306
  • Loading branch information
colinmarc committed Jan 27, 2025
1 parent 5c3d672 commit 0edc00b
Show file tree
Hide file tree
Showing 28 changed files with 619 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,23 @@
//

func swift_func_takes_callback_with_result_arg(
arg: (RustResult<CallbackTestOpaqueRustType, String>) -> Void
arg: (RustResult<CallbackTestOpaqueRustType, String>) -> ()
) {
arg(.Ok(CallbackTestOpaqueRustType(555)))
}

public class ResultTestOpaqueSwiftType {
var num: UInt32

init(val: UInt32) {
self.num = val
}

func val() -> UInt32 {
self.num
}
}

extension AsyncRustFnReturnStruct: @unchecked Sendable {}

extension ResultTestOpaqueRustType: @unchecked Sendable {}
extension ResultTestOpaqueRustType: Error {}

Expand All @@ -43,7 +41,5 @@ extension ResultTransparentStruct: Error {}
extension SameEnum: @unchecked Sendable {}
extension SameEnum: Error {}

extension AsyncResultOkEnum: @unchecked Sendable {}

extension AsyncResultErrEnum: @unchecked Sendable {}
extension AsyncResultErrEnum: Error {}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,30 @@ class ResultTests: XCTestCase {
rust_func_takes_result_string(.Ok("Success Message"))
rust_func_takes_result_string(.Err("Error Message"))
}


/// Verify that we can return a Result<String, String> from Rust -> Swift.
///
/// The Err case evidences Swift’s `Error` protocol is implemented correctly
/// for `RustStringRef`, i.e. `extension RustStringRef: Error {}`
func testSwiftCallRustReturnsResultString() throws {
let resultOk = try! rust_func_returns_result_string(true)
XCTAssertEqual(resultOk.toString(), "Success Message")

do {
let _ = try rust_func_returns_result_string(false)
XCTFail("The function should have returned an error.")
} catch let error as RustString {
XCTAssertEqual(error.toString(), "Error Message")
}
}

/// Verify that we can pass a Result<OpaqueRust, OpaqueRust> from Swift -> Rust
func testSwiftCallRustResultOpaqueRust() throws {
let reflectedOk = try! rust_func_reflect_result_opaque_rust(
.Ok(ResultTestOpaqueRustType(111))
)
XCTAssertEqual(reflectedOk.val(), 111)

do {
let _ = try rust_func_reflect_result_opaque_rust(
.Err(ResultTestOpaqueRustType(222))
Expand All @@ -30,7 +46,7 @@ class ResultTests: XCTestCase {
XCTAssertEqual(error.val(), 222)
}
}

/// Verify that we can pass a Result<OpaqueSwift, OpaqueSwift> from Swift -> Rust
func testSwiftCallRustResultOpaqueSwift() throws {
rust_func_takes_result_opaque_swift(
Expand Down Expand Up @@ -64,7 +80,7 @@ class ResultTests: XCTestCase {
XCTAssertEqual(error.val(), 222)
}
}

/// Verify that we can receive a Result<OpaqueRust, TransparentEnum> from Rust
func testResultOpaqueRustTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") {
Expand All @@ -75,7 +91,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand All @@ -95,7 +111,7 @@ class ResultTests: XCTestCase {
}
}
}

/// Verify that we can receive a Result<TransparentEnum, OpaqueRust> from Rust
func testResultTransparentEnumOpaqueRust() throws {
XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") {
Expand All @@ -114,7 +130,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand All @@ -127,7 +143,7 @@ class ResultTests: XCTestCase {
}
}
}

/// Verify that we can receive a Result<(), TransparentEnum> from Rust
func testResultUnitTypeTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a Unit type") {
Expand All @@ -138,7 +154,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand All @@ -158,7 +174,7 @@ class ResultTests: XCTestCase {
}
}
}

/// Verify that we can receive a Result<(primitive type, OpaqueRustType, String), TransparentEnum> from Rust
func testResultTupleTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a tuple type") {
Expand All @@ -172,7 +188,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand Down Expand Up @@ -249,7 +265,7 @@ class ResultTests: XCTestCase {
XCTAssertEqual(UInt32(i), value.val())
}
}

/// Verify that we can use throwing initializers defined on the Rust side.
func testThrowingInitializers() throws {
XCTContext.runActivity(named: "Should fail") {
Expand Down
4 changes: 2 additions & 2 deletions crates/swift-integration-tests/build.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::path::PathBuf;

fn main() {
let out_dir = "../../SwiftRustIntegrationTestRunner/Generated";
let out_dir = PathBuf::from(out_dir);
// TODO: we can use --artifact-dir with OUT_DIR when that stabilizes.
let out_dir = PathBuf::from("../../integration-tests/Sources/Generated");

let mut bridges = vec![];
read_files_recursive(PathBuf::from("src"), &mut bridges);
Expand Down
8 changes: 8 additions & 0 deletions integration-tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
10 changes: 10 additions & 0 deletions integration-tests/.swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"lineLength": 100,
"indentation": {
"spaces": 4
},
"maximumBlankLines": 1,
"respectsExistingLineBreaks": true,
"lineBreakBeforeControlFlowKeywords": false,
}
26 changes: 26 additions & 0 deletions integration-tests/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// swift-tools-version: 6.0

import PackageDescription

let linkerSettings: [LinkerSetting] = [
.linkedLibrary("swift_integration_tests"),
.unsafeFlags(["-L../target/debug/"])
]

let package = Package(
name: "IntegrationTests",
targets: [
// The compiled static rust library.
.systemLibrary(
name: "RustLib"),
// The generated Swift wrapper code for the Rust library, plus some
// Swift code used by the Rust library.
.target(
name: "SharedLib",
dependencies: ["RustLib"],
linkerSettings: linkerSettings),
.testTarget(
name: "IntegrationTests",
dependencies: ["SharedLib", "RustLib"]),
]
)
2 changes: 2 additions & 0 deletions integration-tests/Sources/Generated/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions integration-tests/Sources/RustLib/bridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#include "../Generated/SwiftBridgeCore.h"
#include "../Generated/swift-integration-tests/swift-integration-tests.h"
5 changes: 5 additions & 0 deletions integration-tests/Sources/RustLib/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module RustLib [system] {
header "bridge.h"
link "swift_integration_tests"
export *
}
33 changes: 33 additions & 0 deletions integration-tests/Sources/SharedLib/ASwiftStack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// RustTests.swift
// SwiftRustIntegrationTestRunner
//
// Created by Frankie Nwafili on 11/14/21.
//

import Foundation

public class ASwiftStack {
private var stack: [UInt8] = []

func push (val: UInt8) {
stack.append(val)
}

func pop () {
let _ = stack.popLast();
}

func as_ptr() -> UnsafePointer<UInt8> {
UnsafePointer(self.stack)
}

func len () -> UInt {
UInt(stack.count)
}

func as_slice () -> UnsafeBufferPointer<UInt8> {
UnsafeBufferPointer(start: self.as_ptr(), count: Int(self.len()))
}
}

57 changes: 57 additions & 0 deletions integration-tests/Sources/SharedLib/Callbacks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// Callbacks.swift
// SwiftRustIntegrationTestRunner
//
// Created by Frankie Nwafili on 9/11/22.
//

import Foundation

func swift_takes_fnonce_callback_no_args_no_return(arg: () -> ()) {
arg()
}

func swift_takes_fnonce_callback_primitive(
arg: (UInt8) -> UInt8
) -> UInt8 {
arg(4)
}

func swift_takes_fnonce_callback_opaque_rust(
arg: (CallbackTestOpaqueRustType) -> CallbackTestOpaqueRustType
) {
let doubled = arg(CallbackTestOpaqueRustType(10))
if doubled.val() != 20 {
fatalError("Callback not called")
}
}

func swift_takes_two_fnonce_callbacks(
arg1: () -> (),
arg2: (UInt8) -> UInt16
) -> UInt16 {
arg1()
return arg2(3)
}

func swift_takes_fnonce_callback_with_two_params(
arg: (UInt8, UInt16) -> UInt16
) -> UInt16 {
arg(1, 2)
}

/// When given an FnOnce callback this should panic.
func swift_calls_rust_fnonce_callback_twice(arg: () -> ()) {
arg()
arg()
}

class SwiftMethodCallbackTester {
func method_with_fnonce_callback(callback: () -> ()) {
callback()
}

func method_with_fnonce_callback_primitive(callback: (UInt16) -> UInt16) -> UInt16 {
callback(5)
}
}
12 changes: 12 additions & 0 deletions integration-tests/Sources/SharedLib/FunctionAttributes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// FunctionAttributes.swift
// SwiftRustIntegrationTestRunner
//
// Created by Erik Živković on 2022-12-17.
//

import Foundation

func testCallSwiftFromRustByNameAttribute() -> RustString {
return "StringFromSwift".intoRustString()
}
2 changes: 2 additions & 0 deletions integration-tests/Sources/SharedLib/Generated/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
Loading

0 comments on commit 0edc00b

Please sign in to comment.