| 1 | module yaml |
| 2 | |
| 3 | // Conformance-style coverage. Each case mirrors a pattern from the YAML 1.2 |
| 4 | // spec (or the public yaml-test-suite) that's within the subset V's parser |
| 5 | // implements: plain mappings, sequences, scalars, flow style, JSON-superset |
| 6 | // documents. Anchors, aliases, tags, merge keys, multi-document streams and |
| 7 | // the chomp/indent indicators on block scalars are intentionally NOT covered: |
| 8 | // V's parser does not support them today. |
| 9 | // |
| 10 | // Each case is `name | yaml_input | expected_json` and is verified by parsing |
| 11 | // `yaml_input`, serializing to JSON, and comparing against the expected JSON |
| 12 | // via `json_logically_eq` (see `test_helpers.v`). |
| 13 | // |
| 14 | // Scalar variants (booleans, nulls, numeric underscores), quoted-string |
| 15 | // escapes, block scalars, `[]`/`{}`, and the empty document are exercised by |
| 16 | // `yaml_edge_cases_test.v` with stricter, typed assertions; not duplicated here. |
| 17 | |
| 18 | struct ConformanceCase { |
| 19 | name string |
| 20 | src string |
| 21 | expected string |
| 22 | } |
| 23 | |
| 24 | const cases = [ |
| 25 | ConformanceCase{ |
| 26 | name: 'single scalar mapping' |
| 27 | src: 'key: value' |
| 28 | expected: '{"key":"value"}' |
| 29 | }, |
| 30 | ConformanceCase{ |
| 31 | name: 'integer values' |
| 32 | src: 'a: 1\nb: -2\nc: 0\nd: 9223372036854775807' |
| 33 | expected: '{"a":1,"b":-2,"c":0,"d":9223372036854775807}' |
| 34 | }, |
| 35 | ConformanceCase{ |
| 36 | name: 'float values' |
| 37 | src: 'a: 1.5\nb: -0.001\nc: 1.0e10\nd: -1.5e-3' |
| 38 | expected: '{"a":1.5,"b":-0.001,"c":10000000000,"d":-0.0015}' |
| 39 | }, |
| 40 | ConformanceCase{ |
| 41 | name: 'simple sequence' |
| 42 | src: 'items:\n - one\n - two\n - three' |
| 43 | expected: '{"items":["one","two","three"]}' |
| 44 | }, |
| 45 | ConformanceCase{ |
| 46 | name: 'nested mapping under sequence item' |
| 47 | src: 'servers:\n - host: a\n port: 1\n - host: b\n port: 2' |
| 48 | expected: '{"servers":[{"host":"a","port":1},{"host":"b","port":2}]}' |
| 49 | }, |
| 50 | ConformanceCase{ |
| 51 | name: 'flow sequence inline' |
| 52 | src: 'tags: [web, api, db]' |
| 53 | expected: '{"tags":["web","api","db"]}' |
| 54 | }, |
| 55 | ConformanceCase{ |
| 56 | name: 'flow mapping inline' |
| 57 | src: 'config: {host: a, port: 8080}' |
| 58 | expected: '{"config":{"host":"a","port":8080}}' |
| 59 | }, |
| 60 | ConformanceCase{ |
| 61 | name: 'mixed flow and block' |
| 62 | src: 'top:\n inner:\n - {k: v, n: 1}\n - [a, b, c]' |
| 63 | expected: '{"top":{"inner":[{"k":"v","n":1},["a","b","c"]]}}' |
| 64 | }, |
| 65 | ConformanceCase{ |
| 66 | name: 'deeply nested mapping' |
| 67 | src: 'a:\n b:\n c:\n d:\n e: end' |
| 68 | expected: '{"a":{"b":{"c":{"d":{"e":"end"}}}}}' |
| 69 | }, |
| 70 | ConformanceCase{ |
| 71 | name: 'sequence of sequences' |
| 72 | src: 'matrix:\n - [1, 2, 3]\n - [4, 5, 6]\n - [7, 8, 9]' |
| 73 | expected: '{"matrix":[[1,2,3],[4,5,6],[7,8,9]]}' |
| 74 | }, |
| 75 | ConformanceCase{ |
| 76 | name: 'unicode in string values' |
| 77 | src: 'a: "café"\nb: "日本語"\nc: plain café' |
| 78 | expected: '{"a":"café","b":"日本語","c":"plain café"}' |
| 79 | }, |
| 80 | ConformanceCase{ |
| 81 | name: 'comment is stripped' |
| 82 | src: '# leading comment\nkey: value # trailing comment' |
| 83 | expected: '{"key":"value"}' |
| 84 | }, |
| 85 | ConformanceCase{ |
| 86 | name: 'document separator markers are tolerated' |
| 87 | src: '---\na: 1\n...' |
| 88 | expected: '{"a":1}' |
| 89 | }, |
| 90 | ConformanceCase{ |
| 91 | name: 'JSON-superset input (object)' |
| 92 | src: '{"a": 1, "b": [2, 3]}' |
| 93 | expected: '{"a":1,"b":[2,3]}' |
| 94 | }, |
| 95 | ConformanceCase{ |
| 96 | name: 'JSON-superset input (array root)' |
| 97 | src: '[1, 2, {"three": 3}]' |
| 98 | expected: '[1,2,{"three":3}]' |
| 99 | }, |
| 100 | ConformanceCase{ |
| 101 | name: 'sequence containing null' |
| 102 | src: 'a:\n - one\n - ~\n - three' |
| 103 | expected: '{"a":["one",null,"three"]}' |
| 104 | }, |
| 105 | ConformanceCase{ |
| 106 | name: 'mapping with quoted keys containing dots' |
| 107 | src: 'plain: 1\n"a.b": 2\n"c.d.e": 3' |
| 108 | expected: '{"plain":1,"a.b":2,"c.d.e":3}' |
| 109 | }, |
| 110 | ConformanceCase{ |
| 111 | name: 'mixed types in flow sequence' |
| 112 | src: 'mix: [1, "two", true, null, 3.14]' |
| 113 | expected: '{"mix":[1,"two",true,null,3.14]}' |
| 114 | }, |
| 115 | ConformanceCase{ |
| 116 | name: 'nested flow inside block sequence' |
| 117 | src: 'items:\n - {a: 1, b: [x, y]}\n - {a: 2, b: [z]}' |
| 118 | expected: '{"items":[{"a":1,"b":["x","y"]},{"a":2,"b":["z"]}]}' |
| 119 | }, |
| 120 | ]! |
| 121 | |
| 122 | fn test_yaml_conformance_cases() ! { |
| 123 | mut failed := []string{} |
| 124 | for c in cases { |
| 125 | doc := parse_text(c.src) or { |
| 126 | failed << '${c.name}: parse error: ${err}' |
| 127 | continue |
| 128 | } |
| 129 | got := doc.to_json() |
| 130 | matched := json_logically_eq(got, c.expected) or { |
| 131 | failed << '${c.name}: invalid JSON produced: ${got} (vs expected ${c.expected}): ${err}' |
| 132 | continue |
| 133 | } |
| 134 | if !matched { |
| 135 | failed << '${c.name}: got ${got}, want ${c.expected}' |
| 136 | } |
| 137 | } |
| 138 | if failed.len > 0 { |
| 139 | eprintln('YAML conformance failures (${failed.len}/${cases.len}):') |
| 140 | for f in failed { |
| 141 | eprintln(' - ${f}') |
| 142 | } |
| 143 | assert false, '${failed.len} conformance case(s) failed' |
| 144 | } |
| 145 | } |
| 146 | |