Bug 1425280 - add a test to catch localization note mismatch draft
authorJulian Descottes <jdescottes@mozilla.com>
Thu, 14 Dec 2017 12:16:45 -0600
changeset 711890 9a90868e529bd5cbaf53d63ba8bbe6788e5b7a81
parent 711737 17d2e4be1891d4c20b81495a371da6a1546f3564
child 711891 835f67f84e7fbafb86b38b0df0ba497d6d264a9e
push id93197
push userjdescottes@mozilla.com
push dateThu, 14 Dec 2017 23:51:10 +0000
bugs1425280
milestone59.0a1
Bug 1425280 - add a test to catch localization note mismatch MozReview-Commit-ID: JHY75LzfPIa
browser/base/content/test/static/browser.ini
browser/base/content/test/static/browser_localization_notes.js
browser/base/content/test/static/browser_misused_characters_in_strings.js
browser/base/content/test/static/head.js
--- a/browser/base/content/test/static/browser.ini
+++ b/browser/base/content/test/static/browser.ini
@@ -3,15 +3,16 @@
 # Since the specific flavor doesn't matter from a correctness standpoint,
 # just skip the tests on ASAN and debug builds. Also known to be flaky on
 # Linux32 (bug 1172468, bug 1349307), so skip them there as well.
 skip-if = asan || debug || (os == 'linux' && bits == 32)
 support-files =
   head.js
 
 [browser_all_files_referenced.js]
+[browser_localization_notes.js]
 [browser_misused_characters_in_strings.js]
 support-files =
   bug1262648_string_with_newlines.dtd
 [browser_parsable_css.js]
 support-files =
   dummy_page.html
 [browser_parsable_script.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/static/browser_localization_notes.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check against typical mistakes in localization notes:
+ * - check if a localization note key does not match any definition in the file
+ * - check if a localization note key is duplicated in the file
+ *
+ * This runs agains both DTDs and properties files.
+ */
+
+var checkDTDs = async function(uriSpec) {
+  let rawContents = await fetchFile(uriSpec);
+  let fileName = uriSpec.match(/[^/\\]+$/)[0];
+
+  let notes = rawContents.match(/LOCALIZATION NOTE\s+\(([\w\.]*)\)/g) || [];
+  for (let note of notes) {
+    let [, key] = note.match(/LOCALIZATION NOTE\s+\(([\w\.]*)\)/);
+
+    info(`Testing file : ${fileName}, key: ${key}`);
+
+    // Find all entity definitions for a given key
+    let entityMatch = rawContents.match(new RegExp(`<!ENTITY\\s+${key}\\s`, "g"));
+
+    // Some localization note keys are only the "prefix" of actual keys
+    // (eg okButton for okButton.title, okButton.label, etc...).
+    let categoryMatch = rawContents.match(new RegExp(`<!ENTITY\\s+${key}\\.`, "g"));
+
+    if (entityMatch && entityMatch.length != 1) {
+      ok(false, `Entity only found once (${key} in ${fileName})`);
+    }
+
+    if (!entityMatch && !categoryMatch) {
+      ok(false, `No entity found for localization note (${key} in ${fileName})`);
+    }
+
+    // Check if the localization note appears several times for the same key.
+    let noteRegExp = new RegExp(`LOCALIZATION NOTE\\s+\\(${key}\\)`, "g");
+    let noteMatch = rawContents.match(noteRegExp);
+    if (noteMatch.length != 1) {
+      ok(false, `No duplicated localization notes (${key} in ${fileName})`);
+    }
+  }
+};
+
+var checkProperties = async function(uriSpec) {
+  let rawContents = await fetchFile(uriSpec);
+  let fileName = uriSpec.match(/[^/\\]+$/)[0];
+
+  let notes = rawContents.match(/LOCALIZATION NOTE\s+\(([\w\.]*)\)/g) || [];
+  for (let note of notes) {
+    let [, key] = note.match(/LOCALIZATION NOTE\s+\(([\w\.]*)\)/);
+
+    info(`Testing file : ${fileName}, key: ${key}`);
+
+    // Find all entity definitions for a given key
+    let entityRegExp = new RegExp(`(\\n|^)\\s*${key}\\s*=`, "g");
+    let entityMatch = rawContents.match(entityRegExp);
+
+    // Some localization note keys are only the "prefix" of actual keys
+    // (eg okButton for okButton.title, okButton.label, etc...).
+    let categoryRegExp = new RegExp(`(\\n|^)\\s*${key}\\.[\\w\\.]+\\s*=`, "g");
+    let categoryMatch = rawContents.match(categoryRegExp);
+
+    if (entityMatch && entityMatch.length != 1) {
+      ok(false, `Entity only found once (${key} in ${fileName})`);
+    }
+
+    if (!entityMatch && !categoryMatch) {
+      ok(false, `No entity found for localization note (${key} in ${fileName})`);
+    }
+
+    let noteRegExp = new RegExp(`LOCALIZATION NOTE\\s+\\(${key}\\)`, "g");
+    let noteMatch = rawContents.match(noteRegExp);
+    if (noteMatch.length != 1) {
+      ok(false, `No duplicated localization notes (${key} in ${fileName})`);
+    }
+  }
+};
+
+add_task(async function checkAllTheDTDs() {
+  let uris = await getAllTheFiles(".dtd");
+  ok(uris.length, `Found ${uris.length} .dtd files to scan for localization note issues`);
+  for (let uri of uris) {
+    await checkDTDs(uri.spec);
+  }
+});
+
+add_task(async function checkAllTheProperties() {
+  let uris = await getAllTheFiles(".properties");
+  ok(uris.length,
+    `Found ${uris.length} .properties files to scan for localization note issues`);
+  for (let uri of uris) {
+    await checkProperties(uri.spec);
+  }
+});
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -187,30 +187,16 @@ function testForError(filepath, key, str
 function testForErrors(filepath, key, str) {
   testForError(filepath, key, str, /\w'\w/, "apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo's.");
   testForError(filepath, key, str, /\w\u2018\w/, "incorrect-apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo\u2018s.");
   testForError(filepath, key, str, /'.+'/, "single-quote", "Single-quoted strings should use Unicode \u2018foo\u2019 instead of 'foo'.");
   testForError(filepath, key, str, /"/, "double-quote", "Double-quoted strings should use Unicode \u201cfoo\u201d instead of \"foo\".");
   testForError(filepath, key, str, /\.\.\./, "ellipsis", "Strings with an ellipsis should use the Unicode \u2026 character instead of three periods.");
 }
 
-async function getAllTheFiles(extension) {
-  let appDirGreD = Services.dirsvc.get("GreD", Ci.nsIFile);
-  let appDirXCurProcD = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
-  if (appDirGreD.contains(appDirXCurProcD)) {
-    return generateURIsFromDirTree(appDirGreD, [extension]);
-  }
-  if (appDirXCurProcD.contains(appDirGreD)) {
-    return generateURIsFromDirTree(appDirXCurProcD, [extension]);
-  }
-  let urisGreD = await generateURIsFromDirTree(appDirGreD, [extension]);
-  let urisXCurProcD = await generateURIsFromDirTree(appDirXCurProcD, [extension]);
-  return Array.from(new Set(urisGreD.concat(appDirXCurProcD)));
-}
-
 add_task(async function checkAllTheProperties() {
   // This asynchronously produces a list of URLs (sadly, mostly sync on our
   // test infrastructure because it runs against jarfiles there, and
   // our zipreader APIs are all sync)
   let uris = await getAllTheFiles(".properties");
   ok(uris.length, `Found ${uris.length} .properties files to scan for misused characters`);
 
   for (let uri of uris) {
--- a/browser/base/content/test/static/head.js
+++ b/browser/base/content/test/static/head.js
@@ -143,8 +143,21 @@ function fetchFile(uri) {
     };
     xhr.onerror = error => {
       ok(false, `XHR error reading ${uri}: ${error}`);
       resolve("");
     };
     xhr.send(null);
   });
 }
+
+async function getAllTheFiles(extension) {
+  let appDirGreD = Services.dirsvc.get("GreD", Ci.nsIFile);
+  let appDirXCurProcD = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+  if (appDirGreD.contains(appDirXCurProcD)) {
+    return generateURIsFromDirTree(appDirGreD, [extension]);
+  }
+  if (appDirXCurProcD.contains(appDirGreD)) {
+    return generateURIsFromDirTree(appDirXCurProcD, [extension]);
+  }
+  let urisGreD = await generateURIsFromDirTree(appDirGreD, [extension]);
+  return Array.from(new Set(urisGreD.concat(appDirXCurProcD)));
+}