1
0
Fork 0
gobject-generator/Tests/GObjectGeneratorCoreTests/InheritedMemberTests.swift

133 lines
6.6 KiB
Swift

// InheritedMemberTests.swift
// Covers Phase E2's global (cross-module) inherited-member post-pass in
// `planModules`: a class in one Swift module that redeclares a same-name,
// same-arity method already provided by an ancestor class in a DIFFERENT
// module (e.g. `Pango.Coverage.ref()`/`unref()` vs. `GObject.Object.ref()`/
// `unref()`) must have its own copy dropped Swift rejects the redundant
// redeclaration across module boundaries. A same-name method with a
// DIFFERENT arity (legitimate overload, e.g. `MemoryOutputStream.getData()`
// vs. inherited `Object.getData(key:)`) must NOT be dropped.
import Foundation
import Testing
@testable import GObjectGeneratorCore
@Suite("Cross-module inherited-member dedup")
struct InheritedMemberTests {
/// Builds a two-module `MultiPackageAnalysis` + `TypeRegistry`: module A
/// declares root class `Object` with `ref()` (no args) and `getData(key:)`
/// (one arg); module B declares `Coverage: Object` (redeclaring `ref()`
/// with the SAME arity) and `Stream: Object` (redeclaring `getData()`
/// with a DIFFERENT arity a legitimate overload).
private func planTwoModules() throws -> [String: ModulePlan] {
let tmpDir = NSTemporaryDirectory() + "inherited_member_test_\(UUID().uuidString)"
try FileManager.default.createDirectory(atPath: tmpDir, withIntermediateDirectories: true)
let moduleAGIR = """
<?xml version="1.0"?>
<repository version="1.2"
xmlns="http://www.gtk.org/introspection/core/1.0"
xmlns:c="http://www.gtk.org/introspection/c/1.0"
xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
<c:include name="gobject.h"/>
<package name="gobject-2.0"/>
<namespace name="AMod" version="1.0"
shared-library="libamod.so.0" c:identifier-prefixes="A">
<class name="Object" c:type="AObject" parent="" glib:type-name="AObject"
glib:get-type="a_object_get_type">
<method name="ref" c:identifier="a_object_ref">
<return-value transfer-ownership="none"><type name="none" c:type="void"/></return-value>
</method>
<method name="get_data" c:identifier="a_object_get_data">
<return-value transfer-ownership="none"><type name="gpointer" c:type="gpointer"/></return-value>
<parameters>
<instance-parameter name="self" transfer-ownership="none">
<type name="Object" c:type="AObject*"/>
</instance-parameter>
<parameter name="key" transfer-ownership="none">
<type name="utf8" c:type="const char*"/>
</parameter>
</parameters>
</method>
</class>
</namespace>
</repository>
"""
let moduleAPath = tmpDir + "/AMod-1.0.gir"
try moduleAGIR.write(toFile: moduleAPath, atomically: true, encoding: .utf8)
let moduleBGIR = """
<?xml version="1.0"?>
<repository version="1.2"
xmlns="http://www.gtk.org/introspection/core/1.0"
xmlns:c="http://www.gtk.org/introspection/c/1.0"
xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
<include name="AMod" version="1.0"/>
<c:include name="bmod.h"/>
<package name="bmod-1.0"/>
<namespace name="BMod" version="1.0"
shared-library="libbmod.so.0" c:identifier-prefixes="B">
<class name="Coverage" c:type="BCoverage" parent="AMod.Object"
glib:type-name="BCoverage" glib:get-type="b_coverage_get_type">
<method name="ref" c:identifier="b_coverage_ref">
<return-value transfer-ownership="none"><type name="none" c:type="void"/></return-value>
</method>
</class>
<class name="Stream" c:type="BStream" parent="AMod.Object"
glib:type-name="BStream" glib:get-type="b_stream_get_type">
<method name="get_data" c:identifier="b_stream_get_data">
<return-value transfer-ownership="none"><type name="gpointer" c:type="gpointer"/></return-value>
<parameters>
<instance-parameter name="self" transfer-ownership="none">
<type name="Stream" c:type="BStream*"/>
</instance-parameter>
</parameters>
</method>
</class>
</namespace>
</repository>
"""
let moduleBPath = tmpDir + "/BMod-1.0.gir"
try moduleBGIR.write(toFile: moduleBPath, atomically: true, encoding: .utf8)
let packages: [PackageEntry] = [
PackageEntry(name: "AMod", girPath: moduleAPath),
PackageEntry(name: "BMod", girPath: moduleBPath),
]
let config = MonorepoConfig(outputDir: tmpDir, packages: packages)
let analysis = try MultiPackageAnalyzer(config: config).analyze()
let registry = TypeRegistry(repositories: analysis.repositories)
return planModules(analysis: analysis, registry: registry)
}
@Test("A same-arity method redeclared across a cross-module inheritance edge is dropped")
func crossModuleSameArityCollisionIsDropped() throws {
let plans = try planTwoModules()
guard let bPlan = plans["BMod"],
case .class(let coverage)? = bPlan.types.first(where: {
if case .class(let c) = $0 { return c.name == "Coverage" }
return false
}) else {
Issue.record("expected BMod.Coverage to plan"); return
}
#expect(!coverage.methods.contains { $0.name == "ref" })
#expect(bPlan.skips.contains { $0.reason == .inheritedMember && $0.symbol.contains("Coverage.ref") })
}
@Test("A different-arity method with the same base name across modules is kept (legitimate overload)")
func crossModuleDifferentArityOverloadIsKept() throws {
let plans = try planTwoModules()
guard let bPlan = plans["BMod"],
case .class(let stream)? = bPlan.types.first(where: {
if case .class(let c) = $0 { return c.name == "Stream" }
return false
}) else {
Issue.record("expected BMod.Stream to plan"); return
}
// Stream.getData() (0 args) must survive despite Object.getData(key:)
// (1 arg) sharing the base name — different arity, not a collision.
#expect(stream.methods.contains { $0.name == "getData" })
#expect(!bPlan.skips.contains { $0.reason == .inheritedMember && $0.symbol.contains("Stream.getData") })
}
}