import Foundation import TerebiConfiguration @main struct ConfigurationChecks { struct Failure: Error, CustomStringConvertible { let description: String } static func require(_ condition: Bool, _ message: String) throws { if !condition { throw Failure(description: "FAIL: " + message) } } static func requireLoadError(at url: URL, _ message: String) throws { do { _ = try PlayerConfiguration.load(from: url) } catch let caught { let diagnostic = caught.localizedDescription + " " + String(describing: caught) try require(diagnostic.contains(url.path), message + " identifies the actual config path") return } throw Failure(description: "FAIL: " + message + " was accepted") } static func main() throws { let files = FileManager.default let root = files.temporaryDirectory.appendingPathComponent("terebi.configuration-check.\(UUID().uuidString)") try files.createDirectory(at: root, withIntermediateDirectories: true) defer { try? files.removeItem(at: root) } let home = root.appendingPathComponent("home") let xdg = root.appendingPathComponent("xdg") let fallback = home.appendingPathComponent(".config/terebi/config.toml") let config = xdg.appendingPathComponent("terebi/config.toml") let defaults = PlayerConfiguration() try require(!defaults.showBufferInfo && !defaults.automaticQuality && defaults.snapToEdges, "built-in defaults hide buffer info, disable automatic quality, and enable snapping") try require(PlayerConfiguration.fileURL(environment: ["XDG_CONFIG_HOME": xdg.path], homeDirectory: home) == config, "absolute XDG_CONFIG_HOME selects terebi/config.toml") for environment in [[:], ["XDG_CONFIG_HOME": ""], ["XDG_CONFIG_HOME": "relative/config"]] { try require(PlayerConfiguration.fileURL(environment: environment, homeDirectory: home) == fallback, "unset, empty, and relative XDG_CONFIG_HOME fall back to ~/.config") } try require(try PlayerConfiguration.load(from: fallback) == defaults, "missing files use built-in defaults") try require(try PlayerConfiguration.loadForApplication(arguments: [], environment: [:], homeDirectory: home) == defaults, "application startup uses defaults when its resolved config is missing") try require(!files.fileExists(atPath: home.path), "missing-file loading creates neither home, config directory, nor file") try files.createDirectory(at: config.deletingLastPathComponent(), withIntermediateDirectories: true) let original = Data(""" # Root keys can be quoted, with comments and whitespace. "show_buffer_info" = true # session display default 'automatic_quality' = true snap_to_edges = false """.utf8) try original.write(to: config) let configured = PlayerConfiguration(showBufferInfo: true, automaticQuality: true, snapToEdges: false) let environment = ["XDG_CONFIG_HOME": xdg.path] try require(try PlayerConfiguration.load(from: config) == configured, "TOML comments, whitespace, quoted root keys, and both boolean values load") var session = try PlayerConfiguration.loadForApplication(arguments: [], environment: environment, homeDirectory: home) try require(session == configured, "application startup automatically loads the XDG config") session.showBufferInfo = false session.automaticQuality = false session.snapToEdges = true try require(session == defaults, "controls can override all settings within the session") try require(try Data(contentsOf: config) == original, "loading and session overrides do not change config bytes") try require(try PlayerConfiguration.load(from: config) == configured, "a fresh load resets session overrides to file values") let partial = root.appendingPathComponent("partial.toml") try Data("automatic_quality = true\n".utf8).write(to: partial) try require(try PlayerConfiguration.load(from: partial) == PlayerConfiguration(automaticQuality: true), "partial configuration retains defaults for omitted settings") let empty = root.appendingPathComponent("empty.toml") try Data("# Defaults only\n\n".utf8).write(to: empty) try require(try PlayerConfiguration.load(from: empty) == defaults, "empty/comment-only TOML uses defaults") try require(try PlayerConfiguration.loadForApplication(arguments: ["--config-file", partial.absoluteString], environment: environment, homeDirectory: home) == PlayerConfiguration(automaticQuality: true), "explicit file URL overrides the resolved XDG config") let invalid: [(String, String)] = [ ("syntax", "show_buffer_info = [\n"), ("duplicate", "show_buffer_info = true\n\"show_buffer_info\" = false\n"), ("unknown", "unknown_setting = true\n"), ("string", "show_buffer_info = \"true\"\n"), ("integer", "automatic_quality = 1\n"), ("array", "snap_to_edges = [true]\n"), ("table", "[show_buffer_info]\nenabled = true\n") ] for (name, text) in invalid { let url = root.appendingPathComponent(name + ".toml") let bytes = Data(text.utf8) try bytes.write(to: url) try requireLoadError(at: url, "invalid \(name) configuration") try require(try Data(contentsOf: url) == bytes, "rejected \(name) config remains unchanged") } let invalidUTF8 = root.appendingPathComponent("invalid-utf8.toml") let invalidBytes = Data([0xff, 0xfe, 0x80]) try invalidBytes.write(to: invalidUTF8) try requireLoadError(at: invalidUTF8, "invalid UTF-8 configuration") try require(try Data(contentsOf: invalidUTF8) == invalidBytes, "invalid UTF-8 config remains unchanged") let directory = root.appendingPathComponent("directory.toml") try files.createDirectory(at: directory, withIntermediateDirectories: false) try requireLoadError(at: directory, "directory instead of config file") let malformed = root.appendingPathComponent("syntax.toml") let inaccessible = partial.appendingPathComponent("config.toml") // A regular file cannot be traversed as a directory. for url in [malformed, inaccessible, directory] { try require(try PlayerConfiguration.loadForApplication(arguments: ["--config-file", url.absoluteString, "--no-config"], environment: environment, homeDirectory: home) == defaults, "--no-config bypasses malformed, inaccessible, and directory config paths") } let badXDG = root.appendingPathComponent("bad-xdg") let badConfig = badXDG.appendingPathComponent("terebi/config.toml") try files.createDirectory(at: badConfig.deletingLastPathComponent(), withIntermediateDirectories: true) let malformedBytes = Data("snap_to_edges =\n".utf8) try malformedBytes.write(to: badConfig) try require(try PlayerConfiguration.loadForApplication(arguments: ["--no-config"], environment: ["XDG_CONFIG_HOME": badXDG.path], homeDirectory: home) == defaults, "--no-config also bypasses automatic XDG loading") try require(try Data(contentsOf: badConfig) == malformedBytes, "bypassing config never repairs or rewrites it") try require(!files.fileExists(atPath: home.path), "all checks leave the isolated fallback home uncreated") print("Configuration checks passed") } }