85 lines
3.7 KiB
Swift
85 lines
3.7 KiB
Swift
// InterfaceGenerationTests.swift
|
|
// Covers interface method planning and rendering under the extension-default
|
|
// model (Phase E1): an interface method's body is rendered as a `public`
|
|
// protocol-extension DEFAULT implementation (`extension Ping { public func … }`)
|
|
// calling the C symbol via `self.pointer`, since interface protocols have no
|
|
// members of their own beyond `pointer`. Unplannable methods (unsupported
|
|
// parameter types) are recorded as skips, mirroring class members. These
|
|
// tests lock in that surface so a regression surfaces here.
|
|
|
|
import Testing
|
|
|
|
@testable import GObjectGeneratorCore
|
|
|
|
@Suite("Interface generation")
|
|
struct InterfaceGenerationTests {
|
|
/// A registry-backed context in the GLib module. The interface under test
|
|
/// needs no named types — its methods use primitives and strings only.
|
|
func makeContext() -> MapContext {
|
|
let glib = Repository(namespaces: [Namespace(name: "GLib", version: "2.0")])
|
|
let registry = TypeRegistry(repositories: ["GLib": glib])
|
|
return MapContext(registry: registry, currentModule: "GLib", currentNamespace: "GLib")
|
|
}
|
|
|
|
/// Renders a single planned interface to Swift source via the public module
|
|
/// renderer, returning the interface's file body.
|
|
func render(_ plan: InterfacePlan) -> String {
|
|
let module = ModulePlan(
|
|
module: "GLib",
|
|
types: [.interface(plan)],
|
|
skips: [],
|
|
coverage: CoverageStats()
|
|
)
|
|
return renderModule(module)["\(plan.name).swift"] ?? ""
|
|
}
|
|
|
|
@Test("Interface instance methods become protocol extension default implementations")
|
|
func methodsBecomeExtensionDefaults() throws {
|
|
let iface = Interface(
|
|
name: "Ping", cType: "GPing",
|
|
methods: [
|
|
Method(
|
|
name: "get_id", cIdentifier: "g_ping_get_id",
|
|
parameters: [Parameter(name: "self", type: .pointer, cType: "GPing*",
|
|
isInstanceParameter: true)],
|
|
returnValue: ReturnValue(type: .long)
|
|
)
|
|
]
|
|
)
|
|
let (plan, skips) = planInterface(iface, context: makeContext())
|
|
#expect(skips.isEmpty)
|
|
#expect(plan.methods.count == 1)
|
|
#expect(plan.methods[0].name == "getId")
|
|
|
|
let source = render(plan)
|
|
// Protocol body is bare — only the `pointer` requirement.
|
|
#expect(source.contains("public protocol Ping {"))
|
|
#expect(source.contains("var pointer: UnsafeMutableRawPointer { get }"))
|
|
// A default implementation — `public`, with a body calling the C symbol.
|
|
#expect(source.contains("extension Ping {"))
|
|
#expect(source.contains("public func getId() -> Int"))
|
|
#expect(source.contains("g_ping_get_id"))
|
|
}
|
|
|
|
@Test("Unplannable interface methods are recorded as skips, not requirements")
|
|
func unplannableMethodsSkip() throws {
|
|
let iface = Interface(
|
|
name: "Ping", cType: "GPing",
|
|
methods: [
|
|
// Variadic methods cannot be bridged — must skip, not appear.
|
|
Method(
|
|
name: "log", cIdentifier: "g_ping_log",
|
|
parameters: [
|
|
Parameter(name: "self", type: .pointer, cType: "GPing*",
|
|
isInstanceParameter: true),
|
|
Parameter(name: "...", type: .vaList, cType: ""),
|
|
]
|
|
)
|
|
]
|
|
)
|
|
let (plan, skips) = planInterface(iface, context: makeContext())
|
|
#expect(plan.methods.isEmpty)
|
|
#expect(skips.count == 1)
|
|
#expect(!render(plan).contains("func log"))
|
|
}
|
|
}
|