docs / Suite de conformidadConformance suite
La suite de la especificación 1.1 tiene 376 casos (303 heredados de la suite 1.0, que se conservan, y 73 nuevos) repartidos en 16 categorías.
Suite de conformidad de .mini (especificación 1.1)#
Casos independientes del lenguaje para comprobar que una implementación de
.mini (Python, TypeScript u otra) se comporta como exige SPEC.md. Todas las
implementaciones deben ejecutar la misma lógica de runner descrita abajo.
python conformance/run_python.py # resumen por categoría (sale con 1 si algo falla)
python conformance/run_python.py -v # muestra el motivo de cada fallo
python conformance/run_python.py -k quotes
python conformance/generate.py # regenera cases/*.json
node conformance/run_js.mjs # misma suite contra js/mini.js (motor del playground)
La suite también corre dentro de pytest (tests/test_conformance.py), de
cd ts && npm test (ts/test/conformance.test.ts) y de node tests/test_js_port.mjs
(motor del playground).
Archivos#
| Ruta | Contenido |
|---|---|
cases/<categoría>.json |
{"suite", "spec", "category", "cases": [caso, ...]} |
generate.py |
Fuente de los casos. Las expectativas salen de los fixtures publicados en forks/*/fixtures o están escritas a mano desde SPEC.md; nunca se calculan ejecutando la implementación. |
run_python.py |
Runner de referencia contra minifmt. |
run_js.mjs |
Runner contra js/mini.js, el motor del playground generado desde ts/src. |
oraculo/ |
Decodificador de referencia independiente, escrito desde la gramática de la SPEC sin importar minifmt (decodificador.py), y su runner sobre este corpus (ejecutar.py). |
Los casos que usan family leen forks/<family>/contract.json desde la raíz
del repositorio.
Formato de un caso#
{
"id": "quote-doubled",
"category": "quotes",
"description": "\"\" dentro de comillas es una comilla",
"mode": "strict",
"family": "a",
"contract": { "...": "contrato embebido, alternativa a family" },
"parent": "a",
"input": "mk|n=1\n\"dijo \"\"sí\"\"\"*,no|a*|a|1",
"expected": {
"canonical": { "prefix": "mk", "header": {"n": 1, "v": 1}, "rows": [ ... ] },
"errors": [ {"code": "E08", "line": 2} ],
"mini": "texto .mini esperado al serializar",
"rejected": true,
"diagnostics": { "invalid_lines": [3], "missing_records": 0 }
}
}
- Cada caso tiene exactamente uno de
family(nombre de familia oficial) ocontract(contrato embebido con el mismo esquema quecontract.json), salvo el modocontract, que no usa ninguno. inputes texto.mini(modosstrictylenient), un objeto canónico (mododumps), un contrato (modocontract) onull(modofork).- En
expectedsolo aparecen las claves que aplican al modo.
Semántica de los modos (lógica del runner)#
Comparaciones comunes:
- Errores: se comparan como conjunto de pares distintos
(code, line); el orden y las repeticiones no importan.linees la línea física (1-based, contando las líneas en blanco omitidas); los errores de documento (E01, E04) y los de contrato/fork (E20, E21) usan línea0. - Canónico: igualdad estructural; las claves de los objetos se comparan
como conjunto y los números numéricamente (
-1≡-1.0, tolerancia 1e-12). Los campos ausentes de un registro valennull; una lista marcada aparece como dos claves hermanas (elementos y selección).
| Modo | Acción | Pasa si |
|---|---|---|
strict |
parse(input, contract, strict) |
Si expected.errors existe: el documento se rechaza y los pares coinciden. Si no: se acepta, el canónico coincide con expected.canonical y, cuando hay expected.mini, dumps(canónico) == mini y parse(mini) vuelve a dar el mismo canónico (ida y vuelta). |
lenient |
parse(input, contract, lenient) |
Si hay expected.canonical: se devuelve un documento con esos registros válidos (y la cabecera leída), y sus errores coinciden con expected.errors (lista vacía = sin errores). Si no hay canonical: el documento no se puede construir (p. ej. E01) y los errores coinciden. Si hay expected.diagnostics: las líneas rechazadas (ascendentes, sin la cabecera) y los registros faltantes (n − líneas de registro, mínimo 0) coinciden. |
dumps |
dumps(input, contract) |
Si expected.rejected: el serializador lanza un error y, si hay expected.errors, su par (code, line) es uno de los esperados (SPEC §9: código del parser y línea que ocuparía la entrada). Si no: el texto es idéntico a expected.mini byte a byte y ese texto se acepta en modo estricto. |
contract |
cargar input como contrato |
Los errores (E20, línea 0) coinciden; [] significa contrato válido. |
fork |
comprobar contract/family contra parent (nombre de familia o contrato embebido) |
Los errores de invariantes (E21, línea 0) coinciden como conjunto; [] significa bifurcación válida. |
Un fallo inesperado de la implementación (excepción no prevista) cuenta como caso fallido.
Categorías#
| Categoría | Cubre |
|---|---|
fixtures |
valid.mini ↔ canonical.json y cada bad_*.mini de las 14 familias |
lenient |
cada bad_*.mini en modo tolerante: registros válidos conservados |
escapes |
\| \, (con cualquier separador) \; \* \" \\ \n, escapes inválidos y colgantes (E09), CRLF, escaping.mini de cada familia |
quotes |
elementos entre comillas estilo CSV, "", marcador dentro o fuera, E09 |
lists |
aridad min/max/count_key (E07), tipos de elementos, separador propio, elementos vacíos |
mlist |
las cuatro reglas de marcador (E08) y la forma canónica de la selección |
tuples |
aridad (E07), componentes opcionales, marcador prohibido |
optionals |
campos vacíos, cola de extensiones, requeridos vacíos (E06), listas opcionales vacías (null) |
arity |
menos campos que el núcleo, o campos excedentes sin v posterior (E05) |
types |
int, float, bool, enum, date, decimal y sus formas léxicas (E06, E10, E13) |
unique |
E11 y su interacción con el modo tolerante |
header |
E01, E02, E03, E04, E12, BOM, líneas en blanco, claves tipadas, mal tipadas, repetidas y desconocidas |
truncation |
último registro incompleto, líneas faltantes, diagnóstico de regeneración |
roundtrip |
serialización canónica y su rechazo de objetos inválidos con el código del parser |
contract |
contratos inválidos (E20) |
fork |
invariantes I3/I4 del protocolo de bifurcación (E21) |
Reglas añadidas en la especificación 1.1#
La versión 1.0 de esta suite evitaba siete comportamientos que SPEC.md 1.0 no
definía con precisión. SPEC 1.1 fija una regla para cada uno (§13 de la
especificación) y la suite la comprueba; SPEC_1_1_RULES en generate.py enumera
los casos de cada regla y tests/test_conformance.py verifica que existen.
| Punto | Regla 1.1 | ADR | Casos (ejemplos) |
|---|---|---|---|
\, con separador distinto de , |
coma literal; \<sep> solo para el separador propio |
0009 | esc-comma-custom-separator, esc-other-separator-invalid |
Booleanos yes/t…, enteros con +, floats .5 o 1. |
E06; solo true/false/1/0 y dígitos ASCII |
0010 | sc-bool-yes, sc-int-plus, sc-float-leading-dot |
Valor de cabecera mal tipado (n=abc) |
código de la violación de tipo (E06), sin E03 | 0011 | hdr-n-not-int, hdr-n-not-int-lenient |
| Claves de cabecera repetidas | E12; cuenta la primera | 0012 | hdr-duplicate-key, hdr-duplicate-n |
Elementos de lista vacíos e ida y vuelta de [""] |
cadena vacía; el serializador escribe "" |
0013 | list-empty-element-middle, list-single-empty-string |
| Lista opcional vacía | null | 0014 | opt-list-empty-null, opt-mlist-empty-null |
| Código de error del serializador | código del parser y línea que ocuparía la entrada | 0015 | dumps-error-range, dumps-error-second-record |
Además, la suite 1.1 cubre los tipos date y decimal
(ADR 0016; casos date-*, decimal-*,
dumps-date-decimal, contract-date-*).
Los 303 casos de la suite 1.0 se conservan con las mismas expectativas; cinco casos
dumps-reject-* ahora también fijan el código de error y contract-unknown-type
usa datetime como tipo desconocido.
Casos añadidos después de 1.1 (376 en total)#
| Caso | Regla (SPEC) |
|---|---|
hdr-escaped-eq-key, hdr-escaped-eq-key-lenient |
key\=value en la cabecera: \= es un escape inválido (E09, §3.3) y su = está escapado, así que la entrada no tiene separador (E12, §3.2 y §5) |
hdr-escaped-eq-n |
n\=1 no declara n: E09, E12 y E03 |
hdr-escaped-eq-in-value |
\= dentro de un valor: solo E09 |
Las expectativas se escribieron desde la norma, no ejecutando ninguna implementación. Los tres ejecutores
(run_python.py, run_js.mjs y ts/test/conformance.test.ts) las cumplen; antes de corregir el parser Python, tres de ellas fallaban.
Origen de las expectativas y oráculo independiente#
De los 376 casos, 128 (fx-*: fixtures, dumps, escaping y lenient de las 14 familias) salen de forks/*/fixtures, que
benchmark/make_forks.py genera con dumps/parse de la implementación Python: respecto de Python son circulares. El resto está
escrito a mano desde SPEC.md. Para no depender solo de eso, oraculo/decodificador.py reimplementa la lectura desde la gramática
de la especificación con otra estructura y sin importar minifmt; python conformance/oraculo/ejecutar.py comprueba que cumple los
307 de ellos, los de modo strict y lenient (los modos dumps, contract y fork no los decodifica). tests/test_nucleo_propiedades.py,
tests/test_nucleo_oraculo.py y tools/fuzz_diferencial.py lo usan para comparar Python y js/mini.js sobre documentos generados.
Las zonas en las que la SPEC calla y el oráculo adopta una lectura están declaradas al principio de su código y en el ADR 0017.
The suite for specification 1.1 has 376 cases (303 inherited from suite 1.0, which are kept, and 73 new) across 16 categories.
.mini conformance suite (specification 1.1)#
Language-independent cases to check that a .mini implementation (Python,
TypeScript or another) behaves as SPEC.md requires. Every implementation must
run the same runner logic described below.
python conformance/run_python.py # summary by category (exits 1 if anything fails)
python conformance/run_python.py -v # shows the reason for each failure
python conformance/run_python.py -k quotes
python conformance/generate.py # regenerates cases/*.json
node conformance/run_js.mjs # same suite against js/mini.js (playground engine)
The suite also runs inside pytest (tests/test_conformance.py), in
cd ts && npm test (ts/test/conformance.test.ts) and in node tests/test_js_port.mjs
(playground engine).
Files#
| Path | Content |
|---|---|
cases/<category>.json |
{"suite", "spec", "category", "cases": [case, ...]} |
generate.py |
Source of the cases. Expectations come from the fixtures published in forks/*/fixtures or are handwritten from SPEC.md; they are never computed by running the implementation. |
run_python.py |
Reference runner against minifmt. |
run_js.mjs |
Runner against js/mini.js, the playground engine generated from ts/src. |
oraculo/ |
Independent reference decoder written from the SPEC grammar without importing minifmt (decodificador.py), and its runner over this corpus (ejecutar.py). |
Cases using family read forks/<family>/contract.json from the repository root.
Case format#
{
"id": "quote-doubled",
"category": "quotes",
"description": "\"\" inside quotes is one quote",
"mode": "strict",
"family": "a",
"contract": { "...": "embedded contract, alternative to family" },
"parent": "a",
"input": "mk|n=1\n\"dijo \"\"sí\"\"\"*,no|a*|a|1",
"expected": {
"canonical": { "prefix": "mk", "header": {"n": 1, "v": 1}, "rows": [ ... ] },
"errors": [ {"code": "E08", "line": 2} ],
"mini": "expected .mini text when serializing",
"rejected": true,
"diagnostics": { "invalid_lines": [3], "missing_records": 0 }
}
}
- Each case has exactly one of
family(official family name) orcontract(embedded contract with the same schema ascontract.json), exceptcontractmode, which uses neither. inputis.minitext (strictandlenientmodes), a canonical object (dumpsmode), a contract (contractmode) ornull(forkmode).expectedonly contains the keys that apply to the mode.
Mode semantics (runner logic)#
Common comparisons:
- Errors: compared as a set of distinct
(code, line)pairs; order and repetition do not matter.lineis the physical line (1-based, counting skipped blank lines); document errors (E01, E04) and contract/fork errors (E20, E21) use line0. - Canonical: structural equality; object keys are compared as a set and numbers
numerically (
-1≡-1.0, 1e-12 tolerance). A record's missing fields count asnull; a marked list appears as two sibling keys (elements and selection).
| Mode | Action | Passes if |
|---|---|---|
strict |
parse(input, contract, strict) |
If expected.errors exists: the document is rejected and the pairs match. If not: it is accepted, the canonical matches expected.canonical and, when expected.mini exists, dumps(canonical) == mini and parse(mini) yields the same canonical again (round-trip). |
lenient |
parse(input, contract, lenient) |
If expected.canonical exists: a document is returned with those valid records (and the header read), and its errors match expected.errors (empty list = no errors). If there is no canonical: the document cannot be built (e.g. E01) and the errors match. If expected.diagnostics exists: the rejected lines (ascending, without the header) and the missing records (n − record lines, minimum 0) match. |
dumps |
dumps(input, contract) |
If expected.rejected: the serializer throws an error and, when expected.errors exists, its (code, line) pair is one of the expected ones (SPEC §9: the parser's code and the line the entry would occupy). If not: the text is byte-for-byte identical to expected.mini and that text is accepted in strict mode. |
contract |
load input as a contract |
The errors (E20, line 0) match; [] means valid contract. |
fork |
check contract/family against parent (family name or embedded contract) |
The invariant errors (E21, line 0) match as a set; [] means valid fork. |
An unexpected implementation failure (unforeseen exception) counts as a failed case.
Categories#
| Category | Covers |
|---|---|
fixtures |
valid.mini ↔ canonical.json and each bad_*.mini of the 14 families |
lenient |
each bad_*.mini in lenient mode: valid records kept |
escapes |
\| \, (with any separator) \; \* \" \\ \n, invalid and dangling escapes (E09), CRLF, each family's escaping.mini |
quotes |
CSV-style quoted elements, "", marker inside or outside, E09 |
lists |
min/max/count_key arity (E07), element types, custom separator, empty elements |
mlist |
the four marker rules (E08) and the canonical form of the selection |
tuples |
arity (E07), optional components, marker forbidden |
optionals |
empty fields, extension tail, empty requireds (E06), empty optional lists (null) |
arity |
fewer fields than the core, or excess fields without a later v (E05) |
types |
int, float, bool, enum, date, decimal and their lexical forms (E06, E10, E13) |
unique |
E11 and its interaction with lenient mode |
header |
E01, E02, E03, E04, E12, BOM, blank lines, typed, ill-typed, repeated and unknown keys |
truncation |
incomplete last record, missing lines, regeneration diagnostics |
roundtrip |
canonical serialization and its rejection of invalid objects with the parser's code |
contract |
invalid contracts (E20) |
fork |
I3/I4 invariants of the forking protocol (E21) |
Rules added in specification 1.1#
Version 1.0 of this suite avoided seven behaviors that SPEC.md 1.0 did not
define precisely. SPEC 1.1 fixes a rule for each one (§13 of the specification)
and the suite checks it; SPEC_1_1_RULES in generate.py lists the cases of each
rule and tests/test_conformance.py verifies that they exist.
| Point | 1.1 rule | ADR | Cases (examples) |
|---|---|---|---|
\, with a separator other than , |
literal comma; \<sep> only for the contract's own separator |
0009 | esc-comma-custom-separator, esc-other-separator-invalid |
Booleans yes/t…, integers with +, floats .5 or 1. |
E06; only true/false/1/0 and ASCII digits |
0010 | sc-bool-yes, sc-int-plus, sc-float-leading-dot |
Ill-typed header value (n=abc) |
code of the type violation (E06), no E03 | 0011 | hdr-n-not-int, hdr-n-not-int-lenient |
| Repeated header keys | E12; the first one counts | 0012 | hdr-duplicate-key, hdr-duplicate-n |
Empty list elements and the round-trip of [""] |
the empty string; the serializer writes "" |
0013 | list-empty-element-middle, list-single-empty-string |
| Empty optional list | null | 0014 | opt-list-empty-null, opt-mlist-empty-null |
| Serializer error code | the parser's code and the line the entry would occupy | 0015 | dumps-error-range, dumps-error-second-record |
The 1.1 suite also covers the date and decimal types
(ADR 0016; cases date-*, decimal-*,
dumps-date-decimal, contract-date-*).
The 303 cases of the 1.0 suite are kept with the same expectations; five
dumps-reject-* cases now also pin the error code and contract-unknown-type
uses datetime as the unknown type.
Cases added after 1.1 (376 in total)#
| Case | Rule (SPEC) |
|---|---|
hdr-escaped-eq-key, hdr-escaped-eq-key-lenient |
key\=value in the header: \= is an invalid escape (E09, §3.3) and its = is escaped, so the entry has no separator (E12, §3.2 and §5) |
hdr-escaped-eq-n |
n\=1 does not declare n: E09, E12 and E03 |
hdr-escaped-eq-in-value |
\= inside a value: E09 only |
The expectations were written from the norm, not by running an implementation. The three executors (run_python.py,
run_js.mjs and ts/test/conformance.test.ts) meet them; before the Python parser was fixed, three of them failed.
Where the expectations come from, and the independent oracle#
Of the 376 cases, 128 (fx-*: fixtures, dumps, escaping and lenient of the 14 families) come from forks/*/fixtures, which
benchmark/make_forks.py produces with the Python implementation's dumps/parse: with respect to Python they are circular. The rest
is written by hand from SPEC.md. So as not to depend only on that, oraculo/decodificador.py re-implements decoding from the
specification grammar with a different structure and without importing minifmt; python conformance/oraculo/ejecutar.py checks
that it meets 307 of them, the strict and lenient ones (it does not decode the dumps, contract and fork modes).
tests/test_nucleo_propiedades.py, tests/test_nucleo_oraculo.py and tools/fuzz_diferencial.py use it to compare Python and
js/mini.js on generated documents. Where the SPEC is silent and the oracle picks a reading, it is declared at the top of its code
and in ADR 0017.