mirror of
https://github.com/kfattic/cupsadmin.git
synced 2026-11-13 19:06:43 -05:00
Flag queues whose driver filters need Rosetta or are missing
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
103
Tests/CupsKitTests/DriverFilterTests.swift
Normal file
103
Tests/CupsKitTests/DriverFilterTests.swift
Normal file
@@ -0,0 +1,103 @@
|
||||
@testable import CupsKit
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
private func le32(_ v: UInt32) -> [UInt8] { [UInt8(v & 0xFF), UInt8(v >> 8 & 0xFF), UInt8(v >> 16 & 0xFF), UInt8(v >> 24)] }
|
||||
private func be32(_ v: UInt32) -> [UInt8] { [UInt8(v >> 24), UInt8(v >> 16 & 0xFF), UInt8(v >> 8 & 0xFF), UInt8(v & 0xFF)] }
|
||||
private let x86_64: UInt32 = 0x0100_0007, arm64: UInt32 = 0x0100_000C
|
||||
|
||||
/// Thin 64-bit Mach-O header (little-endian).
|
||||
private func thin(_ cpu: UInt32, subtype: UInt32 = 0) -> [UInt8] {
|
||||
var bytes: [UInt8] = le32(0xFEEDFACF)
|
||||
bytes += le32(cpu)
|
||||
bytes += le32(subtype)
|
||||
bytes += [UInt8](repeating: 0, count: 20)
|
||||
return bytes
|
||||
}
|
||||
|
||||
/// Universal (fat32) header with the given slices.
|
||||
private func fat(_ slices: [(UInt32, UInt32)]) -> [UInt8] {
|
||||
var bytes: [UInt8] = be32(0xCAFEBABE)
|
||||
bytes += be32(UInt32(slices.count))
|
||||
for (cpu, subtype) in slices {
|
||||
bytes += be32(cpu)
|
||||
bytes += be32(subtype)
|
||||
bytes += be32(0x4000) // offset
|
||||
bytes += be32(0x1000) // size
|
||||
bytes += be32(14) // align
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
@Suite struct MachOArchitectures {
|
||||
@Test func thinAndUniversalBinaries() {
|
||||
#expect(DriverFilterCheck.status(header: thin(arm64)) == .native(architectures: ["arm64"]))
|
||||
#expect(DriverFilterCheck.status(header: thin(x86_64)) == .intelOnly(architectures: ["x86_64"]))
|
||||
#expect(DriverFilterCheck.status(header: fat([(x86_64, 3), (arm64, 2)])) == .native(architectures: ["x86_64", "arm64e"]))
|
||||
#expect(DriverFilterCheck.status(header: fat([(x86_64, 3), (7, 3)])) == .intelOnly(architectures: ["x86_64", "i386"]))
|
||||
// A newer arm64 subtype lipo can't name (seen on macOS 27's rastertohp) still counts as arm64.
|
||||
#expect(DriverFilterCheck.status(header: fat([(x86_64, 3), (arm64, 12)])) == .native(architectures: ["x86_64", "arm64"]))
|
||||
}
|
||||
|
||||
@Test func scriptsAndOtherFiles() {
|
||||
#expect(DriverFilterCheck.status(header: Array("#!/bin/sh\nexec foo\n".utf8)) == .script(interpreter: "/bin/sh"))
|
||||
#expect(DriverFilterCheck.status(header: Array("%PDF-1.4 not a filter".utf8)) == .unrecognized)
|
||||
#expect(DriverFilterCheck.status(header: [0xCA, 0xFE, 0xBA, 0xBE, 0, 0, 0, 52]) == .unrecognized) // Java class file
|
||||
}
|
||||
|
||||
@Test(.enabled(if: FileManager.default.fileExists(atPath: "/usr/libexec/cups/filter/rastertohp")))
|
||||
func appleFilterIsNative() {
|
||||
guard case .native = DriverFilterCheck.status(atPath: "/usr/libexec/cups/filter/rastertohp") else {
|
||||
Issue.record("rastertohp should have an arm64 slice"); return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suite struct FilterLines {
|
||||
@Test func resolvesAndClassifiesEveryFilter() throws {
|
||||
let dir = FileManager.default.temporaryDirectory.appendingPathComponent("cupsadmin-filters-\(UUID().uuidString)")
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
defer { try? FileManager.default.removeItem(at: dir) }
|
||||
try Data(thin(arm64)).write(to: dir.appendingPathComponent("nativefilter"))
|
||||
let intel = dir.appendingPathComponent("Vendor Filters/pstopsvendor")
|
||||
try FileManager.default.createDirectory(at: intel.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try Data(fat([(x86_64, 3)])).write(to: intel)
|
||||
|
||||
let ppd = PPD(text: """
|
||||
*PPD-Adobe: "4.3"
|
||||
*cupsFilter: "application/vnd.cups-raster 50 nativefilter"
|
||||
*cupsFilter: "application/vnd.cups-postscript 0 \(intel.path)"
|
||||
*cupsFilter2: "application/vnd.cups-pdf application/pdf 0 maxsize(0) -"
|
||||
*cupsFilter: "application/vnd.cups-command 0 /Library/Printers/Nobody/Filters/gone"
|
||||
*cupsFilter: "application/vnd.cups-postscript 0 \(intel.path)"
|
||||
""")
|
||||
#expect(ppd.filterLines.count == 5)
|
||||
|
||||
let report = DriverFilterCheck.check(ppd, filterDirectory: dir.path)
|
||||
#expect(report.filters.count == 4) // duplicate pstopsvendor line counted once
|
||||
#expect(report.filters[0].status == .native(architectures: ["arm64"]))
|
||||
#expect(report.filters[0].path == dir.appendingPathComponent("nativefilter").path)
|
||||
#expect(report.filters[1].status == .intelOnly(architectures: ["x86_64"]))
|
||||
#expect(report.filters[2].status == .passthrough)
|
||||
#expect(report.filters[3].status == .missing)
|
||||
|
||||
#expect(report.needsRosetta && report.hasMissingFilter)
|
||||
#expect(report.headline == "Driver filter missing") // missing outranks Rosetta
|
||||
#expect(report.detail == "/Library/Printers/Nobody/Filters/gone not found; pstopsvendor is Intel-only (x86_64)")
|
||||
}
|
||||
|
||||
@Test func rosettaOnlyAndClean() {
|
||||
let passthrough = PPD(text: "*cupsFilter2: \"application/vnd.cups-pdf application/pdf 0 maxsize(0) -\"\n*cupsFilter: \"image/urf 10 -\"\n")
|
||||
let clean = DriverFilterCheck.check(passthrough)
|
||||
#expect(!clean.isFlagged && clean.headline == nil && clean.warning == nil)
|
||||
|
||||
let none = DriverFilterCheck.check(PPD(text: "*NickName: \"No filters\"\n"))
|
||||
#expect(none.filters.isEmpty && !none.isFlagged)
|
||||
}
|
||||
|
||||
@Test(.enabled(if: hasVendorPPD("RICOH IM C4500")))
|
||||
func ricohVendorPPDNamesItsFilters() throws {
|
||||
let report = DriverFilterCheck.check(try #require(vendorPPD("RICOH IM C4500")))
|
||||
#expect(report.filters.map(\.name) == ["commandfilterRV1", "pstopsRV2"] || report.filters.map(\.name) == ["pstopsRV2", "commandfilterRV1"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user