xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestAnnotations.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.unittest;
2 
3 import com.google.common.base.CharMatcher;
4 import com.google.common.base.Joiner;
5 import com.google.common.base.Splitter;
6 import com.google.common.collect.ImmutableSet;
7 import com.google.common.collect.ImmutableSortedSet;
8 import com.google.common.collect.Multimap;
9 import com.google.common.collect.TreeMultimap;
10 import com.ibm.icu.dev.util.UnicodeMap;
11 import com.ibm.icu.impl.Row;
12 import com.ibm.icu.impl.Row.R3;
13 import com.ibm.icu.impl.Row.R4;
14 import com.ibm.icu.impl.Utility;
15 import com.ibm.icu.text.Collator;
16 import com.ibm.icu.text.UnicodeSet;
17 import java.io.File;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.LinkedHashSet;
25 import java.util.Locale;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29 import java.util.TreeMap;
30 import java.util.TreeSet;
31 import java.util.regex.Pattern;
32 import org.unicode.cldr.test.CoverageLevel2;
33 import org.unicode.cldr.util.Annotations;
34 import org.unicode.cldr.util.Annotations.AnnotationSet;
35 import org.unicode.cldr.util.CLDRConfig;
36 import org.unicode.cldr.util.CLDRFile;
37 import org.unicode.cldr.util.CLDRPaths;
38 import org.unicode.cldr.util.CldrUtility;
39 import org.unicode.cldr.util.Emoji;
40 import org.unicode.cldr.util.Factory;
41 import org.unicode.cldr.util.Level;
42 import org.unicode.cldr.util.Pair;
43 import org.unicode.cldr.util.PathHeader;
44 import org.unicode.cldr.util.PathHeader.PageId;
45 import org.unicode.cldr.util.SimpleFactory;
46 import org.unicode.cldr.util.SupplementalDataInfo;
47 import org.unicode.cldr.util.XListFormatter;
48 import org.unicode.cldr.util.XListFormatter.ListTypeLength;
49 import org.unicode.cldr.util.XPathParts;
50 
51 public class TestAnnotations extends TestFmwkPlus {
52     private static final String APPS_EMOJI_DIRECTORY =
53             CLDRPaths.BASE_DIRECTORY + "/tools/cldr-apps/src/main/webapp/images/emoji";
54     private static final boolean DEBUG = false;
55 
main(String[] args)56     public static void main(String[] args) {
57         new TestAnnotations().run(args);
58     }
59 
60     enum Containment {
61         contains,
62         empty,
63         not_contains
64     }
65 
TestBasic()66     public void TestBasic() {
67         String[][] tests = {
68             {"en", "[\u2650]", "contains", "sagitarius", "zodiac"},
69             {"en", "[\u0020]", "empty"},
70             {"en", "[\u2651]", "not_contains", "foobar"},
71         };
72         for (String[] test : tests) {
73             UnicodeMap<Annotations> data = Annotations.getData(test[0]);
74             UnicodeSet us = new UnicodeSet(test[1]);
75             Set<String> annotations = new LinkedHashSet<>();
76             Containment contains = Containment.valueOf(test[2]);
77             for (int i = 3; i < test.length; ++i) {
78                 annotations.add(test[i]);
79             }
80             for (String s : us) {
81                 Set<String> set = data.get(s).getKeywords();
82                 if (set == null) {
83                     set = Collections.emptySet();
84                 }
85                 switch (contains) {
86                     case contains:
87                         if (Collections.disjoint(set, annotations)) {
88                             LinkedHashSet<String> temp = new LinkedHashSet<>(annotations);
89                             temp.removeAll(set);
90                             assertEquals("Missing items", Collections.EMPTY_SET, temp);
91                         }
92                         break;
93                     case not_contains:
94                         if (!Collections.disjoint(set, annotations)) {
95                             LinkedHashSet<String> temp = new LinkedHashSet<>(annotations);
96                             temp.retainAll(set);
97                             assertEquals("Extra items", Collections.EMPTY_SET, temp);
98                         }
99                         break;
100                     case empty:
101                         assertEquals("mismatch", Collections.emptySet(), set);
102                         break;
103                 }
104             }
105         }
106     }
107 
108     final AnnotationSet eng = Annotations.getDataSet("en");
109 
TestNames()110     public void TestNames() {
111         String[][] tests = { // the expected value for keywords can use , as well as |.
112             {"����", "man: light skin tone", "adult | man | light skin tone"},
113             {"��‍♂️", "man: blond hair", "blond, blond-haired man, hair, man, man: blond hair"},
114             {
115                 "����‍♂️",
116                 "man: light skin tone, blond hair",
117                 "blond, blond-haired man, hair, man, man: blond hair, light skin tone, blond hair"
118             },
119             {"��‍��", "man: red hair", "adult | man | red hair"},
120             {
121                 "����‍��",
122                 "man: light skin tone, red hair",
123                 "adult | man | light skin tone| red hair"
124             },
125             {"����", "flag: European Union", "flag"},
126             {"#️⃣", "keycap: #", "keycap"},
127             {"9️⃣", "keycap: 9", "keycap"},
128             {"��", "kiss", "couple | kiss"},
129             {"��‍❤️‍��‍��", "kiss: woman, woman", "couple | kiss | woman"},
130             {"��", "couple with heart", "couple | couple with heart | love"},
131             {
132                 "��‍❤️‍��",
133                 "couple with heart: woman, woman",
134                 "couple | couple with heart | love | woman"
135             },
136             {"��", "family", "family"},
137             {"��‍��‍��", "family: woman, woman, girl", "family | woman | girl"},
138             {"����", "boy: light skin tone", "boy | young | light skin tone"},
139             {"����", "woman: dark skin tone", "adult | woman | dark skin tone"},
140             {"��‍⚖", "man judge", "judge | justice | law | man | scales"},
141             {
142                 "����‍⚖",
143                 "man judge: dark skin tone",
144                 "judge | justice | law | man | scales | dark skin tone"
145             },
146             {"��‍⚖", "woman judge", "judge | justice | law | scales | woman"},
147             {
148                 "����‍⚖",
149                 "woman judge: medium-light skin tone",
150                 "judge | justice | law | scales | woman | medium-light skin tone"
151             },
152             {"��", "police officer", "cop | officer | police"},
153             {"����", "police officer: dark skin tone", "cop | officer | police | dark skin tone"},
154             {"��‍♂️", "man police officer", "cop | man | officer | police"},
155             {
156                 "����‍♂️",
157                 "man police officer: medium-light skin tone",
158                 "cop | man | officer | police | medium-light skin tone"
159             },
160             {"��‍♀️", "woman police officer", "cop | officer | police | woman"},
161             {
162                 "����‍♀️",
163                 "woman police officer: dark skin tone",
164                 "cop | officer | police | woman | dark skin tone"
165             },
166             {"��", "person biking", "bicycle | biking | cyclist | person biking"},
167             {
168                 "����",
169                 "person biking: dark skin tone",
170                 "bicycle | biking | cyclist | person biking | dark skin tone"
171             },
172             {"��‍♂️", "man biking", "bicycle | biking | cyclist | man"},
173             {
174                 "����‍♂️",
175                 "man biking: dark skin tone",
176                 "bicycle | biking | cyclist | man | dark skin tone"
177             },
178             {"��‍♀️", "woman biking", "bicycle | biking | cyclist | woman"},
179             {
180                 "����‍♀️",
181                 "woman biking: dark skin tone",
182                 "bicycle | biking | cyclist | woman | dark skin tone"
183             },
184         };
185 
186         Splitter BAR = Splitter.on(CharMatcher.anyOf("|,")).trimResults();
187         boolean ok = true;
188         for (String[] test : tests) {
189             String emoji = test[0];
190             String expectedName = test[1];
191             Set<String> expectedKeywords = new HashSet<>(BAR.splitToList(test[2]));
192             final String shortName = eng.getShortName(emoji);
193             final Set<String> keywords = eng.getKeywords(emoji);
194             ok &= assertEquals("short name for " + emoji, expectedName, shortName);
195             ok &= assertEquals("keywords for " + emoji, expectedKeywords, keywords);
196         }
197         if (!ok) {
198             System.out.println("Possible replacement, but check");
199             for (String[] test : tests) {
200                 String emoji = test[0];
201                 final String shortName = eng.getShortName(emoji);
202                 final Set<String> keywords = eng.getKeywords(emoji);
203                 System.out.println(
204                         "{\""
205                                 + emoji
206                                 + "\",\""
207                                 + shortName
208                                 + "\",\""
209                                 + Joiner.on(" | ").join(keywords)
210                                 + "\"},");
211             }
212         }
213     }
214 
215     static final UnicodeSet symbols =
216             new UnicodeSet(Emoji.EXTRA_SYMBOL_MINOR_CATEGORIES.keySet()).freeze();
217     /** The English name should line up with the emoji-test.txt file */
TestNamesVsEmojiData()218     public void TestNamesVsEmojiData() {
219         for (Entry<String, Annotations> s : eng.getExplicitValues().entrySet()) {
220             String emoji = s.getKey();
221             Annotations annotations = s.getValue();
222             String name = Emoji.getName(emoji);
223             String annotationName = annotations.getShortName();
224             if (!symbols.contains(emoji) && !emoji.contains("��")) {
225                 assertEquals(emoji + " (en.xml vs. emoji-test.txt)", name, annotationName);
226             }
227         }
228     }
229 
TestCategories()230     public void TestCategories() {
231         if (DEBUG) System.out.println();
232 
233         TreeSet<R4<PageId, Long, String, R3<String, String, String>>> sorted = new TreeSet<>();
234         for (Entry<String, Annotations> s : eng.getExplicitValues().entrySet()) {
235             String emoji = s.getKey();
236             Annotations annotations = s.getValue();
237             final String rawCategory = Emoji.getMajorCategory(emoji);
238             PageId majorCategory = PageId.forString(rawCategory);
239             if (majorCategory == PageId.Symbols) {
240                 majorCategory = PageId.EmojiSymbols;
241             }
242             String minorCategory = Emoji.getMinorCategory(emoji);
243             long emojiOrder = Emoji.getEmojiToOrder(emoji);
244             R3<String, String, String> row2 =
245                     Row.of(
246                             emoji,
247                             annotations.getShortName(),
248                             Joiner.on(" | ").join(annotations.getKeywords()));
249             R4<PageId, Long, String, R3<String, String, String>> row =
250                     Row.of(majorCategory, emojiOrder, minorCategory, row2);
251             sorted.add(row);
252         }
253         for (R4<PageId, Long, String, R3<String, String, String>> row : sorted) {
254             PageId majorCategory = row.get0();
255             Long emojiOrder = row.get1();
256             String minorCategory = row.get2();
257             R3<String, String, String> row2 = row.get3();
258             String emoji = row2.get0();
259             String shortName = row2.get1();
260             String keywords = row2.get2();
261             if (DEBUG)
262                 System.out.println(
263                         majorCategory
264                                 + "\t"
265                                 + emojiOrder
266                                 + "\t"
267                                 + minorCategory
268                                 + "\t"
269                                 + emoji
270                                 + "\t"
271                                 + shortName
272                                 + "\t"
273                                 + keywords);
274         }
275     }
276 
TestUniqueness()277     public void TestUniqueness() {
278         if (logKnownIssue(
279                 "CLDR-16947", "skip duplicate TestUniqueness in favor of CheckDisplayCollisions")) {
280             return;
281         }
282         Set<String> locales = new TreeSet<>();
283         locales.add("en");
284         locales.addAll(Annotations.getAvailable());
285         locales.remove("root");
286         /*
287          * Note: "problems" here is a work-around for what appears to be a deficiency
288          * in the function sourceLocation, involving the call stack. Seemingly sourceLocation
289          * can't handle the "->" notation used for parallelStream().forEach() if
290          * uniquePerLocale calls errln directly.
291          */
292         Set<String> problems = new HashSet<>();
293         locales.parallelStream().forEach(locale -> uniquePerLocale(locale, problems));
294         if (!problems.isEmpty()) {
295             problems.forEach(s -> errln(s));
296         }
297     }
298 
uniquePerLocale(String locale, Set<String> problems)299     private void uniquePerLocale(String locale, Set<String> problems) {
300         logln("uniqueness: " + locale);
301         Multimap<String, String> nameToEmoji = TreeMultimap.create();
302         AnnotationSet data = Annotations.getDataSet(locale);
303         for (String emoji : Emoji.getAllRgi()) {
304             String name = data.getShortName(emoji);
305             if (name == null) {
306                 continue;
307             }
308             if (name.contains(CldrUtility.INHERITANCE_MARKER)) {
309                 throw new IllegalArgumentException(
310                         CldrUtility.INHERITANCE_MARKER + " in name of " + emoji + " in " + locale);
311             }
312             nameToEmoji.put(name, emoji);
313         }
314         Multimap<String, String> duplicateNameToEmoji = null;
315         for (Entry<String, Collection<String>> entry : nameToEmoji.asMap().entrySet()) {
316             String name = entry.getKey();
317             Collection<String> emojis = entry.getValue();
318             if (emojis.size() > 1) {
319                 synchronized (problems) {
320                     if (problems.add(
321                             "Duplicate name in "
322                                     + locale
323                                     + ": “"
324                                     + name
325                                     + "” for "
326                                     + Joiner.on(" & ").join(emojis))) {
327                         int debug = 0;
328                     }
329                 }
330                 if (duplicateNameToEmoji == null) {
331                     duplicateNameToEmoji = TreeMultimap.create();
332                 }
333                 duplicateNameToEmoji.putAll(name, emojis);
334             }
335         }
336         if (isVerbose() && duplicateNameToEmoji != null && !duplicateNameToEmoji.isEmpty()) {
337             System.out.println("\nCollisions");
338             for (Entry<String, String> entry : duplicateNameToEmoji.entries()) {
339                 String emoji = entry.getValue();
340                 System.out.println(locale + "\t" + eng.getShortName(emoji) + "\t" + emoji);
341             }
342         }
343     }
344 
testAnnotationPaths()345     public void testAnnotationPaths() {
346         assertTrue("", Emoji.getNonConstructed().contains("®"));
347         Factory factoryAnnotations = SimpleFactory.make(CLDRPaths.ANNOTATIONS_DIRECTORY, ".*");
348         for (String locale : Arrays.asList("en", "root")) {
349             CLDRFile enAnnotations = factoryAnnotations.make(locale, false);
350             //               //ldml/annotations/annotation[@cp="��"][@type="tts"]
351             Set<String> annotationPaths =
352                     enAnnotations.getPaths(
353                             "//ldml/anno",
354                             Pattern.compile("//ldml/annotations/annotation.*tts.*").matcher(""),
355                             new TreeSet<>());
356             Set<String> annotationPathsExpected = Emoji.getNamePaths();
357             if (!checkAMinusBIsC(
358                     "(" + locale + ".xml - Emoji.getNamePaths)",
359                     annotationPaths,
360                     annotationPathsExpected,
361                     Collections.<String>emptySet())) {
362                 System.out.println("Check Emoji.SPECIALS");
363             }
364             checkAMinusBIsC(
365                     "(Emoji.getNamePaths - " + locale + ".xml)",
366                     annotationPathsExpected,
367                     annotationPaths,
368                     Collections.<String>emptySet());
369         }
370     }
371 
testEmojiImages()372     public void testEmojiImages() {
373         if (CLDRPaths.ANNOTATIONS_DIRECTORY.contains("cldr-staging/production/")) {
374             return; // don't bother checking production for this: the images are only in main, not
375             // production
376         }
377         Factory factoryAnnotations = SimpleFactory.make(CLDRPaths.ANNOTATIONS_DIRECTORY, ".*");
378         CLDRFile enAnnotations = factoryAnnotations.make("en", false);
379 
380         String emojiImageDir = APPS_EMOJI_DIRECTORY;
381         for (String emoji : Emoji.getNonConstructed()) {
382             String noVs = emoji.replace(Emoji.EMOJI_VARIANT, "");
383 
384             // example: emoji_1f1e7_1f1ec.png
385             String fileName =
386                     "emoji_" + Utility.hex(noVs, 4, "_").toLowerCase(Locale.ENGLISH) + ".png";
387             File file = new File(emojiImageDir, fileName);
388 
389             if (!file.exists() && !fileName.endsWith("_200d_27a1.png")) {
390                 String name =
391                         enAnnotations.getStringValue(
392                                 "//ldml/annotations/annotation[@cp=\""
393                                         + noVs
394                                         + "\"][@type=\"tts\"]");
395                 errln(fileName + " missing; " + name);
396             }
397         }
398     }
399 
400     /** Check that the order info, categories, and collation are consistent. */
testEmojiOrdering()401     public void testEmojiOrdering() {
402         // load an array for sorting
403         // and test that every order value maps to exactly one emoji
404         Map<String, String> minorToMajor = new HashMap<>();
405         Map<Long, String> orderToEmoji = new TreeMap<>();
406         Collator col = CLDRConfig.getInstance().getCollatorRoot();
407 
408         for (String emoji : Emoji.getNonConstructed()) {
409             Long emojiOrder = Emoji.getEmojiToOrder(emoji);
410             if (DEBUG) {
411                 String minor = Emoji.getMinorCategory(emoji);
412                 System.out.println(emojiOrder + "\t" + emoji + "\t" + minor);
413             }
414             String oldEmoji = orderToEmoji.get(emojiOrder);
415             if (oldEmoji == null) {
416                 orderToEmoji.put(emojiOrder, emoji);
417             } else {
418                 errln("single order value with different emoji" + emoji + " ≠ " + oldEmoji);
419             }
420         }
421         Set<String> majorsSoFar = new TreeSet<>();
422         String lastMajor = "";
423         Set<String> minorsSoFar = new TreeSet<>();
424         String lastMinor = "";
425         Set<String> lastMajorGroup = new LinkedHashSet<>();
426         Set<String> lastMinorGroup = new LinkedHashSet<>();
427         String lastEmoji = "";
428         long lastEmojiOrdering = -1L;
429         for (Entry<Long, String> entry : orderToEmoji.entrySet()) {
430             String emoji = entry.getValue();
431             Long emojiOrdering = entry.getKey();
432             // check against collation
433             if (col.compare(emoji, lastEmoji) <= 0) {
434                 String name = eng.getShortName(emoji);
435                 String lastName = eng.getShortName(lastEmoji);
436                 int errorType = ERR;
437                 if (logKnownIssue("CLDR-16394", "slightly out of order")) {
438                     errorType = WARN;
439                 }
440                 msg(
441                         "Out of order: "
442                                 + lastEmoji
443                                 + " ("
444                                 + lastEmojiOrdering
445                                 + ") "
446                                 + lastName
447                                 + " > "
448                                 + emoji
449                                 + " ("
450                                 + emojiOrdering
451                                 + ") "
452                                 + name,
453                         errorType,
454                         true,
455                         true);
456             }
457 
458             String major = Emoji.getMajorCategory(emoji);
459             String minor = Emoji.getMinorCategory(emoji);
460             if (isVerbose()) {
461                 System.out.println(major + "\t" + minor + "\t" + emoji);
462             }
463             String oldMajor = minorToMajor.get(minor);
464             // never get major1:minor1 and major2:minor1
465             if (oldMajor == null) {
466                 minorToMajor.put(minor, major);
467             } else {
468                 assertEquals(
469                         minor + " maps to different majors for " + Utility.hex(emoji),
470                         oldMajor,
471                         major);
472             }
473             // never get major1 < major2 < major1
474             if (!major.equals(lastMajor)) {
475                 // System.out.println(lastMajor + "\t" + lastMajorGroup);
476 
477                 //                if (majorsSoFar.contains(major)) {
478                 //                    errln("Non-contiguous majors: " + major + " <… " + lastMajor +
479                 // " < " + major);
480                 //                }
481                 majorsSoFar.add(major);
482                 lastMajor = major;
483                 lastMajorGroup.clear();
484                 lastMajorGroup.add(emoji); // add emoji with different cat
485             } else {
486                 lastMajorGroup.add(emoji);
487             }
488             // never get minor1 < minor2 < minor1
489             if (!minor.equals(lastMinor)) {
490                 if (DEBUG) System.out.println(lastMinor + "\t" + lastMinorGroup);
491                 if (minorsSoFar.contains(minor)) {
492                     errln("Non-contiguous minors: " + minor + " <… " + lastMinor + " < " + minor);
493                 }
494                 minorsSoFar.add(minor);
495                 lastMinor = minor;
496                 lastMinorGroup.clear();
497                 lastMinorGroup.add(emoji); // add emoji with different cat
498             } else {
499                 lastMinorGroup.add(emoji);
500             }
501             lastEmoji = emoji;
502             lastEmojiOrdering = emojiOrdering;
503         }
504         if (DEBUG) System.out.println(lastMinor + "\t" + lastMinorGroup);
505     }
506 
testSuperfluousAnnotationPaths()507     public void testSuperfluousAnnotationPaths() {
508         if (CLDRPaths.ANNOTATIONS_DIRECTORY.contains("cldr-staging/production/")) {
509             return; // don't bother checking production for this: root is empty
510         }
511         Factory factoryAnnotations = SimpleFactory.make(CLDRPaths.ANNOTATIONS_DIRECTORY, ".*");
512         ImmutableSet<String> rootPaths =
513                 ImmutableSortedSet.copyOf(
514                         factoryAnnotations.make("root", false).iterator("//ldml/annotations/"));
515 
516         CLDRFile englishAnnotations = factoryAnnotations.make("en", false);
517         ImmutableSet<String> englishPaths =
518                 ImmutableSortedSet.copyOf(englishAnnotations.iterator("//ldml/annotations/"));
519 
520         Set<String> superfluous2 = setDifference(rootPaths, englishPaths);
521         assertTrue("en contains root", superfluous2.isEmpty());
522         if (!superfluous2.isEmpty()) {
523             for (String path : superfluous2) {
524                 //              XPathParts parts = XPathParts.getFrozenInstance(path);
525                 //              String emoji = parts.getAttributeValue(-1, "cp");
526                 System.out.println("locale=en; action=add; path=" + path + "; value=XXX");
527             }
528         }
529 
530         Set<String> allSuperfluous = new TreeSet<>();
531         for (String locale : factoryAnnotations.getAvailable()) {
532             ImmutableSet<String> currentPaths =
533                     ImmutableSortedSet.copyOf(
534                             factoryAnnotations.make(locale, false).iterator("//ldml/annotations/"));
535             Set<String> superfluous = setDifference(currentPaths, rootPaths);
536             if (!assertTrue("root contains " + locale, superfluous.isEmpty())) {
537                 int debug = 0;
538             }
539             allSuperfluous.addAll(superfluous);
540             for (String s : currentPaths) {
541                 if (s.contains("\uFE0F")) {
542                     errln("Contains FE0F: " + s);
543                     break;
544                 }
545             }
546         }
547         // get items to fix
548         if (!allSuperfluous.isEmpty()) {
549             for (String path : allSuperfluous) {
550                 //                XPathParts parts = XPathParts.getFrozenInstance(path);
551                 //                String emoji = parts.getAttributeValue(-1, "cp");
552                 System.out.println("locale=/.*/; action=delete; path=" + path);
553             }
554         }
555     }
556 
setDifference(ImmutableSet<String> a, ImmutableSet<String> b)557     private Set<String> setDifference(ImmutableSet<String> a, ImmutableSet<String> b) {
558         Set<String> superfluous = new LinkedHashSet<>(a);
559         superfluous.removeAll(b);
560         return superfluous;
561     }
562 
checkAMinusBIsC(String title, Set<String> a, Set<String> b, Set<String> c)563     private boolean checkAMinusBIsC(String title, Set<String> a, Set<String> b, Set<String> c) {
564         Set<String> aMb = new TreeSet<>(a);
565         aMb.removeAll(b);
566         for (Iterator<String> it = aMb.iterator(); it.hasNext(); ) {
567             String item = it.next();
568             if (symbols.containsSome(item)) {
569                 it.remove();
570             }
571         }
572         return assertEquals(title + " (" + aMb.size() + ")", c, aMb);
573     }
574 
testListFormatter()575     public void testListFormatter() {
576         Object[][] tests = {
577             {"en", ListTypeLength.NORMAL, "ABC", "A, B, and C"},
578             {"en", ListTypeLength.AND_SHORT, "ABC", "A, B, & C"},
579             {"en", ListTypeLength.AND_NARROW, "ABC", "A, B, C"},
580             {"en", ListTypeLength.OR_WIDE, "ABC", "A, B, or C"}
581         };
582         Factory factory = CLDRConfig.getInstance().getCldrFactory();
583         for (Object[] test : tests) {
584             CLDRFile cldrFile = factory.make((String) (test[0]), true);
585             ListTypeLength listTypeLength = (ListTypeLength) (test[1]);
586             String expected = (String) test[3];
587             XListFormatter xlistFormatter = new XListFormatter(cldrFile, listTypeLength);
588             String source = (String) test[2];
589             String actual = xlistFormatter.formatCodePoints(source);
590             assertEquals(test[0] + ", " + listTypeLength + ", " + source, expected, actual);
591         }
592     }
593 
testCoverage()594     public void testCoverage() {
595         UnicodeMap<Level> levels = new UnicodeMap<>();
596         for (String minorCategory : Emoji.getMinorCategoriesWithExtras()) {
597             for (String s : Emoji.getEmojiInMinorCategoriesWithExtras(minorCategory)) {
598                 if (s.contentEquals("‾")) {
599                     int debug = 0;
600                 }
601                 CoverageLevel2 coverageLevel =
602                         CoverageLevel2.getInstance(SupplementalDataInfo.getInstance(), "en");
603                 final String pathKeyword = "//ldml/annotations/annotation[@cp=\"" + s + "\"]";
604                 final String pathName = pathKeyword + "[@type=\"tts\"]";
605                 Level levelKeyword = coverageLevel.getLevel(pathKeyword);
606                 Level levelName = coverageLevel.getLevel(pathName);
607                 assertEquals(s, levelName, levelKeyword);
608                 levels.put(s, levelName);
609             }
610         }
611         for (Level level : Level.values()) {
612             UnicodeSet us = levels.getSet(level);
613             getLogger().fine(level + "\t" + us.size());
614             switch (level) {
615                 case MODERN:
616                     assertNotEquals(level.toString(), 0, us.size());
617                     break;
618                 default:
619                     assertEquals(level.toString(), 0, us.size());
620                     break;
621             }
622         }
623     }
624 
625     static final UnicodeSet allRgiNoES = Emoji.getAllRgiNoES();
626     static final UnicodeSet punctuation = new UnicodeSet("[:P:]").freeze();
627     static final UnicodeSet mathSymbols = new UnicodeSet("[:Sm:]").freeze();
628     static final UnicodeSet otherSymbols = new UnicodeSet("[^[:Sm:][:P:]]").freeze();
629 
testSymbols()630     public void testSymbols() {
631         CLDRFile root = CLDRConfig.getInstance().getAnnotationsFactory().make("root", false);
632         UnicodeMap<String> expectedMap =
633                 new UnicodeMap<String>()
634                         .putAll(punctuation, "Punctuation")
635                         .putAll(mathSymbols, "Math Symbols")
636                         .putAll(otherSymbols, "Other Symbols")
637                         .freeze();
638         Set<String> nonEmojiPages = expectedMap.values();
639         UnicodeMap<Pair<String, String>> failures = new UnicodeMap<>();
640         PathHeader.Factory phf = PathHeader.getFactory();
641         for (String path : root) {
642             XPathParts parts = XPathParts.getFrozenInstance(path);
643             String cp = parts.getAttributeValue(-1, "cp");
644             if (cp == null) {
645                 continue; // non-annotation line
646             }
647             PathHeader ph = phf.fromPath(path);
648             PathHeader.SectionId sectionId = ph.getSectionId();
649             assertEquals("Section for " + cp, PathHeader.SectionId.Characters, sectionId);
650             PageId pageId = ph.getPageId();
651             final String actual = pageId.toString();
652 
653             // collect all the failures rather than having a long list of errors
654 
655             if (allRgiNoES.contains(cp)) { // check emoji
656                 if (nonEmojiPages.contains(actual)) {
657                     failures.put(cp, Pair.of("«Emoji-Page»", actual));
658                 } else if (actual.equals("Symbols2")) {
659                     failures.put(cp, Pair.of("Emoji Symbols", actual));
660                 }
661             } else {
662                 String expected = expectedMap.get(cp);
663                 if (!actual.equals(expected)) {
664                     failures.put(cp, Pair.of(expected, actual));
665                 }
666             }
667         }
668         if (!failures.isEmpty()) {
669             for (Pair<String, String> value : ImmutableSortedSet.copyOf(failures.values())) {
670                 UnicodeSet uset = failures.getSet(value);
671                 errln(
672                         "Mismatch in "
673                                 + uset.size()
674                                 + " cases: expected="
675                                 + value.getFirst()
676                                 + " actual="
677                                 + value.getSecond()
678                                 + "\n"
679                                 + uset.toPattern(false));
680             }
681         }
682     }
683 }
684