1
0
Fork 0
gobject-generator/Tests/SwiftGtkGenCoreTests/FunctionGenerationTests.swift
Brendan Szymanski ea41629a1f Tier 3 compile-gate compliance: Pango, GdkPixbuf, Graphene
Root causes fixed, all in TypeMapper.swift / TypeRegistry.swift /
Planner.swift / PlanRenderer.swift / BindingPlan.swift / IRModel.swift /
XMLParser.swift:

- Global (cross-module) inherited-member post-pass in planModules
  replaces the old in-module-only version, dropping properties/methods
  redeclared by an ancestor in a DIFFERENT module (e.g.
  Pango.Coverage.ref()/unref() vs. GObject.Object)
- transfer-ownership="none" GObject-class returns now route through
  init(retaining:) instead of adopting a borrowed pointer as owned
- Non-GObject fundamental root classes (GParamSpec) capture their GIR
  glib:ref-func/glib:unref-func instead of hardcoding g_object_ref
- Interface conformance clauses annotate each interface with @MainActor
  to satisfy isolated-conformance checking across module boundaries
- Scalar marshalOut (.direct/.numericCast) returned through a pointer
  C type with no <array> length now skips instead of misreading a raw
  buffer pointer as its pointee value
- Property/method Swift-name collisions on the same class are resolved
  by dropping the property (e.g. Pango.FontFamily.isVariable)
- MultiPackageAnalyzer only walks includes for configured packages
- No unconditional Foundation import (collided with Gio.InputStream)
- gdk_pixbuf_non_anim_new added to knownMissingCFunctions (GIR-declared,
  not exported through the public umbrella header)

Smoke tests: smoke/tier3/PixbufSmoke.swift (Pixbuf construction +
dimension round-trip against real libgdk_pixbuf) and
smoke/tier3/PangoSmoke.swift (findBaseDir against real libpango,
Latin vs. Hebrew script direction).

208 unit tests pass (+14: InheritedMemberTests + E2 sections in
PropertyGenerationTests/RendererCallableTests/others). compile-gate.sh
1, 2, and 3 all PASS. smoke-test.sh 3 passes 21/21 (18 tier-1 base +
1 GdkPixbuf + 2 Pango).
2026-07-20 01:06:38 -04:00

186 lines
8 KiB
Swift

// FunctionGenerationTests.swift
// Covers Phase C1: namespace-level function planning and rendering the
// C-type bridging that lets primitive, enum, bitfield, and const-string
// callables generate compilable Swift. These lock in the boundary decisions
// (numericCast for width-ambiguous integers, withCString for input strings,
// and the skips for varargs / mutable buffers / string vectors) so a
// regression surfaces here rather than as a mysterious drop in gate coverage.
import Testing
@testable import SwiftGtkGenCore
@Suite("Function generation")
struct FunctionGenerationTests {
/// A registry-backed context in the GLib module. No named types are needed:
/// these tests exercise primitives, strings, and bitfields only.
func makeContext() -> MapContext {
let glib = Repository(namespaces: [
Namespace(
name: "GLib", version: "2.0",
bitfields: [
Bitfield(name: "LogLevelFlags", cType: "GLogLevelFlags",
getTypeFunction: "g_log_level_flags_get_type")
]
)
])
let registry = TypeRegistry(repositories: ["GLib": glib])
return MapContext(registry: registry, currentModule: "GLib", currentNamespace: "GLib")
}
/// Renders a single planned callable to Swift source via the public module
/// renderer, returning the merged `Functions.swift` body it lands in.
func render(_ plan: CallablePlan) -> String {
let module = ModulePlan(
module: "GLib",
types: [.callable(plan)],
skips: [],
coverage: CoverageStats()
)
let files = renderModule(module)
return files["Functions.swift"] ?? ""
}
// MARK: - Plannable callables
@Test("Width-ambiguous integer parameters bridge through numericCast")
func integerParameterBridging() throws {
let fn = GlobalFunction(
name: "free_sized", cIdentifier: "g_free_sized",
parameters: [
Parameter(name: "mem", type: .pointer, cType: "gpointer"),
Parameter(name: "size", type: .size, cType: "gsize"),
]
)
guard case .success(let plan) = planFunction(fn, context: makeContext()) else {
Issue.record("expected g_free_sized to plan successfully"); return
}
#expect(plan.parameters.count == 2)
#expect(plan.parameters[1].mapping.marshalIn == .numericCast(targetType: "UInt"))
let body = render(plan)
#expect(body.contains("g_free_sized(mem, numericCast(size))"))
}
@Test("Const string parameters are bridged with withCString")
func stringParameterBridging() throws {
let fn = GlobalFunction(
name: "set_application_name", cIdentifier: "g_set_application_name",
parameters: [
Parameter(name: "application_name", type: .string, cType: "const char*")
]
)
guard case .success(let plan) = planFunction(fn, context: makeContext()) else {
Issue.record("expected const-string function to plan successfully"); return
}
#expect(plan.parameters[0].mapping.marshalIn == .stringToC)
let body = render(plan)
#expect(body.contains("func setApplicationName(applicationName: String)"))
#expect(body.contains("applicationName.withCString { cString0 in"))
#expect(body.contains("g_set_application_name(cString0)"))
}
@Test("Bitfield parameters and returns round-trip through the C flags type")
func bitfieldRoundTrip() throws {
let fn = GlobalFunction(
name: "log_set_always_fatal", cIdentifier: "g_log_set_always_fatal",
parameters: [
Parameter(name: "fatal_mask", type: .typeRef("LogLevelFlags"), cType: "GLogLevelFlags")
],
returnValue: ReturnValue(type: .typeRef("LogLevelFlags"))
)
guard case .success(let plan) = planFunction(fn, context: makeContext()) else {
Issue.record("expected bitfield function to plan successfully"); return
}
let body = render(plan)
#expect(body.contains("GLogLevelFlags(rawValue: numericCast(fatalMask.rawValue))"))
#expect(body.contains("LogLevelFlags(rawValue: numericCast("))
}
// MARK: - Skips
@Test("Variadic functions are skipped with the varargs reason")
func variadicSkip() throws {
let fn = GlobalFunction(
name: "build_filename", cIdentifier: "g_build_filename",
parameters: [
Parameter(name: "first_element", type: .string, cType: "const char*"),
Parameter(name: "...", type: .void),
]
)
guard case .skip(let entry) = planFunction(fn, context: makeContext()) else {
Issue.record("expected variadic function to be skipped"); return
}
#expect(entry.reason == .varargs)
}
@Test("Mutable string buffers and string vectors are skipped")
func mutableAndVectorStringSkips() throws {
let mutableBuffer = GlobalFunction(
name: "utf8_strncpy", cIdentifier: "g_utf8_strncpy",
parameters: [Parameter(name: "dest", type: .string, cType: "char*")]
)
guard case .skip = planFunction(mutableBuffer, context: makeContext()) else {
Issue.record("expected mutable string buffer to be skipped"); return
}
let stringVector = GlobalFunction(
name: "cmp_strv", cIdentifier: "g_cmp_strv",
parameters: [Parameter(name: "arg1", type: .string, cType: "const char* const*")]
)
guard case .skip = planFunction(stringVector, context: makeContext()) else {
Issue.record("expected string vector to be skipped"); return
}
}
@Test("A scalar return type whose c:type is a pointer with no array length is skipped")
func pointerToScalarReturnSkip() throws {
// Mirrors gdk_pixbuf_read_pixels: GIR names the return `guint8` but
// c:type is `const guint8*` a raw buffer pointer, not a scalar.
let fn = GlobalFunction(
name: "read_pixels", cIdentifier: "gdk_pixbuf_read_pixels",
parameters: [],
returnValue: ReturnValue(type: .uint8, cType: "const guint8*")
)
guard case .skip(let entry) = planFunction(fn, context: makeContext()) else {
Issue.record("expected pointer-to-scalar return to be skipped"); return
}
#expect(entry.reason == .unknownType)
}
@Test("Unexported C symbols never reach a plan")
func missingSymbolSkip() throws {
// g_open is a `#define g_open open` macro on non-Windows: not a real
// symbol. The planner must skip it before rendering an uncompilable call.
var skips: [SkipEntry] = []
var types: [TypePlan] = []
let fn = GlobalFunction(name: "open", cIdentifier: "g_open")
let bound = planOrSkipFunctionForTest(&skips, &types, fn)
#expect(bound == 0)
#expect(types.isEmpty)
#expect(skips.count == 1)
}
/// Test shim over the private `planOrSkipFunction`, exercised through the
/// public planner so the missing-symbol gate is covered end to end.
private func planOrSkipFunctionForTest(
_ skips: inout [SkipEntry], _ types: inout [TypePlan], _ fn: GlobalFunction
) -> Int {
let ns = Namespace(name: "GLib", version: "2.0", functions: [fn])
let repo = Repository(namespaces: [ns])
let analysis = MultiPackageAnalysis(
repositories: ["GLib": repo],
directDependencies: ["GLib": []],
transitiveDependencies: ["GLib": []],
implicitImports: ["GLib": []],
packageConfigs: [:]
)
let registry = TypeRegistry(repositories: ["GLib": repo])
let plans = planModules(analysis: analysis, registry: registry)
let module = plans["GLib"]
skips = module?.skips ?? []
types = module?.types ?? []
return module?.coverage.boundCallables ?? 0
}
}