test(core): Swift oracle — the filius-ipad core built on Linux as the reference
tools/swift-oracle builds the unchanged Swift runtime (pinned 3b5ded0) in Docker and replays JSON scenarios; the TypeScript engine must produce the same trace events, results and end state. Five scenarios, 884 events, identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
.upstream/
|
||||
.build/
|
||||
Sources/FiliusCore/
|
||||
Package.resolved
|
||||
@@ -0,0 +1,23 @@
|
||||
// swift-tools-version:5.9
|
||||
// The filius-ipad core (runtime, model, persistence, reports) built on Linux as
|
||||
// a reference for the TypeScript port. Sources/FiliusCore is filled by
|
||||
// fetch-upstream.sh from a pinned commit; nothing of it is checked in here.
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FiliusOracle",
|
||||
dependencies: [.package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0")],
|
||||
targets: [
|
||||
// Apple-only modules the core imports, reduced to what it uses.
|
||||
.target(name: "CoreGraphics"),
|
||||
.target(name: "CryptoKit", dependencies: [.product(name: "Crypto", package: "swift-crypto")]),
|
||||
.systemLibrary(name: "CZlib"),
|
||||
.target(name: "Compression", dependencies: ["CZlib"]),
|
||||
.target(
|
||||
name: "FiliusCore",
|
||||
dependencies: ["CoreGraphics", "CryptoKit", "Compression"],
|
||||
swiftSettings: [.unsafeFlags(["-enable-testing"])]
|
||||
),
|
||||
.executableTarget(name: "Oracle", dependencies: ["FiliusCore"]),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
# Swift-Orakel
|
||||
|
||||
Der Swift-Kern von `filius-ipad` (Laufzeit, Modell, `.fls`, Berichte — alles
|
||||
ohne Oberfläche) lässt sich unter Linux bauen. Dieses Werkzeug nutzt das als
|
||||
**Maßstab für die Übertragung**: Ein Szenario wird einmal als JSON
|
||||
beschrieben, in Swift abgespielt, und die TypeScript-Übertragung muss
|
||||
dieselbe Ausgabe liefern — jedes einzelne Spurereignis, jede Antwort, den
|
||||
Endzustand.
|
||||
|
||||
```bash
|
||||
npm run oracle # = tools/swift-oracle/run.sh (braucht Docker)
|
||||
npm test # vergleicht mit den geschriebenen *.swift.json
|
||||
```
|
||||
|
||||
* Szenarien: `packages/core/test/scenarios/<name>.scenario.json`
|
||||
* Ergebnis aus Swift: `<name>.swift.json` daneben — **wird eingecheckt**, damit
|
||||
die Tests ohne Docker und ohne Swift laufen.
|
||||
* Vergleich: `packages/core/test/oracle.test.ts`. Er meldet das **erste**
|
||||
abweichende Spurereignis mit Nummer.
|
||||
|
||||
## Wie es gebaut wird
|
||||
|
||||
`fetch-upstream.sh` holt `filius-ipad` am festen Stand `3b5ded0` und kopiert
|
||||
die oberflächenfreien Quellen nach `Sources/FiliusCore/` (nicht eingecheckt).
|
||||
Apple-Module, die es unter Linux nicht gibt, sind auf das Nötige verkleinert:
|
||||
|
||||
| Modul | Ersatz |
|
||||
| --- | --- |
|
||||
| `CoreGraphics` | `Foundation` (hat `CGPoint`/`CGFloat` unter Linux) |
|
||||
| `CryptoKit` | `swift-crypto` (gleiche Schnittstelle) |
|
||||
| `Compression` | eine Funktion, `compression_decode_buffer` mit rohem DEFLATE über zlib |
|
||||
| `Network` | entfällt: `TopologyRemoteLinkLANController.swift` wird nicht mitgebaut |
|
||||
|
||||
`patches/linux-type-checker.patch` teilt zwei lange Ausdrücke auf, an denen der
|
||||
Linux-Compiler die Typprüfung abbricht. Das Verhalten ändert sich dadurch nicht.
|
||||
|
||||
## Neue Schrittarten
|
||||
|
||||
Eine Schrittart gibt es zweimal — in `Sources/Oracle/main.swift` und in
|
||||
`packages/core/test/oracle.test.ts`. Beide müssen dasselbe JSON schreiben.
|
||||
@@ -0,0 +1,5 @@
|
||||
module CZlib [system] {
|
||||
header "shim.h"
|
||||
link "z"
|
||||
export *
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include <zlib.h>
|
||||
@@ -0,0 +1,23 @@
|
||||
// Linux stand-in for the one call filius-ipad makes into Apple's Compression
|
||||
// framework: COMPRESSION_ZLIB is raw DEFLATE (RFC 1951), decoded here by zlib.
|
||||
import CZlib
|
||||
|
||||
public struct compression_algorithm: Equatable { let rawValue: Int32 }
|
||||
public let COMPRESSION_ZLIB = compression_algorithm(rawValue: 0x205)
|
||||
|
||||
public func compression_decode_buffer(
|
||||
_ dst: UnsafeMutablePointer<UInt8>, _ dstSize: Int,
|
||||
_ src: UnsafePointer<UInt8>, _ srcSize: Int,
|
||||
_ scratch: UnsafeMutableRawPointer?, _ algorithm: compression_algorithm
|
||||
) -> Int {
|
||||
var stream = z_stream()
|
||||
guard inflateInit2_(&stream, -15, ZLIB_VERSION, Int32(MemoryLayout<z_stream>.size)) == Z_OK else { return 0 }
|
||||
defer { inflateEnd(&stream) }
|
||||
stream.next_in = UnsafeMutablePointer(mutating: src)
|
||||
stream.avail_in = uInt(srcSize)
|
||||
stream.next_out = dst
|
||||
stream.avail_out = uInt(dstSize)
|
||||
let status = inflate(&stream, Z_FINISH)
|
||||
guard status == Z_STREAM_END || status == Z_OK || status == Z_BUF_ERROR else { return 0 }
|
||||
return Int(stream.total_out)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
@_exported import Foundation
|
||||
@@ -0,0 +1 @@
|
||||
@_exported import Crypto
|
||||
@@ -0,0 +1,273 @@
|
||||
// Replays scenario files in the filius-ipad runtime engine and writes what
|
||||
// happened: every trace event, the result of every step, and the end state.
|
||||
// The TypeScript port replays the same files (packages/core/test/oracle.test.ts)
|
||||
// and must produce the same output.
|
||||
//
|
||||
// Usage: Oracle <directory> — reads <name>.scenario.json, writes <name>.swift.json
|
||||
|
||||
@testable import FiliusCore
|
||||
import Foundation
|
||||
|
||||
// MARK: - Scenario format (shared with packages/core/test/oracle/scenario.ts)
|
||||
|
||||
struct ScenarioPort: Decodable { let id: String; let label: String; let mac: String? }
|
||||
struct ScenarioNode: Decodable { let id: String; let kind: String; let ports: [ScenarioPort] }
|
||||
struct ScenarioLink: Decodable {
|
||||
let id: String
|
||||
let sourceNode: String; let sourcePort: String
|
||||
let targetNode: String; let targetPort: String
|
||||
}
|
||||
struct ScenarioDevice: Decodable { let ip: String; let mask: String; let gateway: String?; let dns: String? }
|
||||
struct ScenarioInterface: Decodable { let node: String; let port: String; let ip: String; let mask: String }
|
||||
struct ScenarioRoute: Decodable { let destination: String; let mask: String; let gateway: String; let interface: String }
|
||||
struct ScenarioSwitch: Decodable { let ssid: String; let retention: UInt64? }
|
||||
struct ScenarioFirewallRule: Decodable {
|
||||
let source: String?; let sourceMask: String?
|
||||
let destination: String?; let destinationMask: String?
|
||||
let port: Int?; let `protocol`: String?; let action: String?
|
||||
}
|
||||
struct ScenarioFirewall: Decodable {
|
||||
let active: Bool?; let defaultPolicy: String?; let dropIcmp: Bool?
|
||||
let synOnly: Bool?; let filterUdp: Bool?; let rules: [ScenarioFirewallRule]?
|
||||
}
|
||||
struct ScenarioStep: Decodable {
|
||||
let op: String
|
||||
let from: String?; let node: String?; let target: String?
|
||||
let ttl: UInt8?; let identifier: UInt16?; let sequence: UInt16?; let maxTtl: UInt8?
|
||||
let to: UInt64?; let enabled: Bool?; let percent: Int?
|
||||
}
|
||||
struct Scenario: Decodable {
|
||||
let seed: UInt64
|
||||
let speedPercent: Int?
|
||||
let nodes: [ScenarioNode]
|
||||
let links: [ScenarioLink]
|
||||
let devices: [String: ScenarioDevice]?
|
||||
let interfaces: [ScenarioInterface]?
|
||||
let routes: [String: [ScenarioRoute]]?
|
||||
let switches: [String: ScenarioSwitch]?
|
||||
let firewalls: [String: ScenarioFirewall]?
|
||||
let steps: [ScenarioStep]
|
||||
}
|
||||
|
||||
// MARK: - Building the snapshot
|
||||
|
||||
func uuid(_ value: String) -> UUID {
|
||||
guard let id = UUID(uuidString: value) else { fatalError("not a UUID: \(value)") }
|
||||
return id
|
||||
}
|
||||
|
||||
func firewallAction(_ value: String?) -> TopologyFirewallAction {
|
||||
value == "accept" ? .accept : .drop
|
||||
}
|
||||
|
||||
func firewallProtocol(_ value: String?) -> TopologyFirewallProtocol {
|
||||
switch value {
|
||||
case "all": return .all
|
||||
case "icmp": return .icmp
|
||||
case "udp": return .udp
|
||||
default: return .tcp
|
||||
}
|
||||
}
|
||||
|
||||
func snapshot(of scenario: Scenario) -> TopologyNetworkRuntimeTopologySnapshot {
|
||||
let nodes = scenario.nodes.map { node -> TopologyNetworkRuntimeNodeSnapshot in
|
||||
guard let kind = TopologyNodeKind(rawValue: node.kind) else { fatalError("unknown kind \(node.kind)") }
|
||||
return TopologyNetworkRuntimeNodeSnapshot(
|
||||
id: uuid(node.id),
|
||||
kind: kind,
|
||||
ports: node.ports.map { TopologyNetworkRuntimePortSnapshot(id: uuid($0.id), label: $0.label, macAddress: $0.mac) }
|
||||
)
|
||||
}
|
||||
let links = scenario.links.map {
|
||||
TopologyNetworkRuntimeLinkSnapshot(
|
||||
id: uuid($0.id),
|
||||
sourceNodeID: uuid($0.sourceNode), sourcePortID: uuid($0.sourcePort),
|
||||
targetNodeID: uuid($0.targetNode), targetPortID: uuid($0.targetPort)
|
||||
)
|
||||
}
|
||||
var devices: [UUID: TopologyRuntimeDeviceConfiguration] = [:]
|
||||
for (node, device) in scenario.devices ?? [:] {
|
||||
devices[uuid(node)] = TopologyRuntimeDeviceConfiguration(
|
||||
ipAddress: device.ip, subnetMask: device.mask,
|
||||
defaultGateway: device.gateway ?? "", dnsServer: device.dns ?? ""
|
||||
)
|
||||
}
|
||||
var interfaces: [TopologyRuntimeInterfaceKey: TopologyRuntimeInterfaceConfiguration] = [:]
|
||||
for item in scenario.interfaces ?? [] {
|
||||
interfaces[TopologyRuntimeInterfaceKey(nodeID: uuid(item.node), portID: uuid(item.port))] =
|
||||
TopologyRuntimeInterfaceConfiguration(ipAddress: item.ip, subnetMask: item.mask)
|
||||
}
|
||||
var routes: [UUID: [TopologyRuntimeManualRoute]] = [:]
|
||||
for (node, rows) in scenario.routes ?? [:] {
|
||||
routes[uuid(node)] = rows.map {
|
||||
TopologyRuntimeManualRoute(
|
||||
destinationNetwork: $0.destination, subnetMask: $0.mask,
|
||||
gateway: $0.gateway, interfaceIPAddress: $0.interface
|
||||
)
|
||||
}
|
||||
}
|
||||
var switches: [UUID: TopologySwitchConfiguration] = [:]
|
||||
for (node, config) in scenario.switches ?? [:] {
|
||||
switches[uuid(node)] = TopologySwitchConfiguration(
|
||||
ssid: config.ssid,
|
||||
retentionTimeMilliseconds: config.retention ?? TopologySwitchConfiguration.defaultRetentionTimeMilliseconds
|
||||
)
|
||||
}
|
||||
var firewalls: [UUID: TopologyFirewallConfiguration] = [:]
|
||||
for (node, config) in scenario.firewalls ?? [:] {
|
||||
firewalls[uuid(node)] = TopologyFirewallConfiguration(
|
||||
isActive: config.active ?? false,
|
||||
defaultPolicy: firewallAction(config.defaultPolicy),
|
||||
dropICMP: config.dropIcmp ?? false,
|
||||
filterSYNSegmentsOnly: config.synOnly ?? true,
|
||||
filterUDP: config.filterUdp ?? true,
|
||||
rules: (config.rules ?? []).map {
|
||||
TopologyFirewallRule(
|
||||
sourceIPAddress: $0.source ?? "", sourceSubnetMask: $0.sourceMask ?? "",
|
||||
destinationIPAddress: $0.destination ?? "", destinationSubnetMask: $0.destinationMask ?? "",
|
||||
port: $0.port ?? TopologyFirewallRule.allPorts,
|
||||
protocolType: firewallProtocol($0.protocol),
|
||||
action: firewallAction($0.action)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
return TopologyNetworkRuntimeTopologySnapshot(
|
||||
nodes: nodes, links: links,
|
||||
deviceConfigurations: devices, interfaceConfigurations: interfaces,
|
||||
manualRoutesByNodeID: routes,
|
||||
firewallConfigurationsByNodeID: firewalls,
|
||||
switchConfigurationsByNodeID: switches
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Output
|
||||
|
||||
typealias JSON = [String: Any]
|
||||
|
||||
func headers(_ fields: [TopologyPacketHeaderField]) -> [[String]] {
|
||||
fields.map { [$0.name, $0.value] }
|
||||
}
|
||||
|
||||
func trace(_ event: TopologyPacketTraceEvent) -> JSON {
|
||||
var result: JSON = [
|
||||
"id": event.id,
|
||||
"time": event.timeMilliseconds,
|
||||
"node": event.nodeID.uuidString,
|
||||
"direction": event.direction.rawValue,
|
||||
"layer": event.layer.rawValue,
|
||||
"operation": event.operation.rawValue,
|
||||
"before": headers(event.beforeHeaders),
|
||||
"after": headers(event.afterHeaders),
|
||||
]
|
||||
if let frame = event.frameIdentity { result["frame"] = frame }
|
||||
if let packet = event.packetIdentity { result["packet"] = packet }
|
||||
if let interface = event.interfaceID { result["interface"] = interface.uuidString }
|
||||
if let detail = event.detail { result["detail"] = detail }
|
||||
return result
|
||||
}
|
||||
|
||||
func delivery(_ result: TopologyIPv4DeliveryResult) -> JSON {
|
||||
switch result {
|
||||
case let .delivered(packet, node): return ["type": "delivered", "packet": packet, "node": node.uuidString]
|
||||
case let .icmpError(packet, node, kind):
|
||||
return ["type": "icmpError", "packet": packet, "node": node.uuidString, "kind": kind.rawValue]
|
||||
case let .dropped(packet, node):
|
||||
var json: JSON = ["type": "dropped", "packet": packet]
|
||||
if let node { json["node"] = node.uuidString }
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
func observation(_ item: TopologyRuntimeICMPObservation) -> JSON {
|
||||
[
|
||||
"kind": item.message.kind.rawValue,
|
||||
"identifier": item.message.identifier,
|
||||
"sequence": item.message.sequenceNumber,
|
||||
"sender": item.packet.senderIPAddress,
|
||||
]
|
||||
}
|
||||
|
||||
func run(_ scenario: Scenario) -> JSON {
|
||||
var engine = TopologyNetworkRuntimeEngine(seed: scenario.seed)
|
||||
if let percent = scenario.speedPercent { engine.setSimulationSpeed(percent: percent) }
|
||||
engine.handle(.start(snapshot: snapshot(of: scenario), seed: scenario.seed, initialTimeMilliseconds: 0))
|
||||
|
||||
var results: [Any] = []
|
||||
for step in scenario.steps {
|
||||
switch step.op {
|
||||
case "ping":
|
||||
results.append(delivery(engine.sendICMPEcho(
|
||||
fromNodeID: uuid(step.from!), targetIPAddress: step.target!,
|
||||
timeToLive: step.ttl ?? 64, identifier: step.identifier ?? 1, sequenceNumber: step.sequence ?? 1
|
||||
)))
|
||||
case "traceroute":
|
||||
results.append(engine.traceICMPEcho(
|
||||
fromNodeID: uuid(step.from!), targetIPAddress: step.target!,
|
||||
maximumTimeToLive: step.maxTtl ?? 64, identifier: step.identifier ?? 1
|
||||
).map(observation))
|
||||
case "resolveMac":
|
||||
results.append(engine.resolveMACAddress(nodeID: uuid(step.node!), targetIPAddress: step.target!) ?? NSNull())
|
||||
case "advance":
|
||||
engine.handle(.advance(toMilliseconds: step.to!))
|
||||
results.append(engine.state.currentTimeMilliseconds)
|
||||
case "packetLoss":
|
||||
engine.setGlobalPacketLossEnabled(step.enabled!)
|
||||
results.append(engine.state.globalPacketLossEnabled)
|
||||
case "sat":
|
||||
results.append(engine.switchSATEntries(nodeID: uuid(step.node!)).map {
|
||||
["mac": $0.macAddress, "port": $0.portLabel, "updated": $0.updatedAtMilliseconds] as JSON
|
||||
})
|
||||
case "speed":
|
||||
engine.setSimulationSpeed(percent: step.percent!)
|
||||
results.append(engine.simulationSpeed.percent)
|
||||
default:
|
||||
fatalError("unknown step \(step.op)")
|
||||
}
|
||||
}
|
||||
|
||||
let state = engine.state
|
||||
var arp: JSON = [:]
|
||||
for (node, cache) in state.arpCachesByNodeID {
|
||||
arp[node.uuidString] = cache.mapValues { $0.macAddress }
|
||||
}
|
||||
var switchTables: JSON = [:]
|
||||
for (node, table) in state.switchForwardingTablesByNodeID {
|
||||
switchTables[node.uuidString] = table.mapValues { $0.uuidString }
|
||||
}
|
||||
var observations: JSON = [:]
|
||||
for (node, items) in state.icmpObservationsByNodeID {
|
||||
observations[node.uuidString] = items.map(observation)
|
||||
}
|
||||
return [
|
||||
"results": results,
|
||||
"traces": state.packetTraces.map(trace),
|
||||
"time": state.currentTimeMilliseconds,
|
||||
"pendingEvents": state.pendingEvents.count,
|
||||
"arp": arp,
|
||||
"switchTables": switchTables,
|
||||
"icmpObservations": observations,
|
||||
"firewallDecisions": state.firewallDecisions.map { decision -> JSON in
|
||||
var json: JSON = [
|
||||
"packet": decision.packetIdentity, "node": decision.nodeID.uuidString, "accepted": decision.accepted,
|
||||
]
|
||||
if let rule = decision.ruleIndex { json["rule"] = rule }
|
||||
return json
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Main
|
||||
|
||||
let directory = URL(fileURLWithPath: CommandLine.arguments.dropFirst().first ?? ".")
|
||||
let files = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)
|
||||
.filter { $0.lastPathComponent.hasSuffix(".scenario.json") }
|
||||
.sorted { $0.lastPathComponent < $1.lastPathComponent }
|
||||
for file in files {
|
||||
let scenario = try JSONDecoder().decode(Scenario.self, from: Data(contentsOf: file))
|
||||
let output = run(scenario)
|
||||
let data = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys])
|
||||
let name = file.lastPathComponent.replacingOccurrences(of: ".scenario.json", with: ".swift.json")
|
||||
try (data + Data("\n".utf8)).write(to: directory.appendingPathComponent(name))
|
||||
print("\(name): \((output["traces"] as? [Any])?.count ?? 0) trace events")
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# Copies the UI-free Swift sources of filius-ipad, at the commit the port
|
||||
# follows, into Sources/FiliusCore and applies the Linux patches.
|
||||
set -eu
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
commit=3b5ded0
|
||||
upstream="$here/.upstream"
|
||||
if [ ! -d "$upstream/.git" ]; then
|
||||
git clone -q https://github.com/filius-project/filius-ipad "$upstream"
|
||||
fi
|
||||
git -C "$upstream" fetch -q origin
|
||||
git -C "$upstream" checkout -q "$commit"
|
||||
|
||||
core="$here/Sources/FiliusCore"
|
||||
rm -rf "$core"
|
||||
mkdir -p "$core"
|
||||
editor="$upstream/ios/FiliusPad/TopologyEditor"
|
||||
cp "$editor"/State/*.swift "$editor"/State/Reports/*.swift "$editor"/Model/*.swift \
|
||||
"$editor"/Persistence/*.swift "$upstream/ios/FiliusPad/Localization/FiliusLocalization.swift" "$core/"
|
||||
# Apple's Network framework has no Linux counterpart; the LAN transport of the
|
||||
# remote link is not needed to replay a simulation.
|
||||
rm "$core/TopologyRemoteLinkLANController.swift"
|
||||
for patch in "$here"/patches/*.patch; do
|
||||
(cd "$core" && patch -p1 --forward --quiet < "$patch")
|
||||
done
|
||||
echo "filius-ipad $commit → $core ($(ls "$core" | wc -l) files)"
|
||||
@@ -0,0 +1,69 @@
|
||||
diff --git a/TopologyPacketCaptureTextExport.swift b/TopologyPacketCaptureTextExport.swift
|
||||
index fcdb43a..c9ed472 100644
|
||||
--- a/TopologyPacketCaptureTextExport.swift
|
||||
+++ b/TopologyPacketCaptureTextExport.swift
|
||||
@@ -245,26 +245,26 @@ enum TopologyPacketCaptureTextExportFormatter {
|
||||
}
|
||||
|
||||
private static func renderRecord(_ record: TopologyPacketCaptureExportRecord) -> String {
|
||||
- [
|
||||
- String(record.number),
|
||||
- String(record.timeMilliseconds),
|
||||
- String(record.traceID),
|
||||
- record.frameID.map(String.init) ?? "",
|
||||
- record.packetID.map(String.init) ?? "",
|
||||
- normalizedUUID(record.nodeID),
|
||||
- record.nodeName,
|
||||
- record.interfaceID.map(normalizedUUID) ?? "",
|
||||
- record.interfaceName ?? "",
|
||||
- record.direction,
|
||||
- record.layer,
|
||||
- record.operation,
|
||||
- record.source,
|
||||
- record.destination,
|
||||
- record.protocolName,
|
||||
- record.beforeHeaders,
|
||||
- record.afterHeaders,
|
||||
- record.detail,
|
||||
- ].map(escapeTSVField).joined(separator: "\t")
|
||||
+ var fields: [String] = []
|
||||
+ fields.append(String(record.number))
|
||||
+ fields.append(String(record.timeMilliseconds))
|
||||
+ fields.append(String(record.traceID))
|
||||
+ fields.append(record.frameID.map { String($0) } ?? "")
|
||||
+ fields.append(record.packetID.map { String($0) } ?? "")
|
||||
+ fields.append(normalizedUUID(record.nodeID))
|
||||
+ fields.append(record.nodeName)
|
||||
+ fields.append(record.interfaceID.map { normalizedUUID($0) } ?? "")
|
||||
+ fields.append(record.interfaceName ?? "")
|
||||
+ fields.append(record.direction)
|
||||
+ fields.append(record.layer)
|
||||
+ fields.append(record.operation)
|
||||
+ fields.append(record.source)
|
||||
+ fields.append(record.destination)
|
||||
+ fields.append(record.protocolName)
|
||||
+ fields.append(record.beforeHeaders)
|
||||
+ fields.append(record.afterHeaders)
|
||||
+ fields.append(record.detail)
|
||||
+ return fields.map(escapeTSVField).joined(separator: "\t")
|
||||
}
|
||||
|
||||
private static func traceIsOrderedBefore(
|
||||
diff --git a/TopologyRuntimeSimpleClientEcho.swift b/TopologyRuntimeSimpleClientEcho.swift
|
||||
index e72694f..5b9baf3 100644
|
||||
--- a/TopologyRuntimeSimpleClientEcho.swift
|
||||
+++ b/TopologyRuntimeSimpleClientEcho.swift
|
||||
@@ -252,8 +252,11 @@ struct TopologyRuntimeWebBrowserHistoryEntry: Equatable {
|
||||
}
|
||||
|
||||
fileprivate var retainedByteCount: Int {
|
||||
- address.utf8.count + resolvedIPAddress.utf8.count + (contentType?.utf8.count ?? 0)
|
||||
- + body.utf8.count + bodyData.count + (errorMessage?.utf8.count ?? 0)
|
||||
+ let head: Int = address.utf8.count + resolvedIPAddress.utf8.count
|
||||
+ let type: Int = contentType?.utf8.count ?? 0
|
||||
+ let error: Int = errorMessage?.utf8.count ?? 0
|
||||
+ let tail: Int = body.utf8.count + bodyData.count
|
||||
+ return head + type + tail + error
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Replays every scenario under packages/core/test/scenarios in the Swift core
|
||||
# and writes the reference result next to it (<name>.swift.json).
|
||||
# Needs Docker; the TypeScript tests only read the written files.
|
||||
set -eu
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
root="$(cd "$here/../.." && pwd)"
|
||||
[ -d "$here/Sources/FiliusCore" ] || "$here/fetch-upstream.sh"
|
||||
docker run --rm -v "$root:/work" -w /work/tools/swift-oracle swift:6.1-jammy \
|
||||
sh -c 'swift build -q 2>&1 | grep -v "Sendable\|^ *|\|^ *\`" ; .build/debug/Oracle /work/packages/core/test/scenarios'
|
||||
Reference in New Issue
Block a user