Address Task 11 review: shared test support, fix TestSkipError semantics, extract preamble
This commit is contained in:
parent
6ae74045fa
commit
4bdc734f29
3 changed files with 71 additions and 99 deletions
|
|
@ -17,13 +17,17 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
private static let girURL = URL(fileURLWithPath: "/usr/share/gir-1.0/Gtk-4.0.gir")
|
||||
|
||||
private static func loadRepository() throws -> Repository {
|
||||
let parser = GIRParser()
|
||||
return try parser.parse(fileURL: girURL)
|
||||
private struct GeneratedGtk {
|
||||
let repo: Repository
|
||||
let output: String
|
||||
let files: [String: String]
|
||||
let gtk: Namespace
|
||||
}
|
||||
|
||||
private static func generateAll() throws -> (String, [String: String]) {
|
||||
let repo = try loadRepository()
|
||||
private static func setup() throws -> GeneratedGtk {
|
||||
try requireGtkGir()
|
||||
let parser = GIRParser()
|
||||
let repo = try parser.parse(fileURL: girURL)
|
||||
let config = GenerationConfig(
|
||||
library: "Gtk", version: "4.0", girsDirectories: [],
|
||||
targetDirectory: "", externalLibraries: [],
|
||||
|
|
@ -31,29 +35,22 @@ struct GIRSpecAccuracyTests {
|
|||
)
|
||||
let analysis = Analyzer(config: config).analyze(repository: repo)
|
||||
let generator = CodeGenerator(config: config)
|
||||
let singleFile = try generator.generate(repository: repo, analysis: analysis)
|
||||
let output = try generator.generate(repository: repo, analysis: analysis)
|
||||
let files = try generator.generateFiles(repository: repo, analysis: analysis)
|
||||
return (singleFile, files)
|
||||
}
|
||||
|
||||
private static func gtkNamespace(_ repo: Repository) throws -> Namespace {
|
||||
guard let gtk = repo.namespaces.first(where: { $0.name == "Gtk" }) else {
|
||||
throw TestSkipError.noNamespace("Gtk")
|
||||
throw IntegrationTestEnvironmentError.missingNamespace("Gtk")
|
||||
}
|
||||
return gtk
|
||||
return GeneratedGtk(repo: repo, output: output, files: files, gtk: gtk)
|
||||
}
|
||||
|
||||
@Test("Every Gtk class appears in generated output")
|
||||
func testEveryClassGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for cls in gtk.classes {
|
||||
let inOwnFile = Self.containsClassDeclaration(in: files["\(cls.name).swift"] ?? "", name: cls.name)
|
||||
let inAggregate = Self.containsClassDeclaration(in: output, name: cls.name)
|
||||
for cls in g.gtk.classes {
|
||||
let inOwnFile = Self.containsClassDeclaration(in: g.files["\(cls.name).swift"] ?? "", name: cls.name)
|
||||
let inAggregate = Self.containsClassDeclaration(in: g.output, name: cls.name)
|
||||
if !inOwnFile && !inAggregate {
|
||||
missing.append(cls.name)
|
||||
}
|
||||
|
|
@ -63,23 +60,20 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Class parent is encoded in the generated declaration")
|
||||
func testClassParentMatchesGIR() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var wrongParent: [String] = []
|
||||
for cls in gtk.classes {
|
||||
for cls in g.gtk.classes {
|
||||
guard let parent = cls.parent else { continue }
|
||||
// Skip cross-namespace parents — those become typealiases, not inheritance.
|
||||
if parent.contains(".") { continue }
|
||||
|
||||
let classFile = files["\(cls.name).swift"] ?? ""
|
||||
let classFile = g.files["\(cls.name).swift"] ?? ""
|
||||
// Generator emits "class Name: Parent {" (no space before colon).
|
||||
let inOwnFile = Self.containsParentReference(
|
||||
in: classFile, child: cls.name, parent: parent)
|
||||
let inAggregate = Self.containsParentReference(
|
||||
in: output, child: cls.name, parent: parent)
|
||||
in: g.output, child: cls.name, parent: parent)
|
||||
if !inOwnFile && !inAggregate {
|
||||
wrongParent.append("\(cls.name) → expected `\(parent)`")
|
||||
}
|
||||
|
|
@ -124,14 +118,11 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every class method appears in generated output")
|
||||
func testEveryMethodGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for cls in gtk.classes {
|
||||
let classDecl = files["\(cls.name).swift"] ?? output
|
||||
for cls in g.gtk.classes {
|
||||
let classDecl = g.files["\(cls.name).swift"] ?? g.output
|
||||
for method in cls.methods {
|
||||
let swiftName = Self.swiftifyMethodName(method.name)
|
||||
if !classDecl.contains("func \(swiftName)(") {
|
||||
|
|
@ -144,17 +135,14 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every readable/writable class property appears in generated output")
|
||||
func testEveryPropertyGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
// The generator intentionally skips accessors for construct-only
|
||||
// properties, so filter those out — they're verified separately by
|
||||
// the "Every class constructor…" test which checks the init signature.
|
||||
var missing: [String] = []
|
||||
for cls in gtk.classes {
|
||||
let classDecl = files["\(cls.name).swift"] ?? output
|
||||
for cls in g.gtk.classes {
|
||||
let classDecl = g.files["\(cls.name).swift"] ?? g.output
|
||||
for prop in cls.properties where !prop.isConstructOnly {
|
||||
let propName = Self.swiftifyPropertyName(prop.name)
|
||||
if !classDecl.contains("var \(propName):") {
|
||||
|
|
@ -167,14 +155,11 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every class signal has a connect method")
|
||||
func testEverySignalGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for cls in gtk.classes {
|
||||
let classDecl = files["\(cls.name).swift"] ?? output
|
||||
for cls in g.gtk.classes {
|
||||
let classDecl = g.files["\(cls.name).swift"] ?? g.output
|
||||
for signal in cls.signals {
|
||||
let swiftName = Self.swiftifySignalName(signal.name)
|
||||
if !classDecl.contains("connect\(swiftName)(") {
|
||||
|
|
@ -187,14 +172,11 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every class with constructors has a convenience init")
|
||||
func testEveryConstructorGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for cls in gtk.classes where !cls.constructors.isEmpty {
|
||||
let classDecl = files["\(cls.name).swift"] ?? output
|
||||
for cls in g.gtk.classes where !cls.constructors.isEmpty {
|
||||
let classDecl = g.files["\(cls.name).swift"] ?? g.output
|
||||
if !classDecl.contains("public convenience init(") {
|
||||
missing.append(cls.name)
|
||||
}
|
||||
|
|
@ -204,15 +186,12 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every enumeration appears in generated output")
|
||||
func testEveryEnumGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for enm in gtk.enumerations {
|
||||
let inOwnFile = (files["\(enm.name).swift"] ?? "").contains("enum \(enm.name):")
|
||||
let inAggregate = output.contains("public enum \(enm.name):")
|
||||
for enm in g.gtk.enumerations {
|
||||
let inOwnFile = (g.files["\(enm.name).swift"] ?? "").contains("enum \(enm.name):")
|
||||
let inAggregate = g.output.contains("public enum \(enm.name):")
|
||||
if !inOwnFile && !inAggregate {
|
||||
missing.append(enm.name)
|
||||
}
|
||||
|
|
@ -222,15 +201,12 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every bitfield appears in generated output")
|
||||
func testEveryBitfieldGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for bf in gtk.bitfields {
|
||||
let inOwnFile = (files["\(bf.name).swift"] ?? "").contains("struct \(bf.name): OptionSet")
|
||||
let inAggregate = output.contains("struct \(bf.name): OptionSet")
|
||||
for bf in g.gtk.bitfields {
|
||||
let inOwnFile = (g.files["\(bf.name).swift"] ?? "").contains("struct \(bf.name): OptionSet")
|
||||
let inAggregate = g.output.contains("struct \(bf.name): OptionSet")
|
||||
if !inOwnFile && !inAggregate {
|
||||
missing.append(bf.name)
|
||||
}
|
||||
|
|
@ -240,15 +216,12 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every record appears in generated output")
|
||||
func testEveryRecordGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for rec in gtk.records {
|
||||
let inOwnFile = Self.containsRecordDeclaration(in: files["\(rec.name).swift"] ?? "", name: rec.name)
|
||||
let inAggregate = Self.containsRecordDeclaration(in: output, name: rec.name)
|
||||
for rec in g.gtk.records {
|
||||
let inOwnFile = Self.containsRecordDeclaration(in: g.files["\(rec.name).swift"] ?? "", name: rec.name)
|
||||
let inAggregate = Self.containsRecordDeclaration(in: g.output, name: rec.name)
|
||||
if !inOwnFile && !inAggregate {
|
||||
missing.append(rec.name)
|
||||
}
|
||||
|
|
@ -258,15 +231,12 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every interface appears in generated output")
|
||||
func testEveryInterfaceGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (output, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for iface in gtk.interfaces {
|
||||
let inOwnFile = (files["\(iface.name).swift"] ?? "").contains("protocol \(iface.name) ")
|
||||
let inAggregate = output.contains("public protocol \(iface.name) ")
|
||||
for iface in g.gtk.interfaces {
|
||||
let inOwnFile = (g.files["\(iface.name).swift"] ?? "").contains("protocol \(iface.name) ")
|
||||
let inAggregate = g.output.contains("public protocol \(iface.name) ")
|
||||
if !inOwnFile && !inAggregate {
|
||||
missing.append(iface.name)
|
||||
}
|
||||
|
|
@ -276,15 +246,12 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
@Test("Every constant appears in generated output")
|
||||
func testEveryConstantGenerated() throws {
|
||||
try Self.requireGtkGir()
|
||||
let repo = try Self.loadRepository()
|
||||
let (_, files) = try Self.generateAll()
|
||||
let gtk = try Self.gtkNamespace(repo)
|
||||
let g = try Self.setup()
|
||||
|
||||
var missing: [String] = []
|
||||
for cst in gtk.constants {
|
||||
for cst in g.gtk.constants {
|
||||
let swiftName = Self.pascalCaseName(cst.name)
|
||||
if files["\(swiftName).swift"] == nil {
|
||||
if g.files["\(swiftName).swift"] == nil {
|
||||
missing.append(cst.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -293,12 +260,12 @@ struct GIRSpecAccuracyTests {
|
|||
|
||||
// MARK: - Setup
|
||||
|
||||
/// Throws a skip error if the GIR fixture is not present in the system
|
||||
/// GIR directory. Tests are skipped (not failed) so the suite still
|
||||
/// passes in environments without GTK installed.
|
||||
/// Throws an environment error if the GIR fixture is not present in the
|
||||
/// system GIR directory. The test will fail in that case (Swift Testing
|
||||
/// marks thrown errors as failures, not skips).
|
||||
private static func requireGtkGir() throws {
|
||||
guard FileManager.default.fileExists(atPath: girURL.path) else {
|
||||
throw TestSkipError.fileNotFound("Gtk-4.0.gir not found at \(girURL.path)")
|
||||
throw IntegrationTestEnvironmentError.missingGIRFile("Gtk-4.0.gir not found at \(girURL.path)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import Foundation
|
|||
func testFullGtkGeneration() throws {
|
||||
let girURL = URL(fileURLWithPath: "/usr/share/gir-1.0/Gtk-4.0.gir")
|
||||
guard FileManager.default.fileExists(atPath: girURL.path) else {
|
||||
throw TestSkipError.fileNotFound("Gtk-4.0.gir not found")
|
||||
throw IntegrationTestEnvironmentError.missingGIRFile("Gtk-4.0.gir not found")
|
||||
}
|
||||
|
||||
let parser = GIRParser()
|
||||
let repo = try parser.parse(fileURL: girURL)
|
||||
guard let gtk = repo.namespaces.first(where: { $0.name == "Gtk" }) else {
|
||||
throw TestSkipError.noNamespace("Gtk")
|
||||
throw IntegrationTestEnvironmentError.missingNamespace("Gtk")
|
||||
}
|
||||
|
||||
let config = GenerationConfig(
|
||||
|
|
@ -76,14 +76,14 @@ func testFullGtkGeneration() throws {
|
|||
func testFullGLibParsing() throws {
|
||||
let girURL = URL(fileURLWithPath: "/usr/share/gir-1.0/GLib-2.0.gir")
|
||||
guard FileManager.default.fileExists(atPath: girURL.path) else {
|
||||
throw TestSkipError.fileNotFound("GLib-2.0.gir not found")
|
||||
throw IntegrationTestEnvironmentError.missingGIRFile("GLib-2.0.gir not found")
|
||||
}
|
||||
|
||||
let parser = GIRParser()
|
||||
let repo = try parser.parse(fileURL: girURL)
|
||||
#expect(!repo.namespaces.isEmpty)
|
||||
guard let glib = repo.namespaces.first(where: { $0.name == "GLib" }) else {
|
||||
throw TestSkipError.noNamespace("GLib")
|
||||
throw IntegrationTestEnvironmentError.missingNamespace("GLib")
|
||||
}
|
||||
|
||||
// Verify parsing of records, functions, enumerations
|
||||
|
|
@ -128,12 +128,12 @@ func testFullGLibParsing() throws {
|
|||
func testGenerateAllGtkTypes() throws {
|
||||
let girURL = URL(fileURLWithPath: "/usr/share/gir-1.0/Gtk-4.0.gir")
|
||||
guard FileManager.default.fileExists(atPath: girURL.path) else {
|
||||
throw TestSkipError.fileNotFound("Gtk-4.0.gir not found")
|
||||
throw IntegrationTestEnvironmentError.missingGIRFile("Gtk-4.0.gir not found")
|
||||
}
|
||||
let parser = GIRParser()
|
||||
let repo = try parser.parse(fileURL: girURL)
|
||||
guard let gtk = repo.namespaces.first(where: { $0.name == "Gtk" }) else {
|
||||
throw TestSkipError.noNamespace("Gtk")
|
||||
throw IntegrationTestEnvironmentError.missingNamespace("Gtk")
|
||||
}
|
||||
|
||||
// Empty generate list = generate ALL
|
||||
|
|
@ -180,8 +180,3 @@ func testGenerateAllGtkTypes() throws {
|
|||
// Verify interface is generated as a protocol
|
||||
#expect(files["Buildable.swift"]?.contains("public protocol") == true)
|
||||
}
|
||||
|
||||
enum TestSkipError: Error {
|
||||
case fileNotFound(String)
|
||||
case noNamespace(String)
|
||||
}
|
||||
|
|
|
|||
10
Tests/IntegrationTests/Support/IntegrationTestSupport.swift
Normal file
10
Tests/IntegrationTests/Support/IntegrationTestSupport.swift
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import Foundation
|
||||
|
||||
/// Error thrown by integration test helpers when the required environment
|
||||
/// is missing. Swift Testing marks the test as failed when this is thrown.
|
||||
enum IntegrationTestEnvironmentError: Error {
|
||||
/// The required GIR file was not found on disk.
|
||||
case missingGIRFile(String)
|
||||
/// The expected namespace was not present in the parsed GIR.
|
||||
case missingNamespace(String)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue