From 0d87c4273994f7c74ca143e96c9d66ba62962748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibaut=20Barr=C3=A8re?= Date: Thu, 17 Sep 2026 08:05:48 +0200 Subject: [PATCH 1/4] Add "failing examples" to CI --- .github/scripts/validate-should-fail.sh | 49 +++++++++++++++++++ .github/workflows/ci.yml | 3 ++ examples/should-fail/README.md | 7 +++ .../should-fail/duplicate-CalendarDate.xml | 17 +++++++ .../duplicate-GroupOfLinkSequences.xml | 13 +++++ .../should-fail/duplicate-ValidBetween.xml | 17 +++++++ .../should-fail/duplicate-ValidityPeriod.xml | 23 +++++++++ 7 files changed, 129 insertions(+) create mode 100755 .github/scripts/validate-should-fail.sh create mode 100644 examples/should-fail/README.md create mode 100644 examples/should-fail/duplicate-CalendarDate.xml create mode 100644 examples/should-fail/duplicate-GroupOfLinkSequences.xml create mode 100644 examples/should-fail/duplicate-ValidBetween.xml create mode 100644 examples/should-fail/duplicate-ValidityPeriod.xml diff --git a/.github/scripts/validate-should-fail.sh b/.github/scripts/validate-should-fail.sh new file mode 100755 index 000000000..c66b282b3 --- /dev/null +++ b/.github/scripts/validate-should-fail.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# Negative examples: documents that MUST be rejected by the schema. +# +# Each case is "|". We assert BEHAVIOUR (the +# document is rejected, and its output contains the declared substring) - not a +# specific constraint name, and each case carries its own clause. +# +# All cases share ONE schema and are validated in a SINGLE xmllint call (one +# compile for many files) to stay fast. Fails closed: accepted, wrong reason, or +# xmllint not running all count as failures. +# +# CI uses the vendored 2025 Linux x86-64 xmllint ("Temporary xmllint master", +# https://github.com/TransmodelEcosystem/NeTEx/pull/915); set XMLLINT_BIN to a local +# xmllint to run this on any other OS or CPU architecture (macOS, Windows, ARM, ...). + +set -u +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +ROOT_DIR=$( cd -- "${SCRIPT_DIR}/../.." &> /dev/null && pwd ) +XMLLINT="${XMLLINT_BIN:-${SCRIPT_DIR}/xmllint}" +cd "${ROOT_DIR}" + +SCHEMA="xsd/NeTEx_publication.xsd" + +# "|" +CASES=( + "examples/should-fail/duplicate-GroupOfLinkSequences.xml|Duplicate key-sequence" + "examples/should-fail/duplicate-CalendarDate.xml|Duplicate key-sequence" + "examples/should-fail/duplicate-ValidBetween.xml|Duplicate key-sequence" + "examples/should-fail/duplicate-ValidityPeriod.xml|Duplicate key-sequence" +) + +files=() +for c in "${CASES[@]}"; do files+=("${c%%|*}"); done +out=$("${XMLLINT}" --noout --schema "${SCHEMA}" "${files[@]}" 2>&1) + +fail=0 +echo "Checking NeTEx 'should-fail' negative examples ..." +for c in "${CASES[@]}"; do + f="${c%%|*}"; expected="${c#*|}" + if printf '%s\n' "${out}" | grep -Fqx "${f} validates"; then + echo "SHOULD HAVE FAILED ${f} — accepted (must be rejected)"; fail=1 + elif printf '%s\n' "${out}" | grep -F "${f}:" | grep -q "${expected}"; then + echo "OK ${f}" + else + echo "ERROR ${f} — rejected, but not matching '${expected}'"; fail=1 + fi +done + +exit "${fail}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abef8b4f9..8b05dcd9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,9 @@ jobs: - name: Validate NeTEx XML examples run: ./.github/scripts/validate-examples.sh + - name: Validate negative examples (must-fail) + run: ./.github/scripts/validate-should-fail.sh + - name: Commit changes uses: EndBug/add-and-commit@v9 # https://github.com/marketplace/actions/add-commit with: diff --git a/examples/should-fail/README.md b/examples/should-fail/README.md new file mode 100644 index 000000000..35110f047 --- /dev/null +++ b/examples/should-fail/README.md @@ -0,0 +1,7 @@ +# Negative examples (must-fail) + +Each file carries one deliberate defect and must therefore be **rejected** by the +schema for a specific, expected reason. The cases — file, schema and expected +constraint — are declared in `.github/scripts/validate-should-fail.sh` (run in CI). + +Run locally: `XMLLINT_BIN=$(which xmllint) ./.github/scripts/validate-should-fail.sh` diff --git a/examples/should-fail/duplicate-CalendarDate.xml b/examples/should-fail/duplicate-CalendarDate.xml new file mode 100644 index 000000000..ac82ae569 --- /dev/null +++ b/examples/should-fail/duplicate-CalendarDate.xml @@ -0,0 +1,17 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + + 2020-01-01 + + + 2020-01-01 + + + + + diff --git a/examples/should-fail/duplicate-GroupOfLinkSequences.xml b/examples/should-fail/duplicate-GroupOfLinkSequences.xml new file mode 100644 index 000000000..40bb4cb59 --- /dev/null +++ b/examples/should-fail/duplicate-GroupOfLinkSequences.xml @@ -0,0 +1,13 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + + + + + + diff --git a/examples/should-fail/duplicate-ValidBetween.xml b/examples/should-fail/duplicate-ValidBetween.xml new file mode 100644 index 000000000..44eb0c8f2 --- /dev/null +++ b/examples/should-fail/duplicate-ValidBetween.xml @@ -0,0 +1,17 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + 2020-01-01T00:00:00Z + + + + + 2020-01-01T00:00:00Z + + + + diff --git a/examples/should-fail/duplicate-ValidityPeriod.xml b/examples/should-fail/duplicate-ValidityPeriod.xml new file mode 100644 index 000000000..e7f49a0d9 --- /dev/null +++ b/examples/should-fail/duplicate-ValidityPeriod.xml @@ -0,0 +1,23 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + + A + + 2020-01-01T00:00:00Z + + + + B + + 2020-01-01T00:00:00Z + + + + + + From 20db183cd4746994e335e544a088ebd6f0eb9696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibaut=20Barr=C3=A8re?= Date: Thu, 17 Sep 2026 08:26:40 +0200 Subject: [PATCH 2/4] Remove incorrect example Thanks @Aurige --- .github/scripts/validate-should-fail.sh | 1 - examples/should-fail/duplicate-CalendarDate.xml | 17 ----------------- 2 files changed, 18 deletions(-) delete mode 100644 examples/should-fail/duplicate-CalendarDate.xml diff --git a/.github/scripts/validate-should-fail.sh b/.github/scripts/validate-should-fail.sh index c66b282b3..41ab03d1a 100755 --- a/.github/scripts/validate-should-fail.sh +++ b/.github/scripts/validate-should-fail.sh @@ -24,7 +24,6 @@ SCHEMA="xsd/NeTEx_publication.xsd" # "|" CASES=( "examples/should-fail/duplicate-GroupOfLinkSequences.xml|Duplicate key-sequence" - "examples/should-fail/duplicate-CalendarDate.xml|Duplicate key-sequence" "examples/should-fail/duplicate-ValidBetween.xml|Duplicate key-sequence" "examples/should-fail/duplicate-ValidityPeriod.xml|Duplicate key-sequence" ) diff --git a/examples/should-fail/duplicate-CalendarDate.xml b/examples/should-fail/duplicate-CalendarDate.xml deleted file mode 100644 index ac82ae569..000000000 --- a/examples/should-fail/duplicate-CalendarDate.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - 2020-01-01T12:00:00Z - TEST - - - - - 2020-01-01 - - - 2020-01-01 - - - - - From 2032aa7f87fcc89e3355e21e6c198c3b625eb305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibaut=20Barr=C3=A8re?= Date: Thu, 17 Sep 2026 08:37:17 +0200 Subject: [PATCH 3/4] Make failure check more narrow --- .github/scripts/validate-should-fail.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/scripts/validate-should-fail.sh b/.github/scripts/validate-should-fail.sh index 41ab03d1a..b1bc1ff7a 100755 --- a/.github/scripts/validate-should-fail.sh +++ b/.github/scripts/validate-should-fail.sh @@ -22,10 +22,11 @@ cd "${ROOT_DIR}" SCHEMA="xsd/NeTEx_publication.xsd" # "|" +NS="{http://www.netex.org.uk/netex}" CASES=( - "examples/should-fail/duplicate-GroupOfLinkSequences.xml|Duplicate key-sequence" - "examples/should-fail/duplicate-ValidBetween.xml|Duplicate key-sequence" - "examples/should-fail/duplicate-ValidityPeriod.xml|Duplicate key-sequence" + "examples/should-fail/duplicate-GroupOfLinkSequences.xml|Element '${NS}GroupOfLinkSequences': Duplicate key-sequence ['TEST:GroupOfLinkSequences:1', '1.0']" + "examples/should-fail/duplicate-ValidBetween.xml|Element '${NS}ValidBetween': Duplicate key-sequence ['TEST:ValidBetween:1', '1.0']" + "examples/should-fail/duplicate-ValidityPeriod.xml|Element '${NS}ValidityPeriod': Duplicate key-sequence ['TEST:ValidityPeriod:1', '1.0']" ) files=() @@ -38,7 +39,7 @@ for c in "${CASES[@]}"; do f="${c%%|*}"; expected="${c#*|}" if printf '%s\n' "${out}" | grep -Fqx "${f} validates"; then echo "SHOULD HAVE FAILED ${f} — accepted (must be rejected)"; fail=1 - elif printf '%s\n' "${out}" | grep -F "${f}:" | grep -q "${expected}"; then + elif printf '%s\n' "${out}" | grep -F "${f}:" | grep -Fq "${expected}"; then echo "OK ${f}" else echo "ERROR ${f} — rejected, but not matching '${expected}'"; fail=1 From 73bb15caff42c17d41a44b1871004c8f22cf116e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibaut=20Barr=C3=A8re?= Date: Thu, 17 Sep 2026 08:44:51 +0200 Subject: [PATCH 4/4] Use more structured input for should_fail (more readable) --- .github/scripts/validate-should-fail.sh | 29 +++++++++++-------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/.github/scripts/validate-should-fail.sh b/.github/scripts/validate-should-fail.sh index b1bc1ff7a..96e2b4534 100755 --- a/.github/scripts/validate-should-fail.sh +++ b/.github/scripts/validate-should-fail.sh @@ -1,9 +1,9 @@ #!/bin/bash # Negative examples: documents that MUST be rejected by the schema. # -# Each case is "|". We assert BEHAVIOUR (the -# document is rejected, and its output contains the declared substring) - not a -# specific constraint name, and each case carries its own clause. +# Each case is declared as: should_fail . +# We assert BEHAVIOUR (the document is rejected, and its output contains the +# declared substring) - not a specific constraint name. # # All cases share ONE schema and are validated in a SINGLE xmllint call (one # compile for many files) to stay fast. Fails closed: accepted, wrong reason, or @@ -21,28 +21,25 @@ cd "${ROOT_DIR}" SCHEMA="xsd/NeTEx_publication.xsd" -# "|" -NS="{http://www.netex.org.uk/netex}" -CASES=( - "examples/should-fail/duplicate-GroupOfLinkSequences.xml|Element '${NS}GroupOfLinkSequences': Duplicate key-sequence ['TEST:GroupOfLinkSequences:1', '1.0']" - "examples/should-fail/duplicate-ValidBetween.xml|Element '${NS}ValidBetween': Duplicate key-sequence ['TEST:ValidBetween:1', '1.0']" - "examples/should-fail/duplicate-ValidityPeriod.xml|Element '${NS}ValidityPeriod': Duplicate key-sequence ['TEST:ValidityPeriod:1', '1.0']" -) +files=(); expected=() +should_fail() { files+=("$1"); expected+=("$2"); } + +should_fail examples/should-fail/duplicate-GroupOfLinkSequences.xml "Duplicate key-sequence ['TEST:GroupOfLinkSequences:1', '1.0']" +should_fail examples/should-fail/duplicate-ValidBetween.xml "Duplicate key-sequence ['TEST:ValidBetween:1', '1.0']" +should_fail examples/should-fail/duplicate-ValidityPeriod.xml "Duplicate key-sequence ['TEST:ValidityPeriod:1', '1.0']" -files=() -for c in "${CASES[@]}"; do files+=("${c%%|*}"); done out=$("${XMLLINT}" --noout --schema "${SCHEMA}" "${files[@]}" 2>&1) fail=0 echo "Checking NeTEx 'should-fail' negative examples ..." -for c in "${CASES[@]}"; do - f="${c%%|*}"; expected="${c#*|}" +for i in "${!files[@]}"; do + f="${files[$i]}"; exp="${expected[$i]}" if printf '%s\n' "${out}" | grep -Fqx "${f} validates"; then echo "SHOULD HAVE FAILED ${f} — accepted (must be rejected)"; fail=1 - elif printf '%s\n' "${out}" | grep -F "${f}:" | grep -Fq "${expected}"; then + elif printf '%s\n' "${out}" | grep -F "${f}:" | grep -Fq "${exp}"; then echo "OK ${f}" else - echo "ERROR ${f} — rejected, but not matching '${expected}'"; fail=1 + echo "ERROR ${f} — rejected, but not matching '${exp}'"; fail=1 fi done