-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Experimental global executor cooperating with JS event loop
- Loading branch information
1 parent
aa521a1
commit d1814e1
Showing
11 changed files
with
496 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
IntegrationTests/TestSuites/Sources/ConcurrencyTests/UnitTestUtils.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import JavaScriptKit | ||
|
||
var printTestNames = false | ||
// Uncomment the next line to print the name of each test suite before running it. | ||
// This will make it easier to debug any errors that occur on the JS side. | ||
//printTestNames = true | ||
|
||
func test(_ name: String, testBlock: () throws -> Void) throws { | ||
if printTestNames { print(name) } | ||
do { | ||
try testBlock() | ||
} catch { | ||
print("Error in \(name)") | ||
print(error) | ||
throw error | ||
} | ||
} | ||
|
||
func asyncTest(_ name: String, testBlock: () async throws -> Void) async throws -> Void { | ||
if printTestNames { print(name) } | ||
do { | ||
try await testBlock() | ||
} catch { | ||
print("Error in \(name)") | ||
print(error) | ||
throw error | ||
} | ||
} | ||
|
||
struct MessageError: Error { | ||
let message: String | ||
let file: StaticString | ||
let line: UInt | ||
let column: UInt | ||
init(_ message: String, file: StaticString, line: UInt, column: UInt) { | ||
self.message = message | ||
self.file = file | ||
self.line = line | ||
self.column = column | ||
} | ||
} | ||
|
||
func expectEqual<T: Equatable>( | ||
_ lhs: T, _ rhs: T, | ||
file: StaticString = #file, line: UInt = #line, column: UInt = #column | ||
) throws { | ||
if lhs != rhs { | ||
throw MessageError("Expect to be equal \"\(lhs)\" and \"\(rhs)\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectCast<T, U>( | ||
_ value: T, to type: U.Type = U.self, | ||
file: StaticString = #file, line: UInt = #line, column: UInt = #column | ||
) throws -> U { | ||
guard let value = value as? U else { | ||
throw MessageError("Expect \"\(value)\" to be \(U.self)", file: file, line: line, column: column) | ||
} | ||
return value | ||
} | ||
|
||
func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObject { | ||
switch value { | ||
case let .object(ref): return ref | ||
default: | ||
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArray { | ||
guard let array = value.array else { | ||
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column) | ||
} | ||
return array | ||
} | ||
|
||
func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunction { | ||
switch value { | ||
case let .function(ref): return ref | ||
default: | ||
throw MessageError("Type of \(value) should be \"function\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectBoolean(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Bool { | ||
switch value { | ||
case let .boolean(bool): return bool | ||
default: | ||
throw MessageError("Type of \(value) should be \"boolean\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectNumber(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Double { | ||
switch value { | ||
case let .number(number): return number | ||
default: | ||
throw MessageError("Type of \(value) should be \"number\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> String { | ||
switch value { | ||
case let .string(string): return String(string) | ||
default: | ||
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column) | ||
} | ||
} | ||
|
||
func expectAsyncThrow<T>(_ body: @autoclosure () async throws -> T, file: StaticString = #file, line: UInt = #line, column: UInt = #column) async throws -> Error { | ||
do { | ||
_ = try await body() | ||
} catch { | ||
return error | ||
} | ||
throw MessageError("Expect to throw an exception", file: file, line: line, column: column) | ||
} | ||
|
||
func expectNotNil<T>(_ value: T?, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws { | ||
switch value { | ||
case .some: return | ||
case .none: | ||
throw MessageError("Expect a non-nil value", file: file, line: line, column: column) | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
IntegrationTests/TestSuites/Sources/ConcurrencyTests/main.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import JavaScriptEventLoop | ||
import JavaScriptKit | ||
|
||
|
||
func entrypoint() async throws { | ||
struct E: Error, Equatable { | ||
let value: Int | ||
} | ||
|
||
try await asyncTest("Task.init value") { | ||
let handle = Task { 1 } | ||
try expectEqual(await handle.value, 1) | ||
} | ||
|
||
try await asyncTest("Task.init throws") { | ||
let handle = Task { | ||
throw E(value: 2) | ||
} | ||
let error = try await expectAsyncThrow(await handle.value) | ||
let e = try expectCast(error, to: E.self) | ||
try expectEqual(e, E(value: 2)) | ||
} | ||
|
||
try await asyncTest("await resolved Promise") { | ||
let p = JSPromise(resolver: { resolve in | ||
resolve(.success(1)) | ||
}) | ||
try await expectEqual(p.get(), 1) | ||
} | ||
|
||
try await asyncTest("await rejected Promise") { | ||
let p = JSPromise(resolver: { resolve in | ||
resolve(.failure(.number(3))) | ||
}) | ||
let error = try await expectAsyncThrow(await p.get()) | ||
let jsValue = try expectCast(error, to: JSValue.self) | ||
try expectEqual(jsValue, 3) | ||
} | ||
|
||
try await asyncTest("Continuation") { | ||
let value = await withUnsafeContinuation { cont in | ||
cont.resume(returning: 1) | ||
} | ||
try expectEqual(value, 1) | ||
} | ||
|
||
try await asyncTest("Task.sleep(_:)") { | ||
await Task.sleep(1_000_000_000) | ||
} | ||
|
||
// FIXME(katei): Somehow it doesn't work due to a mysterious unreachable inst | ||
// at the end of thunk. | ||
// This issue is not only on JS host environment, but also on standalone coop executor. | ||
// try await asyncTest("Task.sleep(nanoseconds:)") { | ||
// try await Task.sleep(nanoseconds: 1_000_000_000) | ||
// } | ||
} | ||
|
||
|
||
// Note: Please define `USE_SWIFT_TOOLS_VERSION_NEWER_THAN_5_5` if the swift-tools-version is newer | ||
// than 5.5 to avoid the linking issue. | ||
#if USE_SWIFT_TOOLS_VERSION_NEWER_THAN_5_5 | ||
// Workaround: The latest SwiftPM rename main entry point name of executable target | ||
// to avoid conflicting "main" with test target since `swift-tools-version >= 5.5`. | ||
// The main symbol is renamed to "{{module_name}}_main" and it's renamed again to be | ||
// "main" when linking the executable target. The former renaming is done by Swift compiler, | ||
// and the latter is done by linker, so SwiftPM passes some special linker flags for each platform. | ||
// But SwiftPM assumes that wasm-ld supports it by returning an empty array instead of nil even though | ||
// wasm-ld doesn't support it yet. | ||
// ref: https://github.com/apple/swift-package-manager/blob/1be68e811d0d814ba7abbb8effee45f1e8e6ec0d/Sources/Build/BuildPlan.swift#L117-L126 | ||
// So define an explicit "main" by @_cdecl | ||
@_cdecl("main") | ||
func main(argc: Int32, argv: Int32) -> Int32 { | ||
JavaScriptEventLoop.installGlobalExecutor() | ||
Task { | ||
do { | ||
try await entrypoint() | ||
} catch { | ||
print(error) | ||
} | ||
} | ||
return 0 | ||
} | ||
#else | ||
JavaScriptEventLoop.installGlobalExecutor() | ||
Task { | ||
do { | ||
try await entrypoint() | ||
} catch { | ||
print(error) | ||
} | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const { startWasiTask } = require("../lib"); | ||
|
||
startWasiTask("./dist/ConcurrencyTests.wasm").catch((err) => { | ||
console.log(err); | ||
process.exit(1); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.