test/jdk/tools/jlink/plugins/StripNativeDebugSymbolsPlugin/StripNativeDebugSymbolsPluginTest.java
changeset 54824 adb3a3aa2e52
equal deleted inserted replaced
54823:1b940da275d2 54824:adb3a3aa2e52
       
     1 /*
       
     2  * Copyright (c) 2019, Red Hat, Inc.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 import java.io.BufferedWriter;
       
    26 import java.io.File;
       
    27 import java.io.IOException;
       
    28 import java.io.InputStream;
       
    29 import java.io.PrintWriter;
       
    30 import java.io.StringWriter;
       
    31 import java.nio.file.FileVisitResult;
       
    32 import java.nio.file.Files;
       
    33 import java.nio.file.NoSuchFileException;
       
    34 import java.nio.file.Path;
       
    35 import java.nio.file.Paths;
       
    36 import java.nio.file.SimpleFileVisitor;
       
    37 import java.nio.file.attribute.BasicFileAttributes;
       
    38 import java.util.ArrayList;
       
    39 import java.util.Arrays;
       
    40 import java.util.List;
       
    41 import java.util.Map;
       
    42 import java.util.Scanner;
       
    43 import java.util.spi.ToolProvider;
       
    44 import java.util.stream.Collectors;
       
    45 import java.util.stream.Stream;
       
    46 
       
    47 import jdk.test.lib.compiler.CompilerUtils;
       
    48 import jdk.tools.jlink.internal.ResourcePoolManager;
       
    49 import jdk.tools.jlink.internal.plugins.StripNativeDebugSymbolsPlugin;
       
    50 import jdk.tools.jlink.internal.plugins.StripNativeDebugSymbolsPlugin.ObjCopyCmdBuilder;
       
    51 import jdk.tools.jlink.plugin.ResourcePool;
       
    52 import jdk.tools.jlink.plugin.ResourcePoolEntry;
       
    53 
       
    54 /*
       
    55  * @test
       
    56  * @requires os.family == "linux"
       
    57  * @bug 8214796
       
    58  * @summary Test --strip-native-debug-symbols plugin
       
    59  * @library /test/lib
       
    60  * @modules jdk.compiler
       
    61  *          jdk.jlink/jdk.tools.jlink.internal.plugins
       
    62  *          jdk.jlink/jdk.tools.jlink.internal
       
    63  *          jdk.jlink/jdk.tools.jlink.plugin
       
    64  * @build jdk.test.lib.compiler.CompilerUtils FakeObjCopy
       
    65  * @run main/othervm -Xmx1g StripNativeDebugSymbolsPluginTest
       
    66  */
       
    67 public class StripNativeDebugSymbolsPluginTest {
       
    68 
       
    69     private static final String OBJCOPY = "objcopy";
       
    70     private static final String DEFAULT_OBJCOPY_CMD = OBJCOPY;
       
    71     private static final String PLUGIN_NAME = "strip-native-debug-symbols";
       
    72     private static final String MODULE_NAME_WITH_NATIVE = "fib";
       
    73     private static final String JAVA_HOME = System.getProperty("java.home");
       
    74     private static final String NATIVE_LIB_NAME = "libFib.so";
       
    75     private static final Path JAVA_LIB_PATH = Paths.get(System.getProperty("java.library.path"));
       
    76     private static final Path LIB_FIB_SRC = JAVA_LIB_PATH.resolve(NATIVE_LIB_NAME);
       
    77     private static final String FIBJNI_CLASS_NAME = "FibJNI.java";
       
    78     private static final Path JAVA_SRC_DIR = Paths.get(System.getProperty("test.src"))
       
    79                                                   .resolve("src")
       
    80                                                   .resolve(MODULE_NAME_WITH_NATIVE);
       
    81     private static final Path FIBJNI_JAVA_CLASS = JAVA_SRC_DIR.resolve(FIBJNI_CLASS_NAME);
       
    82     private static final String DEBUG_EXTENSION = "debug";
       
    83     private static final long ORIG_LIB_FIB_SIZE = LIB_FIB_SRC.toFile().length();
       
    84     private static final String FAKE_OBJ_COPY_LOG_FILE = "objcopy.log";
       
    85     private static final String OBJCOPY_ONLY_DEBUG_SYMS_OPT = "-g";
       
    86     private static final String OBJCOPY_ONLY_KEEP_DEBUG_SYMS_OPT = "--only-keep-debug";
       
    87     private static final String OBJCOPY_ADD_DEBUG_LINK_OPT = "--add-gnu-debuglink";
       
    88 
       
    89     ///////////////////////////////////////////////////////////////////////////
       
    90     //
       
    91     // Tests which do NOT rely on objcopy being present on the test system
       
    92     //
       
    93     ///////////////////////////////////////////////////////////////////////////
       
    94 
       
    95     public void testPluginLoaded() {
       
    96         List<String> output =
       
    97             JLink.run("--list-plugins").output();
       
    98         if (output.stream().anyMatch(s -> s.contains(PLUGIN_NAME))) {
       
    99             System.out.println("DEBUG: " + PLUGIN_NAME + " plugin loaded as expected.");
       
   100         } else {
       
   101             throw new AssertionError("strip-native-debug-symbols plugin not in " +
       
   102                                      "--list-plugins output.");
       
   103         }
       
   104     }
       
   105 
       
   106     public void testConfigureFakeObjCopy() throws Exception {
       
   107         configureConflictingOptions();
       
   108         configureObjcopyWithOmit();
       
   109         configureObjcopyWithKeep();
       
   110         configureUnknownOptions();
       
   111         configureMultipleTimesSamePlugin();
       
   112         System.out.println("Test testConfigureFakeObjCopy() PASSED!");
       
   113     }
       
   114 
       
   115     private void configureMultipleTimesSamePlugin() throws Exception {
       
   116         Map<String, String> keepDebug = Map.of(
       
   117                 StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files"
       
   118         );
       
   119         Map<String, String> excludeDebug = Map.of(
       
   120                 StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files"
       
   121         );
       
   122         StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(keepDebug);
       
   123         try {
       
   124             plugin.doConfigure(false, excludeDebug);
       
   125             throw new AssertionError("should have thrown IAE for broken config: " +
       
   126                                      keepDebug + " and " + excludeDebug);
       
   127         } catch (IllegalArgumentException e) {
       
   128             // pass
       
   129             System.out.println("DEBUG: test threw IAE " + e.getMessage() +
       
   130                                " as expected.");
       
   131         }
       
   132     }
       
   133 
       
   134     private void configureUnknownOptions() throws Exception {
       
   135         Map<String, String> config = Map.of(
       
   136                 StripNativeDebugSymbolsPlugin.NAME, "foobar"
       
   137         );
       
   138         doConfigureUnknownOption(config);
       
   139         config = Map.of(
       
   140                 StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files",
       
   141                 "foo", "bar" // unknown value
       
   142         );
       
   143         doConfigureUnknownOption(config);
       
   144     }
       
   145 
       
   146     private void doConfigureUnknownOption(Map<String, String> config) throws Exception {
       
   147         try {
       
   148             createAndConfigPlugin(config);
       
   149             throw new AssertionError("should have thrown IAE for broken config: " + config);
       
   150         } catch (IllegalArgumentException e) {
       
   151             // pass
       
   152             System.out.println("DEBUG: test threw IAE " + e.getMessage() +
       
   153                                " as expected.");
       
   154         }
       
   155     }
       
   156 
       
   157     private void configureObjcopyWithKeep() throws Exception {
       
   158         String objcopyPath = "foobar";
       
   159         String debugExt = "debuginfo"; // that's the default value
       
   160         Map<String, String> config = Map.of(
       
   161                 StripNativeDebugSymbolsPlugin.NAME, "keep-debuginfo-files",
       
   162                 "objcopy", objcopyPath
       
   163         );
       
   164         doKeepDebugInfoFakeObjCopyTest(config, debugExt, objcopyPath);
       
   165         // Do it again combining options the other way round
       
   166         debugExt = "testme";
       
   167         config = Map.of(
       
   168                 StripNativeDebugSymbolsPlugin.NAME, "objcopy=" + objcopyPath,
       
   169                 "keep-debuginfo-files", debugExt
       
   170         );
       
   171         doKeepDebugInfoFakeObjCopyTest(config, debugExt, objcopyPath);
       
   172         System.out.println("DEBUG: configureObjcopyWithKeep() PASSED!");
       
   173     }
       
   174 
       
   175     private void configureObjcopyWithOmit() throws Exception {
       
   176         String objcopyPath = "something-non-standard";
       
   177         Map<String, String> config = Map.of(
       
   178                 StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files",
       
   179                 "objcopy", objcopyPath
       
   180         );
       
   181         doOmitDebugInfoFakeObjCopyTest(config, objcopyPath);
       
   182         System.out.println("DEBUG: configureObjcopyWithOmit() PASSED!");
       
   183     }
       
   184 
       
   185     private void configureConflictingOptions() throws Exception {
       
   186         Map<String, String> config = Map.of(
       
   187                 StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files",
       
   188                 "keep-debuginfo-files", "foo-ext"
       
   189         );
       
   190         doConfigureConflictingOptions(config);
       
   191         config = Map.of(
       
   192                 StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files=bar",
       
   193                 "keep-debuginfo-files", "foo-ext"
       
   194         );
       
   195         doConfigureConflictingOptions(config);
       
   196     }
       
   197 
       
   198     private void doConfigureConflictingOptions(Map<String, String> config) throws Exception {
       
   199         try {
       
   200             createAndConfigPlugin(config);
       
   201             throw new AssertionError("keep-debuginfo-files and exclude-debuginfo-files " +
       
   202                                      " should have conflicted!");
       
   203         } catch (IllegalArgumentException e) {
       
   204             // pass
       
   205             if (e.getMessage().contains("keep-debuginfo-files") &&
       
   206                     e.getMessage().contains("exclude-debuginfo-files")) {
       
   207                 System.out.println("DEBUG: test threw IAE " + e.getMessage() +
       
   208                                " as expected.");
       
   209             } else {
       
   210                 throw new AssertionError("Unexpected IAE", e);
       
   211             }
       
   212         }
       
   213     }
       
   214 
       
   215     public void testTransformFakeObjCopyNoDebugInfoFiles() throws Exception {
       
   216         Map<String, String> defaultConfig = Map.of(
       
   217                                  StripNativeDebugSymbolsPlugin.NAME, "exclude-debuginfo-files"
       
   218                                  );
       
   219         doOmitDebugInfoFakeObjCopyTest(defaultConfig, DEFAULT_OBJCOPY_CMD);
       
   220         System.out.println("testTransformFakeObjCopyNoDebugInfoFiles() PASSED!");
       
   221     }
       
   222 
       
   223     private void doOmitDebugInfoFakeObjCopyTest(Map<String, String> config,
       
   224                                                 String expectedObjCopy) throws Exception {
       
   225         StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(config, expectedObjCopy);
       
   226         String binFile = "mybin";
       
   227         String path = "/fib/bin/" + binFile;
       
   228         ResourcePoolEntry debugEntry = createMockEntry(path,
       
   229                                                        ResourcePoolEntry.Type.NATIVE_CMD);
       
   230         ResourcePoolManager inResources = new ResourcePoolManager();
       
   231         ResourcePoolManager outResources = new ResourcePoolManager();
       
   232         inResources.add(debugEntry);
       
   233         ResourcePool output = plugin.transform(
       
   234                                         inResources.resourcePool(),
       
   235                                         outResources.resourcePoolBuilder());
       
   236         // expect entry to be present
       
   237         if (output.findEntry(path).isPresent()) {
       
   238             System.out.println("DEBUG: File " + path + " present as exptected.");
       
   239         } else {
       
   240             throw new AssertionError("Test failed. Binary " + path +
       
   241                                      " not present after stripping!");
       
   242         }
       
   243         verifyFakeObjCopyCalled(binFile);
       
   244     }
       
   245 
       
   246     public void testTransformFakeObjCopyKeepDebugInfoFiles() throws Exception {
       
   247         Map<String, String> defaultConfig = Map.of(
       
   248                                  StripNativeDebugSymbolsPlugin.NAME,
       
   249                                  "keep-debuginfo-files=" + DEBUG_EXTENSION
       
   250                                  );
       
   251         doKeepDebugInfoFakeObjCopyTest(defaultConfig,
       
   252                                        DEBUG_EXTENSION,
       
   253                                        DEFAULT_OBJCOPY_CMD);
       
   254         System.out.println("testTransformFakeObjCopyKeepDebugInfoFiles() PASSED!");
       
   255     }
       
   256 
       
   257     private void doKeepDebugInfoFakeObjCopyTest(Map<String, String> config,
       
   258                                                 String debugExt,
       
   259                                                 String expectedObjCopy) throws Exception {
       
   260         StripNativeDebugSymbolsPlugin plugin = createAndConfigPlugin(config, expectedObjCopy);
       
   261         String sharedLib = "myLib.so";
       
   262         String path = "/fib/lib/" + sharedLib;
       
   263         ResourcePoolEntry debugEntry = createMockEntry(path,
       
   264                                                        ResourcePoolEntry.Type.NATIVE_LIB);
       
   265         ResourcePoolManager inResources = new ResourcePoolManager();
       
   266         ResourcePoolManager outResources = new ResourcePoolManager();
       
   267         inResources.add(debugEntry);
       
   268         ResourcePool output = plugin.transform(
       
   269                                         inResources.resourcePool(),
       
   270                                         outResources.resourcePoolBuilder());
       
   271         // expect entry + debug info entry to be present
       
   272         String debugPath = path + "." + debugExt;
       
   273         if (output.findEntry(path).isPresent() &&
       
   274             output.findEntry(debugPath).isPresent()) {
       
   275             System.out.println("DEBUG: Files " + path + "{,." + debugExt +
       
   276                                "} present as exptected.");
       
   277         } else {
       
   278             throw new AssertionError("Test failed. Binary files " + path +
       
   279                                      "{,." + debugExt +"} not present after " +
       
   280                                      "stripping!");
       
   281         }
       
   282         verifyFakeObjCopyCalledMultiple(sharedLib, debugExt);
       
   283     }
       
   284 
       
   285     ///////////////////////////////////////////////////////////////////////////
       
   286     //
       
   287     // Tests which DO rely on objcopy being present on the test system.
       
   288     // Skipped otherwise.
       
   289     //
       
   290     ///////////////////////////////////////////////////////////////////////////
       
   291 
       
   292     public void testStripNativeLibraryDefaults() throws Exception {
       
   293         if (!hasJmods()) return;
       
   294 
       
   295         Path libFibJmod = createLibFibJmod();
       
   296 
       
   297         Path imageDir = Paths.get("stripped-native-libs");
       
   298         JLink.run("--output", imageDir.toString(),
       
   299                 "--verbose",
       
   300                 "--module-path", modulePathWith(libFibJmod),
       
   301                 "--add-modules", MODULE_NAME_WITH_NATIVE,
       
   302                 "--strip-native-debug-symbols=exclude-debuginfo-files").output();
       
   303         Path libDir = imageDir.resolve("lib");
       
   304         Path postStripLib = libDir.resolve(NATIVE_LIB_NAME);
       
   305         long postStripSize = postStripLib.toFile().length();
       
   306 
       
   307         if (postStripSize == 0) {
       
   308             throw new AssertionError("Lib file size 0. Test error?!");
       
   309         }
       
   310         // Heuristic: libLib.so is smaller post debug info stripping
       
   311         if (postStripSize >= ORIG_LIB_FIB_SIZE) {
       
   312             throw new AssertionError("Expected native library stripping to " +
       
   313                                      "reduce file size. Expected < " +
       
   314                                      ORIG_LIB_FIB_SIZE + ", got: " + postStripSize);
       
   315         } else {
       
   316             System.out.println("DEBUG: File size of " + postStripLib.toString() +
       
   317                     " " + postStripSize + " < " + ORIG_LIB_FIB_SIZE + " as expected." );
       
   318         }
       
   319         verifyFibModule(imageDir); // Sanity check fib module which got libFib.so stripped
       
   320         System.out.println("DEBUG: testStripNativeLibraryDefaults() PASSED!");
       
   321     }
       
   322 
       
   323     public void testOptionsInvalidObjcopy() throws Exception {
       
   324         if (!hasJmods()) return;
       
   325 
       
   326         Path libFibJmod = createLibFibJmod();
       
   327 
       
   328         String notExists = "/do/not/exist/objcopy";
       
   329 
       
   330         Path imageDir = Paths.get("invalid-objcopy-command");
       
   331         String[] jlinkCmdArray = new String[] {
       
   332                 JAVA_HOME + File.separator + "bin" + File.separator + "jlink",
       
   333                 "--output", imageDir.toString(),
       
   334                 "--verbose",
       
   335                 "--module-path", modulePathWith(libFibJmod),
       
   336                 "--add-modules", MODULE_NAME_WITH_NATIVE,
       
   337                 "--strip-native-debug-symbols", "objcopy=" + notExists,
       
   338         };
       
   339         List<String> jlinkCmd = Arrays.asList(jlinkCmdArray);
       
   340         System.out.println("Debug: command: " + jlinkCmd.stream().collect(
       
   341                                                     Collectors.joining(" ")));
       
   342         ProcessBuilder builder = new ProcessBuilder(jlinkCmd);
       
   343         Process p = builder.start();
       
   344         int status = p.waitFor();
       
   345         if (status == 0) {
       
   346             throw new AssertionError("Expected jlink to fail!");
       
   347         } else {
       
   348             verifyInvalidObjcopyError(p.getInputStream(), notExists);
       
   349             System.out.println("DEBUG: testOptionsInvalidObjcopy() PASSED!");
       
   350         }
       
   351     }
       
   352 
       
   353     public void testStripNativeLibsDebugSymsIncluded() throws Exception {
       
   354         if (!hasJmods()) return;
       
   355 
       
   356         Path libFibJmod = createLibFibJmod();
       
   357 
       
   358         Path imageDir = Paths.get("stripped-native-libs-with-debug");
       
   359         JLink.run("--output", imageDir.toString(),
       
   360                 "--verbose",
       
   361                 "--module-path", modulePathWith(libFibJmod),
       
   362                 "--add-modules", MODULE_NAME_WITH_NATIVE,
       
   363                 "--strip-native-debug-symbols",
       
   364                 "keep-debuginfo-files=" + DEBUG_EXTENSION);
       
   365 
       
   366         Path libDir = imageDir.resolve("lib");
       
   367         Path postStripLib = libDir.resolve(NATIVE_LIB_NAME);
       
   368         long postStripSize = postStripLib.toFile().length();
       
   369 
       
   370         if (postStripSize == 0) {
       
   371             throw new AssertionError("Lib file size 0. Test error?!");
       
   372         }
       
   373         // Heuristic: libLib.so is smaller post debug info stripping
       
   374         if (postStripSize >= ORIG_LIB_FIB_SIZE) {
       
   375             throw new AssertionError("Expected native library stripping to " +
       
   376                                      "reduce file size. Expected < " +
       
   377                                      ORIG_LIB_FIB_SIZE + ", got: " + postStripSize);
       
   378         } else {
       
   379             System.out.println("DEBUG: File size of " + postStripLib.toString() +
       
   380                     " " + postStripSize + " < " + ORIG_LIB_FIB_SIZE + " as expected." );
       
   381         }
       
   382         // stripped with option to preserve debug symbols file
       
   383         verifyDebugInfoSymbolFilePresent(imageDir);
       
   384         System.out.println("DEBUG: testStripNativeLibsDebugSymsIncluded() PASSED!");
       
   385     }
       
   386 
       
   387     private void verifyFakeObjCopyCalledMultiple(String expectedFile,
       
   388                                                  String dbgExt) throws Exception {
       
   389         // transform of the StripNativeDebugSymbolsPlugin created objcopy.log
       
   390         // with our stubbed FakeObjCopy. See FakeObjCopy.java
       
   391         List<String> allLines = Files.readAllLines(Paths.get(FAKE_OBJ_COPY_LOG_FILE));
       
   392         if (allLines.size() != 3) {
       
   393             throw new AssertionError("Expected 3 calls to objcopy");
       
   394         }
       
   395         // 3 calls to objcopy are as follows:
       
   396         //    1. Only keep debug symbols
       
   397         //    2. Strip debug symbols
       
   398         //    3. Add debug link to stripped file
       
   399         String onlyKeepDebug = allLines.get(0);
       
   400         String stripSymbolsLine = allLines.get(1);
       
   401         String addGnuDebugLink = allLines.get(2);
       
   402         System.out.println("DEBUG: Inspecting fake objcopy calls: " + allLines);
       
   403         boolean passed = stripSymbolsLine.startsWith(OBJCOPY_ONLY_DEBUG_SYMS_OPT);
       
   404         passed &= stripSymbolsLine.endsWith(expectedFile);
       
   405         String[] tokens = onlyKeepDebug.split("\\s");
       
   406         passed &= tokens[0].equals(OBJCOPY_ONLY_KEEP_DEBUG_SYMS_OPT);
       
   407         passed &= tokens[1].endsWith(expectedFile);
       
   408         passed &= tokens[2].endsWith(expectedFile + "." + dbgExt);
       
   409         tokens = addGnuDebugLink.split("\\s");
       
   410         String[] addDbgTokens = tokens[0].split("=");
       
   411         passed &= addDbgTokens[1].equals(expectedFile + "." + dbgExt);
       
   412         passed &= addDbgTokens[0].equals(OBJCOPY_ADD_DEBUG_LINK_OPT);
       
   413         passed &= tokens[1].endsWith(expectedFile);
       
   414         if (!passed) {
       
   415             throw new AssertionError("Test failed! objcopy not properly called " +
       
   416                                      "with expected options!");
       
   417         }
       
   418     }
       
   419 
       
   420     private void verifyFakeObjCopyCalled(String expectedFile) throws Exception {
       
   421         // transform of the StripNativeDebugSymbolsPlugin created objcopy.log
       
   422         // with our stubbed FakeObjCopy. See FakeObjCopy.java
       
   423         List<String> allLines = Files.readAllLines(Paths.get(FAKE_OBJ_COPY_LOG_FILE));
       
   424         if (allLines.size() != 1) {
       
   425             throw new AssertionError("Expected 1 call to objcopy only");
       
   426         }
       
   427         String optionLine = allLines.get(0);
       
   428         System.out.println("DEBUG: Inspecting fake objcopy arguments: " + optionLine);
       
   429         boolean passed = optionLine.startsWith(OBJCOPY_ONLY_DEBUG_SYMS_OPT);
       
   430         passed &= optionLine.endsWith(expectedFile);
       
   431         if (!passed) {
       
   432             throw new AssertionError("Test failed! objcopy not called with " +
       
   433                                      "expected options!");
       
   434         }
       
   435     }
       
   436 
       
   437     private ResourcePoolEntry createMockEntry(String path,
       
   438                                               ResourcePoolEntry.Type type) {
       
   439         byte[] mockContent = new byte[] { 0, 1, 2, 3 };
       
   440         ResourcePoolEntry entry = ResourcePoolEntry.create(
       
   441                 path,
       
   442                 type,
       
   443                 mockContent);
       
   444         return entry;
       
   445     }
       
   446 
       
   447     private StripNativeDebugSymbolsPlugin createAndConfigPlugin(
       
   448                                             Map<String, String> config,
       
   449                                             String expectedObjcopy)
       
   450                                             throws IOException {
       
   451         TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder(expectedObjcopy);
       
   452         return createAndConfigPlugin(config, cmdBuilder);
       
   453     }
       
   454 
       
   455     private StripNativeDebugSymbolsPlugin createAndConfigPlugin(
       
   456             Map<String, String> config) throws IOException {
       
   457         TestObjCopyCmdBuilder cmdBuilder = new TestObjCopyCmdBuilder();
       
   458         return createAndConfigPlugin(config, cmdBuilder);
       
   459     }
       
   460 
       
   461     private StripNativeDebugSymbolsPlugin createAndConfigPlugin(
       
   462                                 Map<String, String> config,
       
   463                                 TestObjCopyCmdBuilder builder) throws IOException {
       
   464         StripNativeDebugSymbolsPlugin plugin =
       
   465                                      new StripNativeDebugSymbolsPlugin(builder);
       
   466         plugin.doConfigure(false, config);
       
   467         return plugin;
       
   468     }
       
   469 
       
   470     // Create the jmod with the native library
       
   471     private Path createLibFibJmod() throws IOException {
       
   472         JmodFileBuilder jmodBuilder = new JmodFileBuilder(MODULE_NAME_WITH_NATIVE);
       
   473         jmodBuilder.javaClass(FIBJNI_JAVA_CLASS);
       
   474         jmodBuilder.nativeLib(LIB_FIB_SRC);
       
   475         return jmodBuilder.build();
       
   476     }
       
   477 
       
   478     private String modulePathWith(Path jmod) {
       
   479         return Paths.get(JAVA_HOME, "jmods").toString() +
       
   480                     File.pathSeparator + jmod.getParent().toString();
       
   481     }
       
   482 
       
   483     private boolean hasJmods() {
       
   484         if (!Files.exists(Paths.get(JAVA_HOME, "jmods"))) {
       
   485             System.err.println("Test skipped. NO jmods directory");
       
   486             return false;
       
   487         }
       
   488         return true;
       
   489     }
       
   490 
       
   491     private void verifyInvalidObjcopyError(InputStream errInput, String match) {
       
   492         boolean foundMatch = false;
       
   493         try (Scanner scanner = new Scanner(errInput)) {
       
   494             while (scanner.hasNextLine()) {
       
   495                 String line = scanner.nextLine();
       
   496                 System.out.println("DEBUG: >>>> " + line);
       
   497                 if (line.contains(match)) {
       
   498                     foundMatch = true;
       
   499                     break;
       
   500                 }
       
   501             }
       
   502         }
       
   503         if (!foundMatch) {
       
   504             throw new AssertionError("Expected to find " + match +
       
   505                                     " in error stream.");
       
   506         } else {
       
   507             System.out.println("DEBUG: Found string " + match + " as expected.");
       
   508         }
       
   509     }
       
   510 
       
   511     private void verifyDebugInfoSymbolFilePresent(Path image)
       
   512                                     throws IOException, InterruptedException {
       
   513         Path debugSymsFile = image.resolve("lib/libFib.so.debug");
       
   514         if (!Files.exists(debugSymsFile)) {
       
   515             throw new AssertionError("Expected stripped debug info file " +
       
   516                                         debugSymsFile.toString() + " to exist.");
       
   517         }
       
   518         long debugSymsSize = debugSymsFile.toFile().length();
       
   519         if (debugSymsSize <= 0) {
       
   520             throw new AssertionError("sanity check for fib.FibJNI failed " +
       
   521                                      "post-stripping!");
       
   522         } else {
       
   523             System.out.println("DEBUG: Debug symbols stripped from libFib.so " +
       
   524                                "present (" + debugSymsFile.toString() + ") as expected.");
       
   525         }
       
   526     }
       
   527 
       
   528     private void verifyFibModule(Path image)
       
   529                                 throws IOException, InterruptedException {
       
   530         System.out.println("DEBUG: sanity checking fib module...");
       
   531         Path launcher = image.resolve("bin/java");
       
   532         List<String> args = new ArrayList<>();
       
   533         args.add(launcher.toString());
       
   534         args.add("--add-modules");
       
   535         args.add(MODULE_NAME_WITH_NATIVE);
       
   536         args.add("fib.FibJNI");
       
   537         args.add("7");
       
   538         args.add("13"); // fib(7) == 13
       
   539         System.out.println("DEBUG: [command] " +
       
   540                                 args.stream().collect(Collectors.joining(" ")));
       
   541         Process proc = new ProcessBuilder(args).inheritIO().start();
       
   542         int status = proc.waitFor();
       
   543         if (status == 0) {
       
   544             System.out.println("DEBUG: sanity checking fib module... PASSED!");
       
   545         } else {
       
   546             throw new AssertionError("sanity check for fib.FibJNI failed post-" +
       
   547                                      "stripping!");
       
   548         }
       
   549     }
       
   550 
       
   551     private static boolean isObjcopyPresent() throws Exception {
       
   552         String[] objcopyVersion = new String[] {
       
   553                 OBJCOPY, "--version",
       
   554         };
       
   555         List<String> command = Arrays.asList(objcopyVersion);
       
   556         try {
       
   557             ProcessBuilder builder = new ProcessBuilder(command);
       
   558             builder.inheritIO();
       
   559             Process p = builder.start();
       
   560             int status = p.waitFor();
       
   561             if (status != 0) {
       
   562                 System.out.println("Debug: objcopy binary doesn't seem to be " +
       
   563                                    "present or functional.");
       
   564                 return false;
       
   565             }
       
   566         } catch (IOException e) {
       
   567             System.out.println("Debug: objcopy binary doesn't seem to be present " +
       
   568                                "or functional.");
       
   569             return false;
       
   570         }
       
   571         return true;
       
   572     }
       
   573 
       
   574     public static void main(String[] args) throws Exception {
       
   575         StripNativeDebugSymbolsPluginTest test = new StripNativeDebugSymbolsPluginTest();
       
   576         if (isObjcopyPresent()) {
       
   577             test.testStripNativeLibraryDefaults();
       
   578             test.testStripNativeLibsDebugSymsIncluded();
       
   579             test.testOptionsInvalidObjcopy();
       
   580         } else {
       
   581             System.out.println("DEBUG: objcopy binary not available. " +
       
   582                                "Running reduced set of tests.");
       
   583         }
       
   584         test.testTransformFakeObjCopyNoDebugInfoFiles();
       
   585         test.testTransformFakeObjCopyKeepDebugInfoFiles();
       
   586         test.testConfigureFakeObjCopy();
       
   587         test.testPluginLoaded();
       
   588     }
       
   589 
       
   590     static class JLink {
       
   591         static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
       
   592             .orElseThrow(() ->
       
   593                 new RuntimeException("jlink tool not found")
       
   594             );
       
   595 
       
   596         static JLink run(String... options) {
       
   597             JLink jlink = new JLink();
       
   598             if (jlink.execute(options) != 0) {
       
   599                 throw new AssertionError("Jlink expected to exit with 0 return code");
       
   600             }
       
   601             return jlink;
       
   602         }
       
   603 
       
   604         final List<String> output = new ArrayList<>();
       
   605         private int execute(String... options) {
       
   606             System.out.println("jlink " +
       
   607                 Stream.of(options).collect(Collectors.joining(" ")));
       
   608 
       
   609             StringWriter writer = new StringWriter();
       
   610             PrintWriter pw = new PrintWriter(writer);
       
   611             int rc = JLINK_TOOL.run(pw, pw, options);
       
   612             System.out.println(writer.toString());
       
   613             Stream.of(writer.toString().split("\\v"))
       
   614                   .map(String::trim)
       
   615                   .forEach(output::add);
       
   616             return rc;
       
   617         }
       
   618 
       
   619         boolean contains(String s) {
       
   620             return output.contains(s);
       
   621         }
       
   622 
       
   623         List<String> output() {
       
   624             return output;
       
   625         }
       
   626     }
       
   627 
       
   628     /**
       
   629      * Builder to create JMOD file
       
   630      */
       
   631     private static class JmodFileBuilder {
       
   632 
       
   633         private static final ToolProvider JMOD_TOOL = ToolProvider
       
   634                 .findFirst("jmod")
       
   635                 .orElseThrow(() ->
       
   636                     new RuntimeException("jmod tool not found")
       
   637                 );
       
   638         private static final Path SRC_DIR = Paths.get("src");
       
   639         private static final Path MODS_DIR = Paths.get("mod");
       
   640         private static final Path JMODS_DIR = Paths.get("jmods");
       
   641         private static final Path LIBS_DIR = Paths.get("libs");
       
   642 
       
   643         private final String name;
       
   644         private final List<Path> nativeLibs = new ArrayList<>();
       
   645         private final List<Path> javaClasses = new ArrayList<>();
       
   646 
       
   647         private JmodFileBuilder(String name) throws IOException {
       
   648             this.name = name;
       
   649 
       
   650             deleteDirectory(MODS_DIR);
       
   651             deleteDirectory(SRC_DIR);
       
   652             deleteDirectory(LIBS_DIR);
       
   653             deleteDirectory(JMODS_DIR);
       
   654             Path msrc = SRC_DIR.resolve(name);
       
   655             if (Files.exists(msrc)) {
       
   656                 deleteDirectory(msrc);
       
   657             }
       
   658         }
       
   659 
       
   660         JmodFileBuilder nativeLib(Path libFileSrc) {
       
   661             nativeLibs.add(libFileSrc);
       
   662             return this;
       
   663         }
       
   664 
       
   665         JmodFileBuilder javaClass(Path srcPath) {
       
   666             javaClasses.add(srcPath);
       
   667             return this;
       
   668         }
       
   669 
       
   670         Path build() throws IOException {
       
   671             compileModule();
       
   672             return createJmodFile();
       
   673         }
       
   674 
       
   675         private void compileModule() throws IOException  {
       
   676             Path msrc = SRC_DIR.resolve(name);
       
   677             Files.createDirectories(msrc);
       
   678             // copy class using native lib to expected path
       
   679             if (javaClasses.size() > 0) {
       
   680                 for (Path srcPath: javaClasses) {
       
   681                     Path targetPath = msrc.resolve(srcPath.getFileName());
       
   682                     Files.copy(srcPath, targetPath);
       
   683                 }
       
   684             }
       
   685             // generate module-info file.
       
   686             Path minfo = msrc.resolve("module-info.java");
       
   687             try (BufferedWriter bw = Files.newBufferedWriter(minfo);
       
   688                  PrintWriter writer = new PrintWriter(bw)) {
       
   689                 writer.format("module %s { }%n", name);
       
   690             }
       
   691 
       
   692             if (!CompilerUtils.compile(msrc, MODS_DIR,
       
   693                                              "--module-source-path",
       
   694                                              SRC_DIR.toString())) {
       
   695 
       
   696             }
       
   697         }
       
   698 
       
   699         private Path createJmodFile() throws IOException {
       
   700             Path mclasses = MODS_DIR.resolve(name);
       
   701             Files.createDirectories(JMODS_DIR);
       
   702             Path outfile = JMODS_DIR.resolve(name + ".jmod");
       
   703             List<String> args = new ArrayList<>();
       
   704             args.add("create");
       
   705             // add classes
       
   706             args.add("--class-path");
       
   707             args.add(mclasses.toString());
       
   708             // native libs
       
   709             if (nativeLibs.size() > 0) {
       
   710                 // Copy the JNI library to the expected path
       
   711                 Files.createDirectories(LIBS_DIR);
       
   712                 for (Path srcLib: nativeLibs) {
       
   713                     Path targetLib = LIBS_DIR.resolve(srcLib.getFileName());
       
   714                     Files.copy(srcLib, targetLib);
       
   715                 }
       
   716                 args.add("--libs");
       
   717                 args.add(LIBS_DIR.toString());
       
   718             }
       
   719             args.add(outfile.toString());
       
   720 
       
   721             if (Files.exists(outfile)) {
       
   722                 Files.delete(outfile);
       
   723             }
       
   724 
       
   725             System.out.println("jmod " +
       
   726                 args.stream().collect(Collectors.joining(" ")));
       
   727 
       
   728             int rc = JMOD_TOOL.run(System.out, System.out,
       
   729                                    args.toArray(new String[args.size()]));
       
   730             if (rc != 0) {
       
   731                 throw new AssertionError("jmod failed: rc = " + rc);
       
   732             }
       
   733             return outfile;
       
   734         }
       
   735 
       
   736         private static void deleteDirectory(Path dir) throws IOException {
       
   737             try {
       
   738                 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
       
   739                     @Override
       
   740                     public FileVisitResult visitFile(Path file,
       
   741                                                      BasicFileAttributes attrs)
       
   742                         throws IOException
       
   743                     {
       
   744                         Files.delete(file);
       
   745                         return FileVisitResult.CONTINUE;
       
   746                     }
       
   747 
       
   748                     @Override
       
   749                     public FileVisitResult postVisitDirectory(Path dir,
       
   750                                                               IOException exc)
       
   751                         throws IOException
       
   752                     {
       
   753                         Files.delete(dir);
       
   754                         return FileVisitResult.CONTINUE;
       
   755                     }
       
   756                 });
       
   757             } catch (NoSuchFileException e) {
       
   758                 // ignore non-existing files
       
   759             }
       
   760         }
       
   761     }
       
   762 
       
   763     private static class TestObjCopyCmdBuilder implements ObjCopyCmdBuilder {
       
   764 
       
   765         private final String expectedObjCopy;
       
   766         private final String logFile;
       
   767 
       
   768         TestObjCopyCmdBuilder() {
       
   769             this(DEFAULT_OBJCOPY_CMD);
       
   770         }
       
   771         TestObjCopyCmdBuilder(String exptectedObjCopy) {
       
   772             Path logFilePath = Paths.get(FAKE_OBJ_COPY_LOG_FILE);
       
   773             try {
       
   774                 Files.deleteIfExists(logFilePath);
       
   775             } catch (Exception e) {
       
   776                 e.printStackTrace();
       
   777             }
       
   778             this.logFile = logFilePath.toFile().getAbsolutePath();
       
   779             this.expectedObjCopy = exptectedObjCopy;
       
   780         }
       
   781 
       
   782         @Override
       
   783         public List<String> build(String objCopy, String... options) {
       
   784             if (!expectedObjCopy.equals(objCopy)) {
       
   785                 throw new AssertionError("Expected objcopy to be '" +
       
   786                                          expectedObjCopy + "' but was '" +
       
   787                                          objCopy);
       
   788             }
       
   789             List<String> fakeObjCopy = new ArrayList<>();
       
   790             fakeObjCopy.add(JAVA_HOME + File.separator + "bin" + File.separator + "java");
       
   791             fakeObjCopy.add("-cp");
       
   792             fakeObjCopy.add(System.getProperty("test.classes"));
       
   793             fakeObjCopy.add("FakeObjCopy");
       
   794             // Note that adding the gnu debug link changes the PWD of the
       
   795             // java process calling FakeObjCopy. As such we need to pass in the
       
   796             // log file path this way. Relative paths won't work as it would be
       
   797             // relative to the temporary directory which gets deleted post
       
   798             // adding the debug link
       
   799             fakeObjCopy.add(logFile);
       
   800             if (options.length > 0) {
       
   801                 fakeObjCopy.addAll(Arrays.asList(options));
       
   802             }
       
   803             return fakeObjCopy;
       
   804         }
       
   805 
       
   806     }
       
   807 }