diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-19 13:43:28 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-19 13:43:28 +0200 |
| commit | 6bdd5afc5e185b639f9cf5e7c3d662ddc3ecf0e1 (patch) | |
| tree | 0fcef5aa47fa6b58c296ed9655c0c8e3be95ab53 /test | |
| parent | b26089630a61a54060c86ec26be6dc4fe5418b12 (diff) | |
| download | colitur-6bdd5afc5e185b639f9cf5e7c3d662ddc3ecf0e1.tar.gz colitur-6bdd5afc5e185b639f9cf5e7c3d662ddc3ecf0e1.zip | |
fix(naming): merge duplicate [section] blocks in the language table
F1 (review round 1): of_string's find took only the FIRST section of a
given name (List.find_opt), so a second [celebration] block anywhere in
the file was silently dropped in its entirety -- reproduced with two
blocks (a in the first, b in the second): b resolved to the slug
fallback "b", not its real value.
This is a data-loss footgun aimed squarely at what happens next: Tasks
3/4 write a 595-entry, hand-edited la.ini, and appending a second
[celebration] block is the natural way to paste in a new batch of names.
Worse, the failure surfaces nowhere near its cause -- a coverage check
reports the dropped slugs as missing a Latin name, with nothing pointing
at the parser.
find now folds over every section sharing the name, in file order, so
all blocks merge. This also settles which value wins when the same key
appears in two different blocks: later in the file wins, consistent with
the existing within-one-block behaviour (unchanged, still last SM.add
wins) and with what a reader expects when appending to an INI file.
lang.mli now documents both duplicate policies explicitly, and notes
they run OPPOSITE to Overlay_ini.get's first-match (List.assoc_opt) over
the same section.fields shape -- undocumented before, and a latent trap
since the two modules read the same section type but resolve a
duplicate key in opposite directions.
Three tests added: two [celebration] blocks both resolve (the F1
regression), a key repeated across two blocks resolves to the later
block, and a key repeated within one block still resolves to the later
line (confirms unchanged behaviour). Confirmed the regression test fails
against the pre-fix code (b resolves to "b", the slug fallback) and
passes after.
Diffstat (limited to 'test')
| -rw-r--r-- | test/test_lang.ml | 34 |
1 files changed, 33 insertions, 1 deletions
diff --git a/test/test_lang.ml b/test/test_lang.ml index 0b16b30..b54e85b 100644 --- a/test/test_lang.ml +++ b/test/test_lang.ml @@ -74,6 +74,33 @@ let test_missing_meta_lang_is_error () = | Error _ -> () | Ok _ -> Alcotest.fail "a language file with no [meta] lang must be an Error" +(* F1 regression: a hand-edited language file WILL grow duplicate [section] + headers as contributors append entries over time (Tasks 3/4's 595-entry + la.ini). Both blocks' keys must resolve -- silently dropping the second + block is a data-loss footgun that surfaces as a false "missing name" + report far from its real cause. *) +let test_duplicate_sections_all_merge () = + let t = + ok + (L.of_string + "[meta]\nlang = la\n[celebration]\na = ALPHA\n[weekday]\nsunday = Dominica\n\ + [celebration]\nb = BETA\n") + in + Alcotest.(check string) "first block's key" "ALPHA" (L.celebration t "a"); + Alcotest.(check string) "second block's key" "BETA" (L.celebration t "b") + +let test_duplicate_key_across_sections_last_wins () = + let t = + ok + (L.of_string + "[meta]\nlang = la\n[celebration]\na = FIRST\n[celebration]\na = SECOND\n") + in + Alcotest.(check string) "later block's value wins" "SECOND" (L.celebration t "a") + +let test_duplicate_key_within_section_last_wins () = + let t = ok (L.of_string "[meta]\nlang = la\n[celebration]\na = FIRST\na = SECOND\n") in + Alcotest.(check string) "later line's value wins" "SECOND" (L.celebration t "a") + let suite = ( "Lang", [ Alcotest.test_case "meta" `Quick test_meta; @@ -82,4 +109,9 @@ let suite = Alcotest.test_case "fallback chain" `Quick test_fallback_chain; Alcotest.test_case "raw is identity" `Quick test_raw_is_identity; Alcotest.test_case "malformed is error" `Quick test_malformed_is_error_not_crash; - Alcotest.test_case "missing meta lang is error" `Quick test_missing_meta_lang_is_error ] ) + Alcotest.test_case "missing meta lang is error" `Quick test_missing_meta_lang_is_error; + Alcotest.test_case "duplicate sections all merge" `Quick test_duplicate_sections_all_merge; + Alcotest.test_case "duplicate key across sections: last wins" `Quick + test_duplicate_key_across_sections_last_wins; + Alcotest.test_case "duplicate key within section: last wins" `Quick + test_duplicate_key_within_section_last_wins ] ) |
