Check case pair stability policy in Java Fixes https://github.com/unicode-org/unicodetools/issues/386
diff --git a/unicodetools/src/main/resources/org/unicode/text/UCD/UnicodeInvariantTest.txt b/unicodetools/src/main/resources/org/unicode/text/UCD/UnicodeInvariantTest.txt index b5822cf..827afac 100644 --- a/unicodetools/src/main/resources/org/unicode/text/UCD/UnicodeInvariantTest.txt +++ b/unicodetools/src/main/resources/org/unicode/text/UCD/UnicodeInvariantTest.txt
@@ -310,7 +310,9 @@ In \P{U-1:gc=Cn}, R-1:NFKC_Simple_Casefold = NFKC_Simple_Casefold # Case Pair Stability: If two characters form a case pair in a version of Unicode, they will remain a case pair in each subsequent version of Unicode. If two characters do not form a case pair in a version of Unicode, they will never become a case pair in any subsequent version of Unicode. -# TODO +# +# Tested for full and simple case mappings in TestCodeInvariants.testCasePairStability. +# The simple counterpart is UTC-174-A11, as for case folding above. # Red Flag: cased and case_ignorable should be disjoint, # except for modifier letters and ◌ͅ.
diff --git a/unicodetools/src/test/java/org/unicode/text/UCD/TestCodeInvariants.java b/unicodetools/src/test/java/org/unicode/text/UCD/TestCodeInvariants.java index 0eea266..492e859 100644 --- a/unicodetools/src/test/java/org/unicode/text/UCD/TestCodeInvariants.java +++ b/unicodetools/src/test/java/org/unicode/text/UCD/TestCodeInvariants.java
@@ -29,6 +29,7 @@ import org.unicode.props.UcdPropertyValues.Grapheme_Cluster_Break_Values; import org.unicode.props.UcdPropertyValues.Script_Values; import org.unicode.props.UnicodeProperty; +import org.unicode.text.utility.Settings; import org.unicode.text.utility.Utility; public class TestCodeInvariants { @@ -70,6 +71,73 @@ void testBlockRanges() { } @Test + void testCasePairStability() { + // https://www.unicode.org/policies/stability_policy.html#Case_Pair + // Also test the simple counterpart per UTC-174-A11; see + // https://www.unicode.org/L2/L2023/23005.htm#174-A11 and + // https://github.com/unicode-org/unicodetools/issues/386. + IndexUnicodeProperties previous = IndexUnicodeProperties.make(Settings.lastVersion); + UnicodeSet oldAssigned = + previous.getProperty(UcdProperty.General_Category) + .getSet("Cn") + .complement() + .freeze(); + checkCasePairStability( + previous, + oldAssigned, + UcdProperty.Lowercase_Mapping, + UcdProperty.Uppercase_Mapping); + checkCasePairStability( + previous, + oldAssigned, + UcdProperty.Simple_Lowercase_Mapping, + UcdProperty.Simple_Uppercase_Mapping); + } + + private static void checkCasePairStability( + IndexUnicodeProperties previous, + UnicodeSet oldAssigned, + UcdProperty lowercaseProperty, + UcdProperty uppercaseProperty) { + UnicodeProperty oldLowercase = previous.getProperty(lowercaseProperty); + UnicodeProperty oldUppercase = previous.getProperty(uppercaseProperty); + UnicodeProperty lowercase = IUP.getProperty(lowercaseProperty); + UnicodeProperty uppercase = IUP.getProperty(uppercaseProperty); + for (String source : oldAssigned) { + // Comparing the other member of the case pair (or null) catches lost, gained, and + // changed pairs. + assertEquals( + casePairTarget(source, oldLowercase, oldUppercase, oldAssigned), + casePairTarget(source, lowercase, uppercase, oldAssigned), + () -> + lowercaseProperty + + " / " + + uppercaseProperty + + " case pair changed for U+" + + Utility.hex(source)); + } + } + + private static String casePairTarget( + String source, + UnicodeProperty lowercase, + UnicodeProperty uppercase, + UnicodeSet oldAssigned) { + String target = lowercase.getValue(source.codePointAt(0)); + // Both members of the case pair must be distinct, single characters assigned in the old + // version. + // Pairs involving newly encoded characters are allowed. + if (target == null + || source.equals(target) + || target.codePointCount(0, target.length()) != 1 + || !oldAssigned.contains(target) + || !source.equals(uppercase.getValue(target.codePointAt(0)))) { + return null; + } + return target; + } + + @Test public void testScriptExtensions() { int testResult = TEST_PASS;