view mod_xmllang_check/test_xmpplang.lua @ 6513:5fb466693e85

mod_storage_xmlarchive: Ensure list index files are removed using os.remove() on the list files leaves the new index files behind since util.datamanager does not know they are removed. Thanks Link Mauve
author Kim Alvefur <zash@zash.se>
date Tue, 07 Apr 2026 21:10:54 +0200
parents de3dc4297035
children
line wrap: on
line source

#!/usr/bin/env lua
-- Test suite for xmpplang.lib.lua parser

local script_dir = arg and arg[0] and arg[0]:match("^(.*)/[^/]+$") or ".";
package.path = script_dir .. "/?.lib.lua;" .. package.path;

local match_xmpplang = require("xmpplang")

local passed = 0
local failed = 0

local function test(input, expected, description)
    local result = match_xmpplang(input)
    if result == expected then
        passed = passed + 1
        print(string.format("PASS: %s (%s) -> %s", description, input, tostring(result)))
    else
        failed = failed + 1
        print(string.format("FAIL: %s (%s) -> expected %s, got %s", description, input, tostring(expected), tostring(result)))
    end
end

print("=== Basic language tags ===")
test("en", true, "2-letter language")
test("eng", true, "3-letter language")
test("abcd", true, "4-letter language")
test("abcde", true, "5-letter language")
test("abcdefgh", true, "8-letter language")
test("abcdefghi", false, "9-letter language (invalid)")
test("e", false, "1-letter language (invalid)")

print("\n=== Language with script ===")
test("en-Latn", true, "language-script")
test("zh-Hans", true, "zh-Hans")
test("zh-Hant", true, "zh-Hant")
test("en-Latn-US", true, "language-script-region")

print("\n=== Language with region ===")
test("en-US", true, "language-region (2 alpha)")
test("en-GB", true, "en-GB")
test("en-123", true, "language-region (3 digit)")
-- Note: en-1234 is valid because 1234 matches variant (digit + 3 alphanum)
test("en-1234", true, "en-1234 (parsed as language + variant)")
-- Note: en-ABC is valid because ABC matches extlang (3 alpha)
test("en-ABC", true, "en-ABC (parsed as language + extlang)")
-- Actually invalid subtags
test("en-AB", true, "en-AB (language + region)")
test("en-A", false, "en-A (single alpha - invalid)")
test("en-12", false, "en-12 (2 digits - invalid)")
test("en-ABCDE", true, "en-ABCDE (language + 5 alpha variant)")

print("\n=== Language with extlang ===")
test("zh-cmn", true, "language-extlang")
test("zh-cmn-Hans", true, "language-extlang-script")
test("zh-cmn-Hant-HK", true, "language-extlang-script-region")
test("zh-yue", true, "zh-yue")
test("zh-cmn-yue", true, "language-extlang-extlang")
test("zh-cmn-yue-gan", true, "language-extlang-extlang-extlang (max)")
test("zh-cmn-yue-gan-hak", false, "4 extlangs (too many)")

print("\n=== Variants ===")
test("en-US-12345", true, "5-char variant")
test("en-US-12345678", true, "8-char variant")
test("en-US-1abc", true, "digit+3alphanum variant")
test("en-US-rozaj", true, "rozaj variant")
test("sl-IT-nedis", true, "sl-IT-nedis")
test("en-US-abcd", false, "4-char alpha variant (invalid)")
test("en-US-123456789", false, "9-char variant (invalid)")

print("\n=== Extensions ===")
test("en-a-bbb", true, "extension a")
test("en-a-bbb-ccc", true, "extension with multiple subtags")
test("en-US-a-bbb", true, "language-region-extension")
test("en-US-a-bbb-b-ccc", true, "multiple extensions")
test("en-u-co-phonebk", true, "unicode extension")
test("en-a-b", false, "extension subtag too short")
test("en-a-bbbbbbbbb", false, "extension subtag too long (9)")

print("\n=== Privateuse ===")
test("x-private", true, "privateuse standalone")
test("x-a", true, "privateuse single char subtag")
test("x-abcdefgh", true, "privateuse 8-char subtag")
test("x-abc-def", true, "privateuse multiple subtags")
test("en-x-private", true, "language-privateuse")
test("en-US-x-private", true, "language-region-privateuse")
test("X-private", true, "uppercase X privateuse")
test("x-abcdefghi", false, "privateuse subtag too long (9)")

print("\n=== Grandfathered irregular ===")
test("en-GB-oed", true, "en-GB-oed")
test("i-ami", true, "i-ami")
test("i-bnn", true, "i-bnn")
test("i-default", true, "i-default")
test("i-enochian", true, "i-enochian")
test("i-hak", true, "i-hak")
test("i-klingon", true, "i-klingon")
test("i-lux", true, "i-lux")
test("i-mingo", true, "i-mingo")
test("i-navajo", true, "i-navajo")
test("i-pwn", true, "i-pwn")
test("i-tao", true, "i-tao")
test("i-tay", true, "i-tay")
test("i-tsu", true, "i-tsu")
test("sgn-BE-FR", true, "sgn-BE-FR")
test("sgn-BE-NL", true, "sgn-BE-NL")
test("sgn-CH-DE", true, "sgn-CH-DE")
-- Case insensitive
test("I-AMI", true, "I-AMI (uppercase)")
test("En-Gb-Oed", true, "En-Gb-Oed (mixed case)")

print("\n=== Grandfathered regular ===")
test("art-lojban", true, "art-lojban")
test("cel-gaulish", true, "cel-gaulish")
test("no-bok", true, "no-bok")
test("no-nyn", true, "no-nyn")
test("zh-guoyu", true, "zh-guoyu")
test("zh-hakka", true, "zh-hakka")
test("zh-min", true, "zh-min")
test("zh-min-nan", true, "zh-min-nan")
test("zh-xiang", true, "zh-xiang")
-- Case insensitive
test("ZH-MIN", true, "ZH-MIN (uppercase)")
test("Art-Lojban", true, "Art-Lojban (mixed case)")

print("\n=== Encoding ===")
test("en.UTF-8", true, "language with encoding")
test("en-US.UTF-8", true, "language-region with encoding")
test("en-Latn-US.UTF-8", true, "language-script-region with encoding")
test("en.utf-8", true, "encoding lowercase")
test("en.Utf-8", true, "encoding mixed case")
test("en.UTF-16", false, "invalid encoding")

print("\n=== Complex valid tags ===")
test("zh-cmn-Hans-CN", true, "zh-cmn-Hans-CN")
test("sr-Latn-RS", true, "sr-Latn-RS")
test("sl-rozaj-biske", true, "sl-rozaj-biske (multiple variants)")
test("de-CH-1901", true, "de-CH-1901")
test("en-US-u-islamcal", true, "en-US-u-islamcal")
test("zh-Hans-CN-u-nu-hanidec", true, "complex with extension")

print("\n=== Invalid cases ===")
test("", false, "empty string")
test("-en", false, "leading hyphen")
test("en-", false, "trailing hyphen")
test("en--US", false, "double hyphen")
test("123", false, "numeric only")
test("en_US", false, "underscore separator")
test("en US", false, "space separator")
test("toolonglanguage", false, "language too long")

print("\n=== Edge cases ===")
test("qaa", true, "qaa (reserved)")
test("qaa-Qaaa-QM-x-southern", true, "all private use subtags")
test("en-Latn-GB-boont-r-extended-sequence-x-private", true, "complex tag")

print("\n=== Additional edge cases ===")
-- Encoding edge cases
test("en.UTF-8extra", false, "encoding with extra chars")
test("en.UTF", false, "partial encoding")
test("en.", false, "dot with nothing after")
test("en-US.UTF-8.extra", false, "double dot")

-- Extension boundary cases
test("en-0-abc", true, "extension with digit singleton")
test("en-9-abc", true, "extension with digit 9 singleton")
test("en-a-12", true, "extension with 2 char subtag")
test("en-a-12345678", true, "extension with 8 char subtag")
test("en-a-ab-cd-ef", true, "extension with multiple short subtags")

-- Privateuse boundary cases
test("x-1", true, "privateuse with single digit")
test("x-12345678", true, "privateuse with 8 chars")
test("x-a-b-c-d-e", true, "privateuse with many 1-char subtags")

-- Language boundary cases (no extlang possible after 4+ alpha language)
-- These are invalid because 3-alpha "ext" doesn't match script/region/variant/etc.
test("abcd-ext", false, "4-letter language with 3-char subtag (invalid)")
test("abcde-ext", false, "5-letter language with 3-char subtag (invalid)")

-- Grandfathered should not be prefix-matched
test("i-ami-extra", false, "grandfathered with extra (invalid)")
-- Note: zh-min-foo is valid as langtag (foo is second extlang in zh-min-foo)
test("zh-min-foo", true, "zh-min-foo (valid: language + extlang + extlang)")

-- Variants cannot repeat in a way that produces invalid structure
test("en-rozaj-rozaj", true, "repeated variant (syntactically valid)")

-- Script + region + variant combinations
test("en-Latn-US-rozaj", true, "all optional components")
test("en-US-Latn", false, "region before script (invalid order)")

print(string.format("\n=== Results: %d passed, %d failed ===", passed, failed))
if failed > 0 then
    os.exit(1)
end