mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 20:14:50 +00:00
105 lines
4.9 KiB
Swift
105 lines
4.9 KiB
Swift
import Foundation
|
|
import Testing
|
|
import os
|
|
|
|
@testable import ShadowsocksX_NG2
|
|
|
|
/// 统一日志行模型:渲染/解析往返、旧格式降级与级别口径。
|
|
struct LogLineTests {
|
|
@Test func rendersAndParsesUniformRows() {
|
|
let stamp = Date(timeIntervalSince1970: 1_758_000_000)
|
|
for level in [LogLineLevel.error, .warning, .info] {
|
|
for source in [LogLineSource.gui, .wrapper, .sslocal] {
|
|
let row = LogLine(
|
|
timestamp: stamp, level: level, source: source, text: "body [with] brackets")
|
|
let rendered = row.rendered
|
|
#expect(rendered.hasPrefix("\(LogLine.timestampFormatter.string(from: stamp)) "))
|
|
#expect(
|
|
rendered.hasSuffix(" [\(level.rawValue)] [\(source.rawValue)] body [with] brackets"))
|
|
#expect(LogLine.parse(rendered) == row, "渲染/解析往返必须无损:\(rendered)")
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test func parseRejectsUnstructuredText() {
|
|
#expect(LogLine.parse("") == nil)
|
|
#expect(LogLine.parse("plain line without metadata") == nil)
|
|
#expect(LogLine.parse("2026-10-20 12:00:00.123 [bogus] [gui] text") == nil)
|
|
#expect(LogLine.parse("2026-10-20 12:00:00.123 [info] [nowhere] text") == nil)
|
|
// 时间戳位置必须是可解析的固定格式(上游 ISO 时间是原文,不是行元数据)。
|
|
#expect(LogLine.parse("2026-10-20T03:04:12.123456Z [info] [gui] text") == nil)
|
|
}
|
|
|
|
@Test func offsetTimestampsPreserveRepeatedLocalTimes() throws {
|
|
let first = try #require(
|
|
LogLine.parse("2026-11-01 01:30:00.000 -0400 [info] [wrapper] first"))
|
|
let second = try #require(
|
|
LogLine.parse("2026-11-01 01:30:00.000 -0500 [info] [wrapper] second"))
|
|
let firstTime = try #require(first.timestamp)
|
|
let secondTime = try #require(second.timestamp)
|
|
#expect(secondTime.timeIntervalSince(firstTime) == 3600)
|
|
#expect(LogLine.parse(first.rendered) == first)
|
|
#expect(LogLine.parse(second.rendered) == second)
|
|
#expect(
|
|
first.rendered.range(of: " [+-][0-9]{4} \\[info\\]", options: .regularExpression) != nil)
|
|
}
|
|
|
|
@Test func readsUniformRowsWithoutTimezoneFromEarlierVersions() throws {
|
|
let row = try #require(
|
|
LogLine.parse("2026-10-20 12:00:00.123 [error] [wrapper] older event"))
|
|
#expect(row.timestamp != nil)
|
|
#expect(row.level == .error)
|
|
#expect(row.source == .wrapper)
|
|
#expect(row.text == "older event")
|
|
}
|
|
|
|
@Test func legacyRowsKeepRawTextAndGuessSource() {
|
|
let event = LogLine.legacy("ssxng: listen not established within deadline: a 127.0.0.1:1")
|
|
#expect(event.timestamp == nil)
|
|
#expect(event.source == .wrapper)
|
|
#expect(event.text == "ssxng: listen not established within deadline: a 127.0.0.1:1")
|
|
#expect(event.rendered == event.text, "旧格式行保留原文(raw capture 不改写)")
|
|
|
|
let upstream = LogLine.legacy("2026-10-20T03:04:12Z ERROR sslocal] boom")
|
|
#expect(upstream.source == .sslocal)
|
|
#expect(upstream.level == .error)
|
|
|
|
let warningText = "[Warning] [107248047] app/dispatcher: default route"
|
|
let warning = LogLine.legacy(warningText)
|
|
#expect(warning.level == .warning)
|
|
#expect(warning.text == warningText)
|
|
#expect(warning.rendered == warningText)
|
|
}
|
|
|
|
@Test func detectsUpstreamLevelTokens() {
|
|
#expect(LogLineLevel.detect(in: "2026-10-20T03:04:12Z ERROR sslocal] boom") == .error)
|
|
#expect(LogLineLevel.detect(in: "[2026-10-20T03:04:12Z WARN sslocal] retrying") == .warning)
|
|
#expect(LogLineLevel.detect(in: "WARNING token alone") == .warning)
|
|
#expect(LogLineLevel.detect(in: "2026-10-20T03:04:12Z error sslocal] boom") == .error)
|
|
#expect(LogLineLevel.detect(in: "2026-10-20T03:04:12Z ErRoR sslocal] boom") == .error)
|
|
#expect(
|
|
LogLineLevel.detect(in: "[Warning] [107248047] app/dispatcher: default route") == .warning)
|
|
#expect(LogLineLevel.detect(in: "[wArN] retrying") == .warning)
|
|
#expect(LogLineLevel.detect(in: "[Warning] later ERROR in message") == .warning)
|
|
#expect(LogLineLevel.detect(in: "connected to server") == .info)
|
|
#expect(LogLineLevel.detect(in: "an error occurred") == .error)
|
|
#expect(LogLineLevel.detect(in: "ERROR: boom") == .info)
|
|
#expect(LogLineLevel.detect(in: "password: [redacted]") == .info)
|
|
// CRLF 行尾不得吞掉级别 token(\r 也是空白)。
|
|
#expect(LogLineLevel.detect(in: "2026-10-20T03:04:12Z ERROR boom\r") == .error)
|
|
#expect(LogLineLevel.detect(in: "2026-10-20T03:04:12Z WARN retry\r") == .warning)
|
|
}
|
|
|
|
@Test func projectsLevelsToUnifiedLogScale() {
|
|
// 统一日志没有 warning 级:WARN/WARNING 落持久的 .default,与 error 可区分。
|
|
#expect(OSLogType(LogLineLevel.error) == .error)
|
|
#expect(OSLogType(LogLineLevel.warning) == .default)
|
|
#expect(OSLogType(LogLineLevel.info) == .info)
|
|
#expect(LogLineLevel(os: .error) == .error)
|
|
#expect(LogLineLevel(os: .fault) == .error)
|
|
#expect(LogLineLevel(os: .default) == .info)
|
|
#expect(LogLineLevel(os: .info) == .info)
|
|
#expect(LogLineLevel(os: .debug) == .info)
|
|
}
|
|
}
|