scripts: gconv_strip: process gconv-modules.d

New glibc versions moved most gconv module information into a
gconv-modules-extra.conf file in gconv-modules.d [1]. gconv_strip
currently only looks at the gconv-modules file. As a result,
gconv_strip isn't actually stripping much right now.

This change updates gconv_strip to search for additional files in
gconv-modules.d and process them in the exact same way as it processes
the gconv-modules file.

[1]: https://sourceware.org/git/?p=glibc.git;a=blob;f=NEWS;h=f976abccbd6ffe3c2d25b6d22bc9e042ab394fab;hb=7f079fdc16e88ebb8020e17b2fd900e8924da29a#l775

BUG=b:277779682
TEST=build a tatl image; check /usr/lib64/gconv for removed modules

Change-Id: Idce5cf6d4898e41759989f757e719ae63bbdebdd
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4679252
Tested-by: Robert Kolchmeyer <rkolchmeyer@google.com>
Commit-Queue: Robert Kolchmeyer <rkolchmeyer@google.com>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Alex Klein <saklein@chromium.org>
diff --git a/scripts/gconv_strip_unittest.py b/scripts/gconv_strip_unittest.py
index 4d4048b..e633c90 100644
--- a/scripts/gconv_strip_unittest.py
+++ b/scripts/gconv_strip_unittest.py
@@ -2,8 +2,16 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Test gconv_strip."""
+"""Test gconv_strip.
 
+To run these tests, do the following inside the chroot:
+$ pytest -c /dev/null scripts/gconv_strip_unittest.py
+
+Explicitly using an empty pytest config is necessary because chromite's
+pytest.ini assumes pytest-xdist is installed, which it is not inside the chroot.
+"""
+
+import glob
 import os
 
 from chromite.lib import cros_test_lib
@@ -14,7 +22,7 @@
 pytestmark = cros_test_lib.pytestmark_inside_only
 
 
-class GconvStriptTest(cros_test_lib.MockTempDirTestCase):
+class GconvStripTest(cros_test_lib.MockTempDirTestCase):
     """Tests for gconv_strip script."""
 
     def testMultipleStringMatch(self):
@@ -43,7 +51,9 @@
 """
         osutils.WriteFile(tmp_gconv_module, data)
 
-        gmods = gconv_strip.GconvModules(tmp_gconv_module)
+        gmods = gconv_strip.GconvModules(
+            tmp_gconv_module, os.path.dirname(tmp_gconv_module)
+        )
         self.assertEqual(
             gmods.Load(),
             [
@@ -81,3 +91,81 @@
 
         content = osutils.ReadFile(tmp_gconv_module)
         self.assertEqual(content, expected)
+
+    def testGconvStrip(self):
+        """Tests GconvStrip end-to-end.
+
+        Creates a fake root directory with fake gconv modules, and expects the
+        non-sticky modules to be deleted.
+        """
+        modules_dir = os.path.join(self.tempdir, "usr", "lib64", "gconv")
+        extras_dir = os.path.join(modules_dir, "gconv-modules.d")
+        os.makedirs(extras_dir)
+        tmp_gconv_modules = os.path.join(modules_dir, "gconv-modules")
+        tmp_gconv_extras = os.path.join(extras_dir, "gconv-modules-extras.conf")
+
+        gconv_data = """
+#       from                    to                      module          cost
+alias   UTF32//                 UTF-32//
+module  UTF-32//                INTERNAL                UTF-32          1
+module  INTERNAL                UTF-32//                UTF-32          1
+
+#       from                    to                      module          cost
+alias   UTF7//                  UTF-7//
+module  UTF-7//                 INTERNAL                UTF-7           1
+module  INTERNAL                UTF-7//                 UTF-7           1
+"""
+        gconv_extras_data = """
+#       from                    to                      module          cost
+alias   UTF16//                 UTF-16//
+module  UTF-16//                INTERNAL                UTF-16          1
+module  INTERNAL                UTF-16//                UTF-16          1
+
+#       from                    to                      module          cost
+alias   EUCTW//                 EUC-TW//
+alias   OSF0005000a//           EUC-TW//
+module  EUC-TW//                INTERNAL                EUC-TW          1
+module  INTERNAL                EUC-TW//                EUC-TW          1
+"""
+        osutils.WriteFile(tmp_gconv_modules, gconv_data)
+        osutils.WriteFile(tmp_gconv_extras, gconv_extras_data)
+        for module in ["UTF-32.so", "UTF-7.so", "UTF-16.so", "EUC-TW.so"]:
+            osutils.Touch(os.path.join(modules_dir, module))
+
+        self.PatchObject(gconv_strip.lddtree, "ParseELF", return_value={})
+
+        class _StubOpts:
+            """Stub for GconvStrip args."""
+
+            def __init__(self, root):
+                self.root = root
+                self.dryrun = False
+
+        gconv_strip.GconvStrip(_StubOpts(self.tempdir))
+
+        expected_gconv_data = """
+#       from                    to                      module          cost
+alias   UTF32//                 UTF-32//
+module  UTF-32//                INTERNAL                UTF-32          1
+module  INTERNAL                UTF-32//                UTF-32          1
+
+#       from                    to                      module          cost
+"""
+        expected_gconv_extras_data = """
+#       from                    to                      module          cost
+alias   UTF16//                 UTF-16//
+module  UTF-16//                INTERNAL                UTF-16          1
+module  INTERNAL                UTF-16//                UTF-16          1
+
+#       from                    to                      module          cost
+"""
+        expected_modules = ["UTF-16.so", "UTF-32.so"]
+        actual_gconv_data = osutils.ReadFile(tmp_gconv_modules)
+        actual_gconv_extras_data = osutils.ReadFile(tmp_gconv_extras)
+        actual_modules = glob.glob(os.path.join(modules_dir, "*.so"))
+        actual_modules_names = sorted(
+            os.path.basename(x) for x in actual_modules
+        )
+        self.assertEqual(actual_gconv_data, expected_gconv_data)
+        self.assertEqual(actual_gconv_extras_data, expected_gconv_extras_data)
+        self.assertEqual(actual_modules_names, expected_modules)