Files
SwiftDBAI/Tests/SwiftDBAITests/BinarySizeTests.swift
Krishna Kumar fcd752466a SwiftDBAI: natural language queries for any SQLite database
Drop-in SwiftUI chat view, headless ChatEngine, LLM-agnostic via
AnyLanguageModel. Read-only by default with configurable allowlists.
Robust SQL parser with 63 tests. Includes demo app with GitHub stars dataset.
2026-04-05 17:11:12 -05:00

255 lines
10 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// BinarySizeTests.swift
// SwiftDBAI
//
// Validates that the SwiftDBAI package stays within its 2 MB binary size budget.
// This test suite uses source-level heuristics since we can't measure the actual
// compiled binary size in a unit test. The constraints ensure the package remains
// lightweight by checking:
// 1. Total source code size (proxy for compiled size)
// 2. No embedded binary assets or large resources
// 3. No unnecessary heavy dependencies
// 4. File count stays reasonable (no code bloat)
import Foundation
import Testing
@Suite("Binary Size Budget")
struct BinarySizeTests {
/// The maximum allowed total source code size in bytes.
/// At typical Swift optimized compilation ratios (2-4x), 500 KB of source
/// compiles to roughly 1-2 MB of binary. We set the source budget at 500 KB
/// to keep the compiled output well under 2 MB.
private static let maxSourceSizeBytes: Int = 500_000 // 500 KB
/// Maximum number of Swift source files allowed.
/// More files generally means more code and larger binaries.
private static let maxSourceFileCount: Int = 60
/// Maximum size for any single source file in bytes.
/// Large individual files often indicate code that should be split or
/// contains embedded data that bloats the binary.
private static let maxSingleFileSizeBytes: Int = 50_000 // 50 KB
/// Disallowed file extensions in the Sources directory that would bloat the binary.
private static let disallowedExtensions: Set<String> = [
"png", "jpg", "jpeg", "gif", "bmp", "tiff",
"mp3", "mp4", "wav", "mov",
"mlmodel", "mlmodelc", "mlpackage",
"sqlite", "db",
"zip", "tar", "gz",
"bin", "dat",
"framework", "dylib", "a"
]
// MARK: - Helper
/// Recursively finds all files in the Sources/SwiftDBAI directory.
private func findSourceFiles() throws -> [URL] {
let sourcesDir = findSourcesDirectory()
guard let sourcesDir else {
Issue.record("Could not locate Sources/SwiftDBAI directory")
return []
}
let fileManager = FileManager.default
guard let enumerator = fileManager.enumerator(
at: sourcesDir,
includingPropertiesForKeys: [.fileSizeKey, .isRegularFileKey],
options: [.skipsHiddenFiles]
) else {
Issue.record("Could not enumerate Sources/SwiftDBAI directory")
return []
}
var files: [URL] = []
for case let fileURL as URL in enumerator {
let resourceValues = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
if resourceValues.isRegularFile == true {
files.append(fileURL)
}
}
return files
}
/// Locates the Sources/SwiftDBAI directory by walking up from the test bundle.
private func findSourcesDirectory() -> URL? {
// Try common locations relative to the build directory
let fileManager = FileManager.default
// In SPM test runs, we can find the package root by checking known paths
var candidateURL = URL(fileURLWithPath: #filePath)
// Walk up from Tests/SwiftDBAITests/BinarySizeTests.swift to package root
for _ in 0..<3 {
candidateURL = candidateURL.deletingLastPathComponent()
}
let sourcesDir = candidateURL.appendingPathComponent("Sources/SwiftDBAI")
if fileManager.fileExists(atPath: sourcesDir.path) {
return sourcesDir
}
// Fallback: check current working directory
let cwdSources = URL(fileURLWithPath: fileManager.currentDirectoryPath)
.appendingPathComponent("Sources/SwiftDBAI")
if fileManager.fileExists(atPath: cwdSources.path) {
return cwdSources
}
return nil
}
// MARK: - Tests
@Test("Total source code size stays under 500 KB budget")
func totalSourceCodeSizeUnderBudget() throws {
let files = try findSourceFiles()
let swiftFiles = files.filter { $0.pathExtension == "swift" }
var totalSize: Int = 0
for file in swiftFiles {
let attributes = try FileManager.default.attributesOfItem(atPath: file.path)
let fileSize = attributes[.size] as? Int ?? 0
totalSize += fileSize
}
#expect(totalSize < Self.maxSourceSizeBytes,
"""
Total Swift source size (\(totalSize) bytes) exceeds \(Self.maxSourceSizeBytes) byte budget.
At typical 2-4x compilation ratio, this would produce a binary larger than 2 MB.
Consider removing unused code or splitting into optional sub-targets.
""")
// Log the actual size for visibility
let sizeKB = Double(totalSize) / 1024.0
let budgetKB = Double(Self.maxSourceSizeBytes) / 1024.0
print("📦 SwiftDBAI source size: \(String(format: "%.1f", sizeKB)) KB / \(String(format: "%.0f", budgetKB)) KB budget (\(String(format: "%.0f", (sizeKB / budgetKB) * 100))% used)")
}
@Test("Source file count stays reasonable")
func sourceFileCountUnderLimit() throws {
let files = try findSourceFiles()
let swiftFiles = files.filter { $0.pathExtension == "swift" }
#expect(swiftFiles.count <= Self.maxSourceFileCount,
"""
Swift source file count (\(swiftFiles.count)) exceeds limit of \(Self.maxSourceFileCount).
More files generally means more code and larger binaries.
""")
print("📦 SwiftDBAI file count: \(swiftFiles.count) / \(Self.maxSourceFileCount) max")
}
@Test("No individual source file exceeds 50 KB")
func noOversizedSourceFiles() throws {
let files = try findSourceFiles()
let swiftFiles = files.filter { $0.pathExtension == "swift" }
for file in swiftFiles {
let attributes = try FileManager.default.attributesOfItem(atPath: file.path)
let fileSize = attributes[.size] as? Int ?? 0
#expect(fileSize < Self.maxSingleFileSizeBytes,
"""
File \(file.lastPathComponent) is \(fileSize) bytes, exceeding the \(Self.maxSingleFileSizeBytes) byte limit.
Large files may contain embedded data or code that should be split.
""")
}
}
@Test("No binary assets or heavy resources in Sources directory")
func noBinaryAssetsInSources() throws {
let files = try findSourceFiles()
let disallowedFiles = files.filter { file in
Self.disallowedExtensions.contains(file.pathExtension.lowercased())
}
#expect(disallowedFiles.isEmpty,
"""
Found \(disallowedFiles.count) disallowed file(s) in Sources directory:
\(disallowedFiles.map(\.lastPathComponent).joined(separator: "\n"))
These file types bloat the binary. Remove them or move to a separate resource bundle.
""")
}
@Test("Package has no resource bundles that could bloat binary")
func noResourceBundles() throws {
let files = try findSourceFiles()
let resourceFiles = files.filter { file in
let ext = file.pathExtension.lowercased()
return ["xcassets", "storyboard", "xib", "nib", "xcdatamodeld"].contains(ext)
}
#expect(resourceFiles.isEmpty,
"""
Found resource bundle files that could bloat the binary:
\(resourceFiles.map(\.lastPathComponent).joined(separator: "\n"))
SwiftDBAI should be pure code — no bundled resources.
""")
}
@Test("Only expected dependencies declared (GRDB + AnyLanguageModel)")
func minimalDependencies() throws {
// Read Package.swift to verify we only have the expected dependencies
var packageURL = URL(fileURLWithPath: #filePath)
for _ in 0..<3 {
packageURL = packageURL.deletingLastPathComponent()
}
let packageSwiftURL = packageURL.appendingPathComponent("Package.swift")
guard FileManager.default.fileExists(atPath: packageSwiftURL.path) else {
// Skip if we can't find Package.swift (CI environments etc.)
return
}
let packageContents = try String(contentsOf: packageSwiftURL, encoding: .utf8)
// Count .package() declarations (dependencies)
let packageDeclarations = packageContents.components(separatedBy: ".package(")
.count - 1 // subtract 1 because the first segment is before any .package(
#expect(packageDeclarations <= 3,
"""
Found \(packageDeclarations) package dependencies, expected at most 4 (GRDB + AnyLanguageModel + ViewInspector for tests).
Additional dependencies increase binary size. Evaluate if they're truly needed.
""")
// Verify the expected dependencies are present
#expect(packageContents.contains("GRDB"), "Expected GRDB dependency")
#expect(packageContents.contains("AnyLanguageModel"), "Expected AnyLanguageModel dependency")
print("📦 SwiftDBAI dependencies: \(packageDeclarations) (GRDB + AnyLanguageModel)")
}
@Test("Estimated binary size under 2 MB")
func estimatedBinarySizeUnderLimit() throws {
let files = try findSourceFiles()
let swiftFiles = files.filter { $0.pathExtension == "swift" }
var totalSize: Int = 0
for file in swiftFiles {
let attributes = try FileManager.default.attributesOfItem(atPath: file.path)
let fileSize = attributes[.size] as? Int ?? 0
totalSize += fileSize
}
// Conservative estimate: optimized Swift binary is typically 2-4x source size.
// Use 4x as worst case multiplier for safety margin.
let worstCaseMultiplier = 4.0
let estimatedBinarySize = Double(totalSize) * worstCaseMultiplier
let maxBinarySize: Double = 2.0 * 1024.0 * 1024.0 // 2 MB
#expect(estimatedBinarySize < maxBinarySize,
"""
Estimated binary size (\(String(format: "%.1f", estimatedBinarySize / 1024.0)) KB) exceeds 2 MB limit.
Source: \(totalSize) bytes × \(worstCaseMultiplier)x multiplier = \(String(format: "%.1f", estimatedBinarySize / 1024.0)) KB
Note: This is the SwiftDBAI module only — excludes GRDB and AnyLanguageModel
which are existing dependencies the developer already includes.
""")
let estimatedMB = estimatedBinarySize / (1024.0 * 1024.0)
print("📦 Estimated SwiftDBAI binary size: \(String(format: "%.2f", estimatedMB)) MB / 2.00 MB limit (worst case \(worstCaseMultiplier)x)")
}
}