146 lines
6.8 KiB
Swift
146 lines
6.8 KiB
Swift
// AsyncCallableTests.swift
|
|
// Covers the GIO async pattern binding: `planAsyncPairs` recognizing a
|
|
// `*_async`/`*_finish` pair and `renderAsyncMethod` emitting the paired
|
|
// Swift `async` method. Mirrors `CallbackGenerationTests.swift`'s style —
|
|
// exercising the planner through its public entry points and the renderer
|
|
// through `renderModule`'s public file output.
|
|
|
|
import Testing
|
|
|
|
@testable import GObjectGeneratorCore
|
|
|
|
@Suite("GIO async pattern")
|
|
struct AsyncCallableTests {
|
|
|
|
// MARK: - Fixture
|
|
|
|
/// A minimal `Gio`-module fixture: an `AsyncResult` class, a `Widget`
|
|
/// class with one properly-shaped `foo_async`/`foo_finish` pair, and
|
|
/// the `Gio.AsyncReadyCallback` callback type the pair's starter uses.
|
|
/// Modeled entirely inside "Gio" itself (rather than a separate
|
|
/// dependent module) so rule 6 (`currentModule == "Gio"`) is satisfied
|
|
/// trivially, keeping the fixture minimal.
|
|
func makeFooAsyncClass(includeFinish: Bool = true) -> Class {
|
|
var methods = [
|
|
Method(
|
|
name: "foo_async", cIdentifier: "g_widget_foo_async",
|
|
parameters: [
|
|
Parameter(name: "self", type: .typeRef("Widget"), isInstanceParameter: true),
|
|
Parameter(name: "callback", type: .typeRef("AsyncReadyCallback", namespace: "Gio"),
|
|
cType: "GAsyncReadyCallback", isNullable: true),
|
|
Parameter(name: "user_data", type: .pointer, cType: "gpointer", isNullable: true),
|
|
],
|
|
returnValue: ReturnValue()
|
|
),
|
|
]
|
|
if includeFinish {
|
|
methods.append(Method(
|
|
name: "foo_finish", cIdentifier: "g_widget_foo_finish",
|
|
parameters: [
|
|
Parameter(name: "self", type: .typeRef("Widget"), isInstanceParameter: true),
|
|
Parameter(name: "result", type: .typeRef("AsyncResult", namespace: "Gio"), cType: "GAsyncResult*"),
|
|
],
|
|
returnValue: ReturnValue(type: .boolean),
|
|
throwsGError: true
|
|
))
|
|
}
|
|
return Class(name: "Widget", cType: "GWidget", parent: nil,
|
|
getTypeFunction: "g_widget_get_type", methods: methods)
|
|
}
|
|
|
|
func makeContext(includeFinish: Bool = true) -> (context: MapContext, klass: Class) {
|
|
let klass = makeFooAsyncClass(includeFinish: includeFinish)
|
|
let gio = Repository(namespaces: [
|
|
Namespace(
|
|
name: "Gio", version: "2.0",
|
|
classes: [
|
|
Class(name: "AsyncResult", cType: "GAsyncResult", parent: nil,
|
|
getTypeFunction: "g_async_result_get_type"),
|
|
klass,
|
|
],
|
|
callbacks: [Callback(
|
|
name: "AsyncReadyCallback", cType: "GAsyncReadyCallback",
|
|
parameters: [
|
|
Parameter(name: "source_object", type: .pointer, cType: "gpointer", isNullable: true),
|
|
Parameter(name: "res", type: .typeRef("AsyncResult"), cType: "GAsyncResult*"),
|
|
Parameter(name: "data", type: .pointer, cType: "gpointer", isNullable: true),
|
|
]
|
|
)]
|
|
)
|
|
])
|
|
let registry = TypeRegistry(repositories: ["Gio": gio])
|
|
let context = MapContext(registry: registry, currentModule: "Gio", currentNamespace: "Gio")
|
|
return (context, klass)
|
|
}
|
|
|
|
// MARK: - Recognizer
|
|
|
|
@Test("A parsed foo_async + foo_finish pair produces one AsyncCallablePlan, and the starter's asyncRole parameters are the last two")
|
|
func fooAsyncFooFinishProducesOnePair() throws {
|
|
let (context, klass) = makeContext()
|
|
let (plan, memberSkips) = planClass(klass, context: context)
|
|
|
|
#expect(plan.asyncMethods.count == 1)
|
|
let pair = try #require(plan.asyncMethods.first)
|
|
#expect(pair.starter.name == "fooAsync")
|
|
#expect(pair.finish.name == "fooFinish")
|
|
|
|
// The starter's Swift-facing signature carries no callback/user-data
|
|
// parameters — only the async-role pair, trailing.
|
|
let realParams = pair.starter.parameters.filter { !$0.isInstanceParameter }
|
|
#expect(realParams.count == 2)
|
|
#expect(realParams[0].asyncRole == .callback)
|
|
#expect(realParams[1].asyncRole == .userData)
|
|
|
|
// No callable reaches methodPlans still skipped for the callback param.
|
|
#expect(!plan.methods.contains { $0.name == "fooAsync" })
|
|
#expect(plan.methods.contains { $0.name == "fooFinish" })
|
|
|
|
// The stale D4.3 skip for the paired starter was dropped.
|
|
#expect(!memberSkips.contains { $0.cIdentifier == "g_widget_foo_async" })
|
|
}
|
|
|
|
@Test("foo_async with no foo_finish produces no pair and keeps its callbackWithoutUserData skip")
|
|
func fooAsyncWithoutFinishStaysSkipped() throws {
|
|
let (context, klass) = makeContext(includeFinish: false)
|
|
let (plan, memberSkips) = planClass(klass, context: context)
|
|
|
|
#expect(plan.asyncMethods.isEmpty)
|
|
#expect(!plan.methods.contains { $0.name == "fooAsync" })
|
|
let skip = try #require(memberSkips.first { $0.cIdentifier == "g_widget_foo_async" })
|
|
#expect(skip.reason == .callbackWithoutUserData)
|
|
}
|
|
|
|
// MARK: - Renderer
|
|
|
|
@Test("renderAsyncMethod output contains async throws, _sgtkAwaitAsyncReady, defer g_object_unref, and AsyncResultRef(retaining:)")
|
|
func renderedAsyncMethodContainsExpectedFragments() throws {
|
|
let (context, klass) = makeContext()
|
|
let (plan, _) = planClass(klass, context: context)
|
|
let module = ModulePlan(module: "Gio", types: [.class(plan)], skips: [], coverage: CoverageStats())
|
|
let files = renderModule(module)
|
|
let source = try #require(files["Widget.swift"])
|
|
|
|
#expect(source.contains("public func fooAsync(") )
|
|
#expect(source.contains(") async throws"))
|
|
#expect(source.contains("_sgtkAwaitAsyncReady"))
|
|
#expect(source.contains("defer { g_object_unref("))
|
|
#expect(source.contains("AsyncResultRef(retaining:"))
|
|
#expect(source.contains("try fooFinish(result:"))
|
|
}
|
|
|
|
@Test("swiftSignature for a starter omits the callback and user-data parameters")
|
|
func starterSignatureOmitsAsyncRoleParameters() throws {
|
|
let (context, klass) = makeContext()
|
|
let (plan, _) = planClass(klass, context: context)
|
|
let module = ModulePlan(module: "Gio", types: [.class(plan)], skips: [], coverage: CoverageStats())
|
|
let files = renderModule(module)
|
|
let source = try #require(files["Widget.swift"])
|
|
|
|
// The starter takes no real parameters beyond the trailing async
|
|
// pair, so its emitted signature is empty parens.
|
|
#expect(source.contains("public func fooAsync() async throws"))
|
|
#expect(!source.contains("callback:"))
|
|
#expect(!source.contains("userData:"))
|
|
}
|
|
}
|