1 package org.unicode.cldr.unittest; 2 3 import com.google.common.collect.ImmutableList; 4 import com.ibm.icu.dev.test.TestFmwk; 5 import com.ibm.icu.impl.Row.R2; 6 import com.ibm.icu.impl.Row.R5; 7 import com.ibm.icu.text.UnicodeSet; 8 import java.util.ArrayList; 9 import java.util.Arrays; 10 import java.util.Collection; 11 import java.util.Collections; 12 import java.util.HashMap; 13 import java.util.HashSet; 14 import java.util.LinkedHashMap; 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Map.Entry; 18 import java.util.Set; 19 import java.util.TreeMap; 20 import java.util.TreeSet; 21 import java.util.regex.Matcher; 22 import org.unicode.cldr.test.CheckCLDR; 23 import org.unicode.cldr.test.CheckCLDR.CheckStatus; 24 import org.unicode.cldr.test.CheckCLDR.CheckStatus.Subtype; 25 import org.unicode.cldr.test.CheckCLDR.InputMethod; 26 import org.unicode.cldr.test.CheckCLDR.Options; 27 import org.unicode.cldr.test.CheckCLDR.Phase; 28 import org.unicode.cldr.test.CheckCLDR.StatusAction; 29 import org.unicode.cldr.test.CheckConsistentCasing; 30 import org.unicode.cldr.test.CheckDates; 31 import org.unicode.cldr.test.CheckForExemplars; 32 import org.unicode.cldr.test.CheckNames; 33 import org.unicode.cldr.test.CheckNew; 34 import org.unicode.cldr.test.OutdatedPaths; 35 import org.unicode.cldr.test.SubmissionLocales; 36 import org.unicode.cldr.test.TestCache; 37 import org.unicode.cldr.test.TestCache.TestResultBundle; 38 import org.unicode.cldr.tool.LikelySubtags; 39 import org.unicode.cldr.util.CLDRConfig; 40 import org.unicode.cldr.util.CLDRFile; 41 import org.unicode.cldr.util.CLDRInfo.CandidateInfo; 42 import org.unicode.cldr.util.CLDRInfo.PathValueInfo; 43 import org.unicode.cldr.util.CLDRInfo.UserInfo; 44 import org.unicode.cldr.util.CLDRLocale; 45 import org.unicode.cldr.util.Counter; 46 import org.unicode.cldr.util.DayPeriodInfo; 47 import org.unicode.cldr.util.DayPeriodInfo.DayPeriod; 48 import org.unicode.cldr.util.DayPeriodInfo.Type; 49 import org.unicode.cldr.util.Factory; 50 import org.unicode.cldr.util.GrammarInfo; 51 import org.unicode.cldr.util.LanguageTagParser; 52 import org.unicode.cldr.util.Level; 53 import org.unicode.cldr.util.Organization; 54 import org.unicode.cldr.util.Pair; 55 import org.unicode.cldr.util.PathHeader; 56 import org.unicode.cldr.util.PathHeader.SurveyToolStatus; 57 import org.unicode.cldr.util.PatternPlaceholders; 58 import org.unicode.cldr.util.PatternPlaceholders.PlaceholderInfo; 59 import org.unicode.cldr.util.PatternPlaceholders.PlaceholderStatus; 60 import org.unicode.cldr.util.SimpleXMLSource; 61 import org.unicode.cldr.util.StandardCodes; 62 import org.unicode.cldr.util.StandardCodes.LstrType; 63 import org.unicode.cldr.util.StringId; 64 import org.unicode.cldr.util.SupplementalDataInfo; 65 import org.unicode.cldr.util.Validity; 66 import org.unicode.cldr.util.Validity.Status; 67 import org.unicode.cldr.util.VoteResolver.VoterInfo; 68 import org.unicode.cldr.util.XMLSource; 69 70 public class TestCheckCLDR extends TestFmwk { 71 72 private static final boolean SHOW_LIMITED = 73 System.getProperty("TestCheckCLDR:SHOW_LIMITED") != null; 74 75 static CLDRConfig testInfo = CLDRConfig.getInstance(); 76 private final Set<String> eightPointLocales = 77 new TreeSet<>( 78 Arrays.asList( 79 "ar ca cs da de el es fi fr he hi hr hu id it ja ko lt lv nl no pl pt pt_PT ro ru sk sl sr sv th tr uk vi zh zh_Hant" 80 .split(" "))); 81 main(String[] args)82 public static void main(String[] args) { 83 new TestCheckCLDR().run(args); 84 } 85 86 static class MyCheckCldr extends org.unicode.cldr.test.CheckCLDR { doTest()87 CheckStatus doTest() { 88 try { 89 throw new IllegalArgumentException("hi"); 90 } catch (Exception e) { 91 return new CheckStatus() 92 .setCause(this) 93 .setMainType(CheckStatus.warningType) 94 .setSubtype(Subtype.abbreviatedDateFieldTooWide) 95 .setMessage("An exception {0}, and a number {1}", e, 1.5); 96 } 97 } 98 99 @Override handleCheck( String path, String fullPath, String value, Options options, List<CheckStatus> result)100 public CheckCLDR handleCheck( 101 String path, 102 String fullPath, 103 String value, 104 Options options, 105 List<CheckStatus> result) { 106 return null; 107 } 108 } 109 TestExceptions()110 public void TestExceptions() { 111 CheckStatus status = new MyCheckCldr().doTest(); 112 Exception[] exceptions = status.getExceptionParameters(); 113 assertEquals("Number of exceptions:", exceptions.length, 1); 114 assertEquals("Exception message:", "hi", exceptions[0].getMessage()); 115 logln(Arrays.asList(exceptions[0].getStackTrace()).toString()); 116 logln(status.getMessage()); 117 } 118 TestCheckConsistentCasing()119 public static void TestCheckConsistentCasing() { 120 CheckConsistentCasing c = new CheckConsistentCasing(testInfo.getCldrFactory()); 121 Map<String, String> options = new LinkedHashMap<>(); 122 List<CheckStatus> possibleErrors = new ArrayList<>(); 123 final CLDRFile english = testInfo.getEnglish(); 124 c.setCldrFileToCheck(english, new CheckCLDR.Options(options), possibleErrors); 125 for (String path : english) { 126 c.check( 127 path, 128 english.getFullXPath(path), 129 english.getStringValue(path), 130 new CheckCLDR.Options(options), 131 possibleErrors); 132 } 133 } 134 135 /** Test the TestCache and TestResultBundle objects */ TestTestCache()136 public void TestTestCache() { 137 String localeString = "en"; 138 CLDRLocale locale = CLDRLocale.getInstance(localeString); 139 CheckCLDR.Options checkCldrOptions = 140 new Options(locale, Phase.SUBMISSION, "default", "basic"); 141 TestCache testCache = testInfo.getCldrFactory().getTestCache(); 142 testCache.setNameMatcher(".*"); // will clear the cache 143 TestResultBundle bundle = testCache.getBundle(checkCldrOptions); 144 final CLDRFile cldrFile = testInfo.getCLDRFile(localeString, true); 145 /* 146 * Loop through the set of paths twice. The second time should be much faster. 147 * Measured times for the two passes, without pathCache in TestResultBundle, 148 * 4017 and 3293 milliseconds. With pathCache, 4125 and 46 milliseconds. 149 * That's with locale "en", all 19698 paths. Results for "fr" were similar. 150 * To save time, limit the number of paths if getInclusion() is small. 151 * A thousand paths take about half a second to loop through twice. 152 */ 153 int maxPathCount = (getInclusion() < 5) ? 1000 : 100000; 154 double[] deltaTime = {0, 0}; 155 for (int i = 0; i < 2; i++) { 156 List<CheckStatus> possibleErrors = new ArrayList<>(); 157 int pathCount = 0; 158 double startTime = System.currentTimeMillis(); 159 for (String path : cldrFile) { 160 String fullPath = cldrFile.getFullXPath(path); 161 String value = cldrFile.getStringValue(path); 162 bundle.check(fullPath, possibleErrors, value); 163 if (++pathCount == maxPathCount) { 164 break; 165 } 166 } 167 deltaTime[i] = System.currentTimeMillis() - startTime; 168 /* 169 * Expect possibleErrors to have size zero. 170 * A future enhancement of this test could modify some values to force errors, 171 * and confirm that the errors are returned identically the first and second times. 172 */ 173 assertEquals("possibleErrors, loop index " + i, possibleErrors.size(), 0); 174 } 175 /* 176 * Expect second time to be about a hundredth of first time; error if more than a tenth. 177 * On one occasion, smoketest had times 171.0 and 5.0. 178 */ 179 if (deltaTime[1] > deltaTime[0] / 10) { 180 errln( 181 "TestResultBundle cache should yield more benefit: times " 182 + deltaTime[0] 183 + " and " 184 + deltaTime[1]); 185 } 186 } 187 188 /** Test the "collisionless" error/warning messages. */ 189 public static final String INDIVIDUAL_TESTS = 190 ".*(CheckCasing|CheckCurrencies|CheckDates|CheckExemplars|CheckForCopy|CheckForExemplars|CheckMetazones|CheckNumbers)"; 191 192 static final Factory factory = testInfo.getCldrFactory(); 193 static final CLDRFile english = testInfo.getEnglish(); 194 195 private static final boolean DEBUG = true; 196 197 static final Factory cldrFactory = CLDRConfig.getInstance().getCldrFactory(); 198 static final Factory cldrFactoryWithSeed = 199 CLDRConfig.getInstance().getCommonAndSeedAndMainAndAnnotationsFactory(); 200 testPlaceholderSamples()201 public void testPlaceholderSamples() { 202 CLDRFile root = cldrFactory.make("root", true); 203 String[][] tests = { 204 {"he", "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"one\"]", "שנה"}, 205 // test edge cases 206 // locale, path, value, 0..n Subtype errors 207 {"en", "//ldml/localeDisplayNames/localeDisplayPattern/localePattern", "{0}huh?{1}"}, 208 { 209 "en", 210 "//ldml/localeDisplayNames/localeDisplayPattern/localePattern", 211 "huh?", 212 "missingPlaceholders" 213 }, 214 { 215 "en", 216 "//ldml/localeDisplayNames/localeDisplayPattern/localePattern", 217 "huh?{0}", 218 "missingPlaceholders" 219 }, 220 { 221 "en", 222 "//ldml/localeDisplayNames/localeDisplayPattern/localePattern", 223 "huh?{1}", 224 "missingPlaceholders", 225 "gapsInPlaceholderNumbers" 226 }, 227 { 228 "en", 229 "//ldml/localeDisplayNames/localeDisplayPattern/localePattern", 230 "{0}huh?{1}{2}", 231 "extraPlaceholders" 232 }, 233 { 234 "en", 235 "//ldml/localeDisplayNames/localeDisplayPattern/localePattern", 236 "{0}huh?{1}{0}", 237 "duplicatePlaceholders" 238 }, 239 { 240 "fr", 241 "//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"other\"]", 242 "Prenez la e à droite.", 243 "missingPlaceholders" 244 }, 245 { 246 "fr", 247 "//ldml/numbers/minimalPairs/ordinalMinimalPairs[@ordinal=\"other\"]", 248 "Prenez la {0}e à droite." 249 }, 250 { 251 "fr", 252 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", 253 "jours", 254 "missingPlaceholders" 255 }, 256 {"fr", "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", "{0} jours"}, 257 { 258 "cy", 259 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", 260 "ci cath", 261 "missingPlaceholders" 262 }, 263 {"cy", "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", "{0} ci"}, 264 { 265 "cy", 266 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", 267 "{0} ci, {0} cath" 268 }, 269 { 270 "pl", 271 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"accusative\"]", 272 "biernik", 273 "missingPlaceholders" 274 }, 275 { 276 "pl", 277 "//ldml/numbers/minimalPairs/caseMinimalPairs[@case=\"accusative\"]", 278 "{0} biernik" 279 }, 280 { 281 "fr", 282 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"feminine\"]", 283 "de genre féminin", 284 "missingPlaceholders" 285 }, 286 { 287 "fr", 288 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"feminine\"]", 289 "la {0}" 290 }, 291 { 292 "ar", 293 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-hour\"]/unitPattern[@count=\"one\"]", 294 "ساعة" 295 }, 296 { 297 "ar", 298 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-hour\"]/unitPattern[@count=\"one\"]", 299 "{0} ساعة" 300 }, 301 { 302 "ar", 303 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-hour\"]/unitPattern[@count=\"one\"]", 304 "{1}{0} ساعة", 305 "extraPlaceholders" 306 }, 307 {"he", "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"one\"]", "שנה"}, 308 {"he", "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"two\"]", "שנתיים"}, 309 { 310 "he", 311 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"many\"]", 312 "שנה", 313 "missingPlaceholders" 314 }, 315 { 316 "he", 317 "//ldml/numbers/minimalPairs/pluralMinimalPairs[@count=\"other\"]", 318 "שנים", 319 "missingPlaceholders" 320 }, 321 }; 322 for (String[] row : tests) { 323 String localeId = row[0]; 324 String path = row[1]; 325 String value = row[2]; 326 Set<Subtype> expected = new TreeSet<>(); 327 for (int i = 3; i < row.length; ++i) { 328 expected.add(Subtype.valueOf(row[i])); 329 } 330 List<CheckStatus> possibleErrors = new ArrayList<>(); 331 checkPathValue(root, localeId, path, value, possibleErrors); 332 Set<Subtype> actual = new TreeSet<>(); 333 for (CheckStatus item : possibleErrors) { 334 if (PatternPlaceholders.PLACEHOLDER_SUBTYPES.contains(item.getSubtype())) { 335 actual.add(item.getSubtype()); 336 } 337 } 338 if (!assertEquals(Arrays.asList(row).toString(), expected, actual)) { 339 int debug = 0; 340 } 341 } 342 } 343 checkPathValue( CLDRFile root, String localeId, String path, String value, List<CheckStatus> possibleErrors)344 public void checkPathValue( 345 CLDRFile root, 346 String localeId, 347 String path, 348 String value, 349 List<CheckStatus> possibleErrors) { 350 XMLSource localeSource = new SimpleXMLSource(localeId); 351 localeSource.putValueAtPath(path, value); 352 353 TestFactory currFactory = makeTestFactory(root, localeSource); 354 CLDRFile cldrFile = currFactory.make(localeSource.getLocaleID(), true); 355 CheckForExemplars check = new CheckForExemplars(currFactory); 356 357 Options options = new Options(); 358 check.setCldrFileToCheck(cldrFile, options, possibleErrors); 359 check.handleCheck(path, path, value, options, possibleErrors); 360 } 361 TestPlaceholders()362 public void TestPlaceholders() { 363 CheckCLDR.setDisplayInformation(english); 364 checkPlaceholders(english); 365 checkPlaceholders(factory.make("de", true)); 366 } 367 checkPlaceholders(CLDRFile cldrFileToTest)368 public void checkPlaceholders(CLDRFile cldrFileToTest) { 369 // verify that every item with {0} has a pattern in pattern 370 // placeholders, 371 // and that every one generates an error in CheckCDLR for patterns when 372 // given "?" 373 // and that every non-pattern doesn't have an error in CheckCLDR for 374 // patterns when given "?" 375 // 376 // For the following: traditional placeholders just have {0}, {1}, {2}, ... 377 // But personName namePattern placeHolders start with [a-z], then continue with 378 // [0-9a-zA-Z-]+ 379 // They need to be distinguished from non-placeholder patterns using {} in UnicodeSets 380 Matcher messagePlaceholder = CheckForExemplars.PLACEHOLDER.matcher(""); 381 PatternPlaceholders patternPlaceholders = PatternPlaceholders.getInstance(); 382 383 CheckCLDR test = CheckCLDR.getCheckAll(factory, ".*"); 384 List<CheckStatus> possibleErrors = new ArrayList<>(); 385 Options options = new Options(); 386 test.setCldrFileToCheck(cldrFileToTest, options, possibleErrors); 387 List<CheckStatus> result = new ArrayList<>(); 388 389 PathHeader.Factory pathHeaderFactory = PathHeader.getFactory(cldrFileToTest); 390 Set<PathHeader> sorted = new TreeSet<>(); 391 for (String path : cldrFileToTest.fullIterable()) { 392 sorted.add(pathHeaderFactory.fromPath(path)); 393 } 394 // test actual example with count=<digits> 395 final String testPath = 396 "//ldml/numbers/decimalFormats[@numberSystem=\"latn\"]/decimalFormatLength[@type=\"long\"]/decimalFormat[@type=\"standard\"]/pattern[@type=\"1000\"][@count=\"1\"]"; 397 sorted.add(pathHeaderFactory.fromPath(testPath)); 398 399 for (PathHeader pathHeader : sorted) { 400 String path = pathHeader.getOriginalPath(); 401 if (path.contains("/exemplarCharacters") || path.contains("/parseLenients")) { 402 // skip some paths with UnicodeSets that may include {} constructs 403 // that should not be interpreted as placeholders 404 continue; 405 } 406 String value = cldrFileToTest.getStringValue(path); 407 if (value == null) { 408 continue; 409 } 410 boolean containsMessagePattern = messagePlaceholder.reset(value).find(); 411 final Map<String, PlaceholderInfo> placeholderInfo = patternPlaceholders.get(path); 412 final PlaceholderStatus placeholderStatus = patternPlaceholders.getStatus(path); 413 if (placeholderStatus == PlaceholderStatus.DISALLOWED) { 414 if (containsMessagePattern) { 415 errln( 416 cldrFileToTest.getLocaleID() 417 + " Value (" 418 + value 419 + ") contains placeholder, but placeholder info = «" 420 + placeholderStatus 421 + "»\t" 422 + path); 423 continue; 424 } 425 } else { // not disallowed 426 if (!containsMessagePattern) { 427 // The following error seems wrong if placeholderStatus is LOCALE_DEPENDENT 428 // in which case some entries might not have placeholders; see CLDR-17820 429 errln( 430 cldrFileToTest.getLocaleID() 431 + " Value (" 432 + value 433 + ") does not contain placeholder, but placeholder info = «" 434 + placeholderStatus 435 + "»\t" 436 + path); 437 continue; 438 } 439 // get the set of placeholders 440 HashSet<String> found = new HashSet<>(); 441 do { 442 found.add(messagePlaceholder.group()); // we loaded first one up above 443 } while (messagePlaceholder.find()); 444 445 if (!found.equals(placeholderInfo.keySet())) { 446 if (placeholderStatus != PlaceholderStatus.LOCALE_DEPENDENT 447 && placeholderStatus != PlaceholderStatus.OPTIONAL) { 448 errln( 449 cldrFileToTest.getLocaleID() 450 + " Value (" 451 + value 452 + ") has different placeholders than placeholder info «" 453 + placeholderInfo.keySet() 454 + "»\t" 455 + path); 456 } 457 } else { 458 logln("placeholder info = " + placeholderInfo + "\t" + path); 459 } 460 } 461 } 462 } 463 TestFullErrors()464 public void TestFullErrors() { 465 CheckCLDR test = CheckCLDR.getCheckAll(factory, INDIVIDUAL_TESTS); 466 CheckCLDR.setDisplayInformation(english); 467 468 final String localeID = "fr"; 469 checkLocale(test, localeID, "?", null); 470 } 471 TestAllLocales()472 public void TestAllLocales() { 473 CheckCLDR test = CheckCLDR.getCheckAll(factory, INDIVIDUAL_TESTS); 474 CheckCLDR.setDisplayInformation(english); 475 Set<String> unique = new HashSet<>(); 476 LanguageTagParser ltp = new LanguageTagParser(); 477 Set<String> locales = new HashSet<>(); 478 for (String locale : getInclusion() <= 5 ? eightPointLocales : factory.getAvailable()) { 479 /* 480 * Only test locales without regions. E.g., test "pt", skip "pt_PT" 481 */ 482 if (ltp.set(locale).getRegion().isEmpty()) { 483 locales.add(locale); 484 } 485 } 486 // With ICU4J libs of 2020-03-23, using locales.parallelStream().forEach below 487 // hangs, or crashes with NPE. Likely an ICU4J issue, but we don't really need 488 // parallelStream() here anyway since we are only handling around 35 locales. 489 // (And in fact this test seems faster without it) 490 locales.forEach(locale -> checkLocale(test, locale, null, unique)); 491 logln("Count:\t" + locales.size()); 492 } 493 TestA()494 public void TestA() { 495 CheckCLDR test = CheckCLDR.getCheckAll(factory, INDIVIDUAL_TESTS); 496 CheckCLDR.setDisplayInformation(english); 497 Set<String> unique = new HashSet<>(); 498 499 checkLocale(test, "ko", null, unique); 500 } 501 checkLocale( CheckCLDR test, String localeID, String dummyValue, Set<String> unique)502 public void checkLocale( 503 CheckCLDR test, String localeID, String dummyValue, Set<String> unique) { 504 checkLocale(test, testInfo.getCLDRFile(localeID, false), dummyValue, unique); 505 } 506 checkLocale( CheckCLDR test, CLDRFile nativeFile, String dummyValue, Set<String> unique)507 public void checkLocale( 508 CheckCLDR test, CLDRFile nativeFile, String dummyValue, Set<String> unique) { 509 String localeID = nativeFile.getLocaleID(); 510 List<CheckStatus> possibleErrors = new ArrayList<>(); 511 CheckCLDR.Options options = new CheckCLDR.Options(); 512 test.setCldrFileToCheck(nativeFile, options, possibleErrors); 513 List<CheckStatus> result = new ArrayList<>(); 514 515 CLDRFile patched = nativeFile; // new CLDRFile(override); 516 PathHeader.Factory pathHeaderFactory = PathHeader.getFactory(english); 517 Set<PathHeader> sorted = new TreeSet<>(); 518 for (String path : patched) { 519 final PathHeader pathHeader = pathHeaderFactory.fromPath(path); 520 if (pathHeader != null) { 521 sorted.add(pathHeader); 522 } 523 } 524 525 logln("Checking: " + localeID); 526 UnicodeSet missingCurrencyExemplars = new UnicodeSet(); 527 UnicodeSet missingExemplars = new UnicodeSet(); 528 529 for (PathHeader pathHeader : sorted) { 530 String path = pathHeader.getOriginalPath(); 531 // override.overridePath = path; 532 final String resolvedValue = 533 dummyValue == null ? patched.getStringValueWithBailey(path) : dummyValue; 534 test.handleCheck(path, patched.getFullXPath(path), resolvedValue, options, result); 535 if (result.size() != 0) { 536 for (CheckStatus item : result) { 537 addExemplars(item, missingCurrencyExemplars, missingExemplars); 538 final String mainMessage = 539 StringId.getId(path) 540 + "\t" 541 + pathHeader 542 + "\t" 543 + english.getStringValue(path) 544 + "\t" 545 + item.getType() 546 + "\t" 547 + item.getSubtype(); 548 if (unique != null) { 549 if (unique.contains(mainMessage)) { 550 continue; 551 } else { 552 unique.add(mainMessage); 553 } 554 } 555 logln( 556 localeID 557 + "\t" 558 + mainMessage 559 + "\t" 560 + resolvedValue 561 + "\t" 562 + item.getMessage() 563 + "\t" 564 + pathHeader.getOriginalPath()); 565 } 566 } 567 } 568 if (missingCurrencyExemplars.size() != 0) { 569 logln( 570 localeID 571 + "\tMissing Exemplars (Currency):\t" 572 + missingCurrencyExemplars.toPattern(false)); 573 } 574 if (missingExemplars.size() != 0) { 575 logln(localeID + "\tMissing Exemplars:\t" + missingExemplars.toPattern(false)); 576 } 577 } 578 addExemplars( CheckStatus status, UnicodeSet missingCurrencyExemplars, UnicodeSet missingExemplars)579 void addExemplars( 580 CheckStatus status, UnicodeSet missingCurrencyExemplars, UnicodeSet missingExemplars) { 581 Object[] parameters = status.getParameters(); 582 if (parameters != null) { 583 if (parameters.length >= 1 && status.getCause().getClass() == CheckForExemplars.class) { 584 try { 585 UnicodeSet set = new UnicodeSet(parameters[0].toString()); 586 if (status.getMessage().contains("currency")) { 587 missingCurrencyExemplars.addAll(set); 588 } else { 589 missingExemplars.addAll(set); 590 } 591 } catch (RuntimeException e) { 592 } // skip if not parseable as set 593 } 594 } 595 } 596 TestCheckNames()597 public void TestCheckNames() { 598 CheckCLDR c = new CheckNames(); 599 Options options = new CheckCLDR.Options(new LinkedHashMap<>()); 600 List<CheckStatus> possibleErrors = new ArrayList<>(); 601 final CLDRFile english = testInfo.getEnglish(); 602 c.setCldrFileToCheck(english, options, possibleErrors); 603 String xpath = "//ldml/localeDisplayNames/languages/language[@type=\"mga\"]"; 604 c.check(xpath, xpath, "Middle Irish (900-1200) ", options, possibleErrors); 605 assertEquals("There should be an error", 1, possibleErrors.size()); 606 607 possibleErrors.clear(); 608 xpath = "//ldml/localeDisplayNames/currencies/currency[@type=\"afa\"]/name"; 609 c.check(xpath, xpath, "Afghan Afghani (1927-2002)", options, possibleErrors); 610 assertEquals("Currencies are allowed to have dates", 0, possibleErrors.size()); 611 } 612 613 /** 614 * Check that at least one path in a locale is outdated and one path is not. That may change 615 * each time. This needs to be a <locale,path> that is currently outdated (birth older than 616 * English's) if the test fails with "no failure message" run GenerateBirths (if you haven't 617 * done so) look at readable results in the log file in 618 * https://github.com/unicode-org/cldr-staging/blob/main/births/41.0/fr.txt (for the current 619 * version, not nec. 41.0) for a reasonable locale ( may change locale to something other than 620 * fr) find a path that is outdated. To work on both limited and full submissions, choose one 621 * with English = trunk Sometimes the English change is suppressed in a limited release if the 622 * change is small. Pick another in that case. check the data files to ensure that it is in fact 623 * outdated. change the path to that value the 3rd parameter is the message displayed to the 624 * user, or "" if not 'English Changed' So the first group of tests are for items that should 625 * not be outdated And the second group is ones that should be outdated. 626 */ TestCheckNew()627 public void TestCheckNew() { 628 // Not outdated 629 checkCheckNew("de", "//ldml/localeDisplayNames/languages/language[@type=\"en\"]", ""); 630 631 // Outdated 632 checkCheckNew( 633 "de", 634 "//ldml/localeDisplayNames/territories/territory[@type=\"001\"]", 635 "In CLDR 39.0 the English value for this field changed from “World” to “world”, but the corresponding value for your locale didn't change."); 636 checkCheckNew( 637 "el", 638 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"mass-grain\"]/displayName", 639 "In CLDR 40.0 the English value for this field changed from “grain” to “grains”, but the corresponding value for your locale didn't change."); 640 } 641 checkCheckNew(String locale, String path, String expectedMessage)642 public void checkCheckNew(String locale, String path, String expectedMessage) { 643 final String title = "CheckNew " + locale + ", " + path; 644 645 OutdatedPaths outdatedPaths = OutdatedPaths.getInstance(); 646 boolean isOutdated = outdatedPaths.isOutdated(locale, path); 647 648 // 649 String oldEnglishValue = outdatedPaths.getPreviousEnglish(path); 650 if (!OutdatedPaths.NO_VALUE.equals(oldEnglishValue)) { 651 assertEquals(title, expectedMessage.isEmpty(), !isOutdated); 652 } 653 654 CheckCLDR c = new CheckNew(testInfo.getCommonAndSeedAndMainAndAnnotationsFactory()); 655 List<CheckStatus> result = new ArrayList<>(); 656 final CheckCLDR.Options options = new CheckCLDR.Options(new HashMap<>()); 657 c.setCldrFileToCheck(testInfo.getCLDRFile(locale, true), options, result); 658 c.check(path, path, "foobar", options, result); 659 String actualMessage = ""; 660 for (CheckStatus status : result) { 661 if (status.getSubtype() != Subtype.modifiedEnglishValue) { 662 continue; 663 } 664 actualMessage = status.getMessage(); 665 break; 666 } 667 assertEquals(title, expectedMessage, actualMessage); 668 } 669 TestCheckNewRootFailure()670 public void TestCheckNewRootFailure() { 671 // Check that we get an error with the root value for an emoji. 672 final Factory annotationsFactory = testInfo.getAnnotationsFactory(); 673 String locale = "yo"; // the name doesn't matter, since we're going to create a new one 674 String path = "//ldml/annotations/annotation[@cp=\"\"][@type=\"tts\"]"; 675 CheckCLDR c = new CheckNew(annotationsFactory); 676 List<CheckStatus> result = new ArrayList<>(); 677 Map<String, String> options = new HashMap<>(); 678 for (Phase phase : Phase.values()) { 679 options.put(Options.Option.phase.getKey(), phase.toString()); 680 String value = "E10-836"; 681 // The following code used to check values of both "E10-836" and 682 // CldrUtility.INHERITANCE_MARKER="↑↑↑", 683 // but the latter does not make sense; "↑↑↑" will be followed up through its parent 684 // chain, either 685 // yielding a real value or the root value (but never "↑↑↑"). In regular CLDR data the 686 // root value will 687 // be like "E10-836" but in production data those root entreis are stripped and the root 688 // value will be 689 // null. Hence CheckNew.handleCheck only checks for entries like "E10-836", not "↑↑↑", 690 // and the test 691 // should only check those as well. 692 { 693 // make a fake locale, starting with real root 694 695 CLDRFile root = annotationsFactory.make("root", false); 696 XMLSource localeSource = new SimpleXMLSource(locale); 697 localeSource.putValueAtPath(path, value); 698 699 TestFactory currFactory = makeTestFactory(root, localeSource); 700 CLDRFile cldrFile = currFactory.make(localeSource.getLocaleID(), true); 701 702 c.setCldrFileToCheck(cldrFile, new CheckCLDR.Options(options), result); 703 c.check(path, path, value, new CheckCLDR.Options(options), result); 704 boolean gotOne = false; 705 for (CheckStatus status : result) { 706 if (status.getSubtype() == Subtype.valueMustBeOverridden) { 707 gotOne = true; 708 assertEquals( 709 phase + " Error message check", 710 "This value must be a real translation, NOT the name/keyword placeholder.", 711 status.getMessage()); 712 } 713 } 714 if (!gotOne) { 715 errln(phase + " Missing failure message for value=" + value + "; path=" + path); 716 } 717 } 718 } 719 } 720 makeTestFactory(CLDRFile root, XMLSource localeSource)721 public TestFactory makeTestFactory(CLDRFile root, XMLSource localeSource) { 722 CLDRFile localeCldr = new CLDRFile(localeSource); 723 724 TestFactory factory = new TestFactory(); 725 factory.addFile(root); 726 factory.addFile(localeCldr); 727 return factory; 728 } 729 TestCheckDates()730 public void TestCheckDates() { 731 CheckCLDR.setDisplayInformation(testInfo.getEnglish()); // just in case 732 String prefix = 733 "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\""; 734 String infix = "\"]/dayPeriodWidth[@type=\"wide\"]/dayPeriod[@type=\""; 735 String suffix = "\"]"; 736 737 TestFactory testFactory = new TestFactory(); 738 739 List<CheckStatus> result = new ArrayList<>(); 740 Options options = new Options(); 741 final String collidingValue = "foobar"; 742 743 // Selection has stricter collision rules, because is is used to select different messages. 744 // So two types with the same localization do collide unless they have exactly the same 745 // rules. 746 747 Object[][] tests = { 748 {"en"}, // set locale 749 750 // nothing collides with itself 751 {Type.format, DayPeriod.night1, Type.format, DayPeriod.night1, Subtype.none}, 752 {Type.format, DayPeriod.morning1, Type.format, DayPeriod.morning1, Subtype.none}, 753 {Type.format, DayPeriod.afternoon1, Type.format, DayPeriod.afternoon1, Subtype.none}, 754 {Type.format, DayPeriod.evening1, Type.format, DayPeriod.evening1, Subtype.none}, 755 {Type.format, DayPeriod.am, Type.format, DayPeriod.am, Subtype.none}, 756 {Type.format, DayPeriod.pm, Type.format, DayPeriod.pm, Subtype.none}, 757 {Type.format, DayPeriod.noon, Type.format, DayPeriod.noon, Subtype.none}, 758 {Type.format, DayPeriod.midnight, Type.format, DayPeriod.midnight, Subtype.none}, 759 {Type.selection, DayPeriod.night1, Type.selection, DayPeriod.night1, Subtype.none}, 760 {Type.selection, DayPeriod.morning1, Type.selection, DayPeriod.morning1, Subtype.none}, 761 { 762 Type.selection, 763 DayPeriod.afternoon1, 764 Type.selection, 765 DayPeriod.afternoon1, 766 Subtype.none 767 }, 768 {Type.selection, DayPeriod.evening1, Type.selection, DayPeriod.evening1, Subtype.none}, 769 {Type.selection, DayPeriod.am, Type.selection, DayPeriod.am, Subtype.none}, 770 {Type.selection, DayPeriod.pm, Type.selection, DayPeriod.pm, Subtype.none}, 771 {Type.selection, DayPeriod.noon, Type.selection, DayPeriod.noon, Subtype.none}, 772 {Type.selection, DayPeriod.midnight, Type.selection, DayPeriod.midnight, Subtype.none}, 773 774 // fixed classes always collide 775 {Type.format, DayPeriod.am, Type.format, DayPeriod.pm, Subtype.dateSymbolCollision}, 776 {Type.format, DayPeriod.am, Type.format, DayPeriod.noon, Subtype.dateSymbolCollision}, 777 { 778 Type.format, 779 DayPeriod.am, 780 Type.format, 781 DayPeriod.midnight, 782 Subtype.dateSymbolCollision 783 }, 784 {Type.format, DayPeriod.pm, Type.format, DayPeriod.noon, Subtype.dateSymbolCollision}, 785 { 786 Type.format, 787 DayPeriod.pm, 788 Type.format, 789 DayPeriod.midnight, 790 Subtype.dateSymbolCollision 791 }, 792 { 793 Type.format, 794 DayPeriod.noon, 795 Type.format, 796 DayPeriod.midnight, 797 Subtype.dateSymbolCollision 798 }, 799 { 800 Type.selection, 801 DayPeriod.am, 802 Type.selection, 803 DayPeriod.pm, 804 Subtype.dateSymbolCollision 805 }, 806 { 807 Type.selection, 808 DayPeriod.am, 809 Type.selection, 810 DayPeriod.noon, 811 Subtype.dateSymbolCollision 812 }, 813 { 814 Type.selection, 815 DayPeriod.am, 816 Type.selection, 817 DayPeriod.midnight, 818 Subtype.dateSymbolCollision 819 }, 820 { 821 Type.selection, 822 DayPeriod.pm, 823 Type.selection, 824 DayPeriod.noon, 825 Subtype.dateSymbolCollision 826 }, 827 { 828 Type.selection, 829 DayPeriod.pm, 830 Type.selection, 831 DayPeriod.midnight, 832 Subtype.dateSymbolCollision 833 }, 834 { 835 Type.selection, 836 DayPeriod.noon, 837 Type.selection, 838 DayPeriod.midnight, 839 Subtype.dateSymbolCollision 840 }, 841 842 // 00-06 night1 843 // 06-12 morning1 844 // 12-18 afternoon1 845 // 18-21 evening1 846 // 21-24 night1 847 // 848 // So for a 12hour time, we have: 849 // 850 // 12 1 2 3 4 5 6 7 8 9 10 11 851 // n n n n n n m m m m m m 852 // a a a a a a e e e n n n 853 854 // Formatting has looser collision rules, because it is always paired with a time. 855 // That is, it is not a problem if two items collide, 856 // if it doesn't cause a collision when paired with a time. 857 // But if 11:00 has the same format (eg 11 X) as 23:00, there IS a collision. 858 // So we see if there is an overlap mod 12. 859 860 { 861 Type.format, 862 DayPeriod.night1, 863 Type.format, 864 DayPeriod.morning1, 865 Subtype.dateSymbolCollision 866 }, 867 { 868 Type.format, 869 DayPeriod.night1, 870 Type.format, 871 DayPeriod.afternoon1, 872 Subtype.dateSymbolCollision 873 }, 874 {Type.format, DayPeriod.night1, Type.format, DayPeriod.evening1, Subtype.none}, 875 {Type.format, DayPeriod.morning1, Type.format, DayPeriod.afternoon1, Subtype.none}, 876 { 877 Type.format, 878 DayPeriod.morning1, 879 Type.format, 880 DayPeriod.evening1, 881 Subtype.dateSymbolCollision 882 }, 883 {Type.format, DayPeriod.afternoon1, Type.format, DayPeriod.evening1, Subtype.none}, 884 885 // Selection has stricter collision rules, because is is used to select different 886 // messages. 887 // So two types with the same localization do collide unless they have exactly the same 888 // rules. 889 // We use chr to test the "unless they have exactly the same rules" below. 890 891 { 892 Type.selection, 893 DayPeriod.morning1, 894 Type.selection, 895 DayPeriod.night1, 896 Subtype.dateSymbolCollision 897 }, 898 { 899 Type.selection, 900 DayPeriod.morning1, 901 Type.selection, 902 DayPeriod.afternoon1, 903 Subtype.dateSymbolCollision 904 }, 905 { 906 Type.selection, DayPeriod.morning1, Type.selection, DayPeriod.am, Subtype.none 907 }, // morning1 and am is allowable 908 { 909 Type.selection, 910 DayPeriod.morning1, 911 Type.selection, 912 DayPeriod.pm, 913 Subtype.dateSymbolCollision 914 }, 915 {"fr"}, 916 917 // nothing collides with itself 918 {Type.format, DayPeriod.night1, Type.format, DayPeriod.night1, Subtype.none}, 919 {Type.format, DayPeriod.morning1, Type.format, DayPeriod.morning1, Subtype.none}, 920 {Type.format, DayPeriod.afternoon1, Type.format, DayPeriod.afternoon1, Subtype.none}, 921 {Type.format, DayPeriod.evening1, Type.format, DayPeriod.evening1, Subtype.none}, 922 923 // French has different rules 924 // 00-04 night1 925 // 04-12 morning1 926 // 12-18 afternoon1 927 // 18-00 evening1 928 // 929 // So for a 12hour time, we have: 930 // 931 // 12 1 2 3 4 5 6 7 8 9 10 11 932 // n n n n m m m m m m m m 933 // a a a a a a e e e e e e 934 935 {Type.format, DayPeriod.night1, Type.format, DayPeriod.morning1, Subtype.none}, 936 { 937 Type.format, 938 DayPeriod.night1, 939 Type.format, 940 DayPeriod.afternoon1, 941 Subtype.dateSymbolCollision 942 }, 943 {Type.format, DayPeriod.night1, Type.format, DayPeriod.evening1, Subtype.none}, 944 { 945 Type.format, 946 DayPeriod.morning1, 947 Type.format, 948 DayPeriod.afternoon1, 949 Subtype.dateSymbolCollision 950 }, 951 { 952 Type.format, 953 DayPeriod.morning1, 954 Type.format, 955 DayPeriod.evening1, 956 Subtype.dateSymbolCollision 957 }, 958 {Type.format, DayPeriod.afternoon1, Type.format, DayPeriod.evening1, Subtype.none}, 959 {"chr"}, 960 // Chr lets use test that same rules don't collide in selection 961 // <dayPeriodRule type="morning1" from="0:00" before="12:00" /> 962 // <dayPeriodRule type="noon" at="12:00" /> 963 // <dayPeriodRule type="afternoon1" after="12:00" before="24:00" /> 964 {Type.selection, DayPeriod.morning1, Type.selection, DayPeriod.am, Subtype.none}, 965 {Type.selection, DayPeriod.afternoon1, Type.selection, DayPeriod.pm, Subtype.none}, 966 }; 967 CLDRFile testFile = null; 968 for (Object[] test : tests) { 969 // set locale 970 if (test.length == 1) { 971 if (testFile != null) { 972 logln(""); 973 } 974 testFile = new CLDRFile(new SimpleXMLSource((String) test[0])); 975 testFactory.addFile(testFile); 976 continue; 977 } 978 final DayPeriodInfo.Type type1 = (Type) test[0]; 979 final DayPeriodInfo.DayPeriod period1 = (DayPeriod) test[1]; 980 final DayPeriodInfo.Type type2 = (Type) test[2]; 981 final DayPeriodInfo.DayPeriod period2 = (DayPeriod) test[3]; 982 final Subtype expectedSubtype = (Subtype) test[4]; 983 984 final String path1 = prefix + type1.pathValue + infix + period1 + suffix; 985 final String path2 = prefix + type2.pathValue + infix + period2 + suffix; 986 987 testFile.add(path1, collidingValue); 988 testFile.add(path2, collidingValue); 989 990 CheckCLDR c = new CheckDates(testFactory); 991 c.setCldrFileToCheck(testFile, options, result); 992 993 result.clear(); 994 c.check(path1, path1, collidingValue, options, result); 995 Subtype actualSubtype = Subtype.none; 996 String message = null; 997 for (CheckStatus status : result) { 998 actualSubtype = status.getSubtype(); 999 message = status.getMessage(); 1000 break; 1001 } 1002 assertEquals( 1003 testFile.getLocaleID() 1004 + " " 1005 + type1 1006 + "/" 1007 + period1 1008 + " vs " 1009 + type2 1010 + "/" 1011 + period2 1012 + (message == null ? "" : " [" + message + "]"), 1013 expectedSubtype, 1014 actualSubtype); 1015 1016 testFile.remove(path1); 1017 testFile.remove(path2); 1018 } 1019 // Test for CLDR-14865 1020 testFile = new CLDRFile(new SimpleXMLSource("fi")); 1021 testFactory.addFile(testFile); 1022 String availableFormatTestPath = 1023 "//ldml/dates/calendars/calendar[@type=\"generic\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"GyMd\"]"; 1024 String availableFormatValue = "d.m.y G"; // erroneous, should have M not m 1025 testFile.add(availableFormatTestPath, availableFormatValue); 1026 CheckCLDR c = new CheckDates(testFactory); 1027 c.setCldrFileToCheck(testFile, options, result); 1028 result.clear(); 1029 c.check( 1030 availableFormatTestPath, 1031 availableFormatTestPath, 1032 availableFormatValue, 1033 options, 1034 result); 1035 Subtype actualSubtype = Subtype.none; 1036 String message = null; 1037 for (CheckStatus status : result) { 1038 actualSubtype = status.getSubtype(); 1039 message = status.getMessage(); 1040 break; 1041 } 1042 if (actualSubtype != Subtype.incorrectDatePattern 1043 || message == null 1044 || !message.contains("d.M.y G")) { 1045 String errorMessage = 1046 "fi generic availableFormat for id=GyMd with value " 1047 + availableFormatValue 1048 + ":"; 1049 if (actualSubtype != Subtype.incorrectDatePattern) { 1050 errorMessage += 1051 " expected Subtype.incorrectDatePattern, got " + actualSubtype + " ;"; 1052 } 1053 if (message == null || !message.contains("d.M.y G")) { 1054 errorMessage += " expected message should contain suggested d.M.y G, got message: "; 1055 errorMessage += (message == null) ? "(null)" : message; 1056 } 1057 errln(errorMessage); 1058 } 1059 } 1060 1061 /** Should be some CLDR locales, plus a locale specially allowed in limited submission */ 1062 final List<String> localesForRowAction = ImmutableList.of("cs", "fr"); 1063 1064 /** Needs adjustment for Limited Submission! */ TestShowRowAction()1065 public void TestShowRowAction() { 1066 Map<Key, Pair<Boolean, String>> actionToExamplePath = new TreeMap<>(); 1067 Counter<Key> counter = new Counter<>(); 1068 1069 for (String locale : localesForRowAction) { 1070 DummyPathValueInfo dummyPathValueInfo = new DummyPathValueInfo(); 1071 dummyPathValueInfo.setLocale(CLDRLocale.getInstance(locale)); 1072 CLDRFile cldrFile = testInfo.getCldrFactory().make(locale, true); 1073 CLDRFile cldrFileUnresolved = testInfo.getCldrFactory().make(locale, false); 1074 1075 Set<PathHeader> sorted = new TreeSet<>(); 1076 for (String path : cldrFile) { 1077 PathHeader ph = pathHeaderFactory.fromPath(path); 1078 sorted.add(ph); 1079 } 1080 1081 for (Phase phase : Arrays.asList(Phase.SUBMISSION, Phase.VETTING)) { 1082 for (CheckStatus.Type status : 1083 Arrays.asList(CheckStatus.warningType, CheckStatus.errorType)) { 1084 dummyPathValueInfo.checkStatusType = status; 1085 1086 for (PathHeader ph : sorted) { 1087 String path = ph.getOriginalPath(); 1088 SurveyToolStatus surveyToolStatus = ph.getSurveyToolStatus(); 1089 dummyPathValueInfo.setXpath(path); 1090 dummyPathValueInfo.setBaselineValue( 1091 cldrFileUnresolved.getStringValue(path)); 1092 StatusAction action = 1093 phase.getShowRowAction( 1094 dummyPathValueInfo, InputMethod.DIRECT, ph, dummyUserInfo); 1095 1096 if (ph.shouldHide()) { 1097 assertEquals( 1098 "HIDE ==> FORBID_READONLY", 1099 StatusAction.FORBID_READONLY, 1100 action); 1101 } else if (CheckCLDR.LIMITED_SUBMISSION) { 1102 if (status == CheckStatus.Type.Error) { 1103 assertEquals("ERROR ==> ALLOW", StatusAction.ALLOW, action); 1104 } else if (locale.equalsIgnoreCase("vo")) { 1105 assertEquals( 1106 "vo ==> FORBID_READONLY", 1107 StatusAction.FORBID_READONLY, 1108 action); 1109 } else if (dummyPathValueInfo.getBaselineValue() == null) { 1110 if (!assertEquals( 1111 "missing ==> ALLOW", StatusAction.ALLOW, action)) { 1112 warnln("\t\t" + locale + "\t" + ph); 1113 } 1114 } 1115 } 1116 1117 if (isVerbose()) { 1118 Key key = new Key(locale, phase, status, surveyToolStatus, action); 1119 counter.add(key, 1); 1120 1121 if (!actionToExamplePath.containsKey(key)) { 1122 // for debugging 1123 if (locale.equals("vo") && action == StatusAction.ALLOW) { 1124 StatusAction action2 = 1125 phase.getShowRowAction( 1126 dummyPathValueInfo, 1127 InputMethod.DIRECT, 1128 ph, 1129 dummyUserInfo); 1130 } 1131 actionToExamplePath.put( 1132 key, 1133 Pair.of( 1134 dummyPathValueInfo.getBaselineValue() != null, 1135 path)); 1136 } 1137 } 1138 } 1139 } 1140 } 1141 } 1142 if (isVerbose()) { 1143 for (Entry<Key, Pair<Boolean, String>> entry : actionToExamplePath.entrySet()) { 1144 System.out.print( 1145 "\n" 1146 + entry.getKey() 1147 + "\t" 1148 + entry.getValue().getFirst() 1149 + "\t" 1150 + entry.getValue().getSecond()); 1151 } 1152 System.out.println(); 1153 for (R2<Long, Key> entry : counter.getEntrySetSortedByCount(false, null)) { 1154 System.out.println(entry.get0() + "\t" + entry.get1()); 1155 } 1156 } 1157 } 1158 1159 static class Key extends R5<Phase, CheckStatus.Type, SurveyToolStatus, StatusAction, String> { Key( String locale, Phase phase, CheckStatus.Type status, SurveyToolStatus stStatus, StatusAction action)1160 public Key( 1161 String locale, 1162 Phase phase, 1163 CheckStatus.Type status, 1164 SurveyToolStatus stStatus, 1165 StatusAction action) { 1166 super(phase, status, stStatus, action, locale); 1167 } 1168 1169 @Override toString()1170 public String toString() { 1171 return get0() + "\t" + get1() + "\t" + get2() + "\t" + get3() + "\t" + get4(); 1172 } 1173 } 1174 1175 // private static CLDRURLS URLS = testInfo.urls(); 1176 1177 private static final PathHeader.Factory pathHeaderFactory = 1178 PathHeader.getFactory(testInfo.getEnglish()); 1179 1180 // private static final CoverageInfo coverageInfo = new 1181 // CoverageInfo(testInfo.getSupplementalDataInfo()); 1182 1183 private static final VoterInfo dummyVoterInfo = 1184 new VoterInfo( 1185 Organization.cldr, org.unicode.cldr.util.VoteResolver.Level.vetter, "somename"); 1186 1187 private static final UserInfo dummyUserInfo = 1188 new UserInfo() { 1189 @Override 1190 public VoterInfo getVoterInfo() { 1191 return dummyVoterInfo; 1192 } 1193 }; 1194 1195 public static class DummyPathValueInfo implements PathValueInfo { 1196 private CLDRLocale locale; 1197 private String xpath; 1198 private String baselineValue; 1199 private CheckStatus.Type checkStatusType; 1200 1201 private CandidateInfo candidateInfo = 1202 new CandidateInfo() { 1203 @Override 1204 public String getValue() { 1205 return null; 1206 } 1207 1208 @Override 1209 public Collection<UserInfo> getUsersVotingOn() { 1210 throw new UnsupportedOperationException(); 1211 } 1212 1213 @Override 1214 public List<CheckStatus> getCheckStatusList() { 1215 return checkStatusType == null 1216 ? Collections.emptyList() 1217 : Collections.singletonList( 1218 new CheckStatus().setMainType(checkStatusType)); 1219 } 1220 }; 1221 1222 @Override getValues()1223 public Collection<? extends CandidateInfo> getValues() { 1224 throw new UnsupportedOperationException(); 1225 } 1226 1227 @Override getCurrentItem()1228 public CandidateInfo getCurrentItem() { 1229 return candidateInfo; 1230 } 1231 1232 @Override getBaselineValue()1233 public String getBaselineValue() { 1234 return baselineValue; 1235 } 1236 1237 @Override getCoverageLevel()1238 public Level getCoverageLevel() { 1239 return Level.MODERN; 1240 } 1241 1242 @Override hadVotesSometimeThisRelease()1243 public boolean hadVotesSometimeThisRelease() { 1244 throw new UnsupportedOperationException(); 1245 } 1246 1247 @Override getLocale()1248 public CLDRLocale getLocale() { 1249 return locale; 1250 } 1251 1252 @Override getXpath()1253 public String getXpath() { 1254 return xpath; 1255 } 1256 setLocale(CLDRLocale locale)1257 public void setLocale(CLDRLocale locale) { 1258 this.locale = locale; 1259 } 1260 setXpath(String xpath)1261 public void setXpath(String xpath) { 1262 this.xpath = xpath; 1263 } 1264 setBaselineValue(String baselineValue)1265 public void setBaselineValue(String baselineValue) { 1266 this.baselineValue = baselineValue; 1267 } 1268 } 1269 1270 final Set<String> cldrLocales = 1271 StandardCodes.make().getLocaleCoverageLocales(Organization.cldr); 1272 final Map<String, Status> validity = Validity.getInstance().getCodeToStatus(LstrType.language); 1273 final Map<String, R2<List<String>, String>> langAliases = 1274 CLDRConfig.getInstance().getSupplementalDataInfo().getLocaleAliasInfo().get("language"); 1275 final Set<String> existingLocales = 1276 CLDRConfig.getInstance().getCommonAndSeedAndMainAndAnnotationsFactory().getAvailable(); 1277 final LikelySubtags likely = new LikelySubtags(); 1278 1279 /** Simple check on locales and paths for limited submissions */ TestSubmissionLocales()1280 public void TestSubmissionLocales() { 1281 1282 for (String locale : SubmissionLocales.ALLOW_ALL_PATHS_BASIC) { 1283 checkLocaleOk(locale, false); 1284 } 1285 for (String locale : SubmissionLocales.LOCALES_FOR_LIMITED) { 1286 checkLocaleOk(locale, true); 1287 } 1288 } 1289 1290 /** 1291 * Check that the locale is valid, that it is either in or out of allowed locales, and that the 1292 * locale is not deprecated 1293 * 1294 * @param language 1295 * @param expectedInCLDRLocales 1296 */ checkLocaleOk(final String locale, final boolean expectedInCLDRLocales)1297 private void checkLocaleOk(final String locale, final boolean expectedInCLDRLocales) { 1298 final LanguageTagParser ltp = new LanguageTagParser().set(locale); 1299 String language = ltp.getLanguage(); 1300 1301 Status status = validity.get(language); 1302 assertTrue( 1303 language + " valid?", status == Status.regular); // || status == Status.macroregion 1304 1305 final R2<List<String>, String> alias = langAliases.get(language); 1306 if (!assertNull(language + " language is not deprecated", alias)) { 1307 errln(language + ": " + alias); 1308 } 1309 1310 // locale tests 1311 if (!assertTrue( 1312 locale + " locale is in common or seed", existingLocales.contains(locale))) { 1313 return; 1314 } 1315 if (expectedInCLDRLocales) { 1316 assertTrue(locale + " is in cldrLocales", cldrLocales.contains(locale)); 1317 } 1318 } 1319 1320 final String UNIT_PATH = "//ldml/units/unitLength[@type=\"long\"]"; 1321 1322 enum LimitedStatus { 1323 allowedUnitMissing, 1324 allowedUnitNotMissing, 1325 allowedOtherMissing, 1326 allowedOtherNotMissing, 1327 disallowed; 1328 of(boolean unit, boolean missing)1329 static LimitedStatus of(boolean unit, boolean missing) { 1330 if (unit && missing) { 1331 return allowedUnitMissing; 1332 } else if (unit && !missing) { 1333 return allowedUnitNotMissing; 1334 } else if (!unit && missing) { 1335 return allowedOtherMissing; 1336 } else { 1337 return allowedOtherNotMissing; 1338 } 1339 } 1340 } 1341 1342 /** Depends on correct values in the above constants. */ TestALLOWED_IN_LIMITED_PATHS()1343 public void TestALLOWED_IN_LIMITED_PATHS() { 1344 if (!CheckCLDR.LIMITED_SUBMISSION) { 1345 return; 1346 } 1347 1348 /** 1349 * Note: Constants moved from here to data driven test. 1350 * 1351 * <p>see org.unicode.cldr.test.TestSubmissionLocales and TestSubmissionLocales.csv 1352 */ 1353 if (SHOW_LIMITED) { 1354 System.out.println(); 1355 for (String locale : cldrFactoryWithSeed.getAvailable()) { 1356 LanguageTagParser ltp = new LanguageTagParser(); 1357 if (!ltp.set(locale).getRegion().isEmpty() 1358 || !ltp.set(locale).getVariants().isEmpty() 1359 || locale.equals("root")) { 1360 continue; 1361 } 1362 CLDRFile cldrFile = cldrFactoryWithSeed.make(locale, false); 1363 Level cldrLevel = 1364 StandardCodes.make().getLocaleCoverageLevel(Organization.cldr, locale); 1365 // patch until Rohingya is added 1366 if (cldrLevel == Level.UNDETERMINED && locale.equals("rhg")) { 1367 cldrLevel = Level.BASIC; 1368 } 1369 Counter<LimitedStatus> counter = new Counter<>(); 1370 for (String path : cldrFile.fullIterable()) { 1371 Level coverage = 1372 SupplementalDataInfo.getInstance().getCoverageLevel(path, locale); 1373 if (coverage.compareTo(cldrLevel) > 0) { 1374 continue; 1375 } 1376 String value = cldrFile.getStringValue(path); 1377 boolean isMissing = value == null; 1378 boolean allowed = 1379 SubmissionLocales.allowEvenIfLimited(locale, path, false, isMissing); 1380 if (allowed) { 1381 boolean isUnit = path.startsWith(UNIT_PATH); 1382 counter.add(LimitedStatus.of(isUnit, isMissing), 1); 1383 } else { 1384 counter.add(LimitedStatus.disallowed, 1); 1385 } 1386 } 1387 System.out.print(locale + "\t" + english.getName(locale) + "\t" + cldrLevel); 1388 for (LimitedStatus limitedStatus : LimitedStatus.values()) { 1389 System.out.print("\t" + limitedStatus + ":\t" + counter.get(limitedStatus)); 1390 } 1391 System.out.println(); 1392 } 1393 } else { 1394 warnln("Set -DTestCheckCLDR:SHOW_LIMITED to see information about affected paths."); 1395 } 1396 } 1397 TestInfohubLinks13979()1398 public void TestInfohubLinks13979() { 1399 CLDRFile root = cldrFactory.make("root", true); 1400 List<CheckStatus> possibleErrors = new ArrayList<>(); 1401 String[][] tests = { 1402 // test edge cases 1403 // locale, path, value, expected 1404 { 1405 "fr", 1406 "//ldml/numbers/minimalPairs/genderMinimalPairs[@gender=\"feminine\"]", 1407 "de genre féminin", 1408 "Need at least 1 placeholder(s), but only have 0. Placeholders are: {{0}={GENDER}, e.g. “‹noun phrase in this gender›”}; see <a href='http://cldr.unicode.org/translation/error-codes#missingPlaceholders' target='cldr_error_codes'>missing placeholders</a>." 1409 }, 1410 }; 1411 for (String[] row : tests) { 1412 String localeId = row[0]; 1413 String path = row[1]; 1414 String value = row[2]; 1415 String expected = row[3]; 1416 1417 checkPathValue(root, localeId, path, value, possibleErrors); 1418 for (CheckStatus error : possibleErrors) { 1419 if (error.getSubtype() == Subtype.missingPlaceholders) { 1420 assertEquals("message", expected, error.getMessage()); 1421 } 1422 } 1423 } 1424 } 1425 Test14866()1426 public void Test14866() { 1427 final SupplementalDataInfo supplementalDataInfo = SupplementalDataInfo.getInstance(); 1428 String locale = "pl"; 1429 int expectedCount = 14; 1430 1431 GrammarInfo grammarInfo = supplementalDataInfo.getGrammarInfo(locale); 1432 logln("Locale:\t" + locale + "\n\tGrammarInfo:\t" + grammarInfo); 1433 CLDRFile pl = factory.make(locale, true); 1434 System.out.println(""); 1435 Collection<PathHeader> pathHeaders = new TreeSet<>(); // new ArrayList(); // 1436 for (String path : pl.fullIterable()) { 1437 if (path.startsWith( 1438 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-century\"]")) { 1439 PathHeader pathHeader = PathHeader.getFactory().fromPath(path); 1440 boolean added = pathHeaders.add(pathHeader); 1441 } 1442 } 1443 int count = 0; 1444 for (PathHeader pathHeader : pathHeaders) { 1445 String path = pathHeader.getOriginalPath(); 1446 String value = pl.getStringValue(path); 1447 CLDRFile.Status status = new CLDRFile.Status(); 1448 String localeFound = pl.getSourceLocaleID(path, status); 1449 Level level = supplementalDataInfo.getCoverageLevel(path, locale); 1450 logln( 1451 "\n\t" 1452 + ++count 1453 + " Locale:\t" 1454 + locale 1455 + "\n\tLocaleFound:\t" 1456 + (locale.equals(localeFound) ? "«same»" : localeFound) 1457 + "\n\tPathHeader:\t" 1458 + pathHeader 1459 + "\n\tPath: \t" 1460 + path 1461 + "\n\tPathFound:\t" 1462 + (path.equals(status.pathWhereFound) 1463 ? "«same»" 1464 : status.pathWhereFound) 1465 + "\n\tValue:\t" 1466 + value 1467 + "\n\tLevel:\t" 1468 + level); 1469 } 1470 assertEquals("right number of elements found", expectedCount, count); 1471 } 1472 } 1473