|
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 } |