Commit c35e9255d3
Verified · cmc
Layout: unified · split
Tests/OrgCoreTests/RoundTripTests.swift added +75
| @@ -0,0 +1,75 @@ | ||
| 1 | import Foundation | |
| 2 | import Testing | |
| 3 | @testable import OrgCore | |
| 4 | ||
| 5 | /// SplitMix64, so failures reproduce from the seed. | |
| 6 | struct SeededGenerator: RandomNumberGenerator { | |
| 7 | var state: UInt64 | |
| 8 | mutating func next() -> UInt64 { | |
| 9 | state &+= 0x9E37_79B9_7F4A_7C15 | |
| 10 | var z = state | |
| 11 | z = (z ^ (z >> 30)) &* 0xBF58_476D_1CE4_E5B9 | |
| 12 | z = (z ^ (z >> 27)) &* 0x94D0_49BB_1331_11EB | |
| 13 | return z ^ (z >> 31) | |
| 14 | } | |
| 15 | } | |
| 16 | ||
| 17 | let fragments = [ | |
| 18 | "* ", "** TODO [#A] title :a:b:", "*** DONE", "#+TODO: NEXT | DONE", "#+begin_src sh", "#+end_src", | |
| 19 | "#+BEGIN_QUOTE", "#+end_quote", "#+BEGIN: clocktable", "#+END:", ":PROPERTIES:", ":ID: x", ":END:", | |
| 20 | ":LOGBOOK:", "CLOCK: [2026-10-04 Sun 10:00]", "SCHEDULED: <2026-10-04 Sun>", "- item", " - nested", | |
| 21 | "\t+ tab", "1. one", "| a | b |", "|---+---|", "#+TBLFM: $2=$1", "# comment", ": fixed", "-----", | |
| 22 | "[fn:1] note", "#+NAME: x", "plain text", "é", "😀", "e\u{301}", " ", "\t", "\n", "\n", "\r\n", "\r", "", | |
| 23 | ] | |
| 24 | ||
| 25 | func randomDocument(_ rng: inout SeededGenerator) -> String { | |
| 26 | (0..<Int.random(in: 0...40, using: &rng)).map { _ in | |
| 27 | fragments.randomElement(using: &rng)! + (Bool.random(using: &rng) ? "\n" : "") | |
| 28 | }.joined() | |
| 29 | } | |
| 30 | ||
| 31 | func checkLengths(_ node: SyntaxNode) -> Bool { | |
| 32 | let sum = node.green.children.reduce(0) { $0 + $1.length } | |
| 33 | return sum == node.green.length && node.children.allSatisfy(checkLengths) | |
| 34 | } | |
| 35 | ||
| 36 | struct RoundTripTests { | |
| 37 | @Test func fuzzedDocumentsRoundTrip() { | |
| 38 | var rng = SeededGenerator(state: 20261004) | |
| 39 | for n in 0..<2_000 { | |
| 40 | let text = randomDocument(&rng) | |
| 41 | let tree = OrgParser.parse(text) | |
| 42 | #expect(tree.text == text, "document \(n)") | |
| 43 | #expect(checkLengths(tree.root), "document \(n)") | |
| 44 | } | |
| 45 | } | |
| 46 | ||
| 47 | @Test(arguments: [ | |
| 48 | [0xEF, 0xBB, 0xBF] + Array("* a\r\n".utf8), | |
| 49 | Array("* a\r\n- b\n\tc".utf8), | |
| 50 | Array("* a".utf8), | |
| 51 | Array("😀 e\u{301}\n".utf8), | |
| 52 | [0x2A, 0x20, 0xFF, 0x0A] as [UInt8], | |
| 53 | ]) | |
| 54 | func encodingFixturesRoundTrip(bytes: [UInt8]) { | |
| 55 | let source = SourceText(bytes: bytes) | |
| 56 | let tree = OrgParser.parse(source.text) | |
| 57 | #expect(source.encode(tree.text) == bytes) | |
| 58 | } | |
| 59 | ||
| 60 | /// Private corpus: `ORGSTAR_CORPUS=/path/to/org swift test --filter corpus`. | |
| 61 | @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_CORPUS"] != nil)) | |
| 62 | func corpusRoundTrips() throws { | |
| 63 | let root = URL(fileURLWithPath: ProcessInfo.processInfo.environment["ORGSTAR_CORPUS"]!) | |
| 64 | let files = FileManager.default.enumerator(at: root, includingPropertiesForKeys: nil)! | |
| 65 | .compactMap { $0 as? URL } | |
| 66 | .filter { ["org", "org_archive"].contains($0.pathExtension) } | |
| 67 | #expect(!files.isEmpty) | |
| 68 | for file in files { | |
| 69 | let bytes = try [UInt8](Data(contentsOf: file)) | |
| 70 | let source = SourceText(bytes: bytes) | |
| 71 | let tree = OrgParser.parse(source.text) | |
| 72 | #expect(source.encode(tree.text) == bytes, "\(file.path)") | |
| 73 | } | |
| 74 | } | |
| 75 | } | |