/** * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8169925 * @summary Validate the license files deduplicated in the image * @library /lib/testlibrary * @modules jdk.compiler * jdk.jlink * @build CompilerUtils * @run testng LegalFilePluginTest */ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UncheckedIOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.spi.ToolProvider; import java.util.stream.Collectors; import java.util.stream.Stream; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.*; public class LegalFilePluginTest { static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") .orElseThrow(() -> new RuntimeException("jmod tool not found") ); static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") .orElseThrow(() -> new RuntimeException("jlink tool not found") ); static final Path MODULE_PATH = Paths.get(System.getProperty("java.home"), "jmods"); static final Path SRC_DIR = Paths.get("src"); static final Path MODS_DIR = Paths.get("mods"); static final Path JMODS_DIR = Paths.get("jmods"); static final Path LEGAL_DIR = Paths.get("legal"); static final Path IMAGES_DIR = Paths.get("images"); static final Map> LICENSES = Map.of( // module name to a map of filename to the file content "m1", Map.of("LICENSE", "m1 LICENSE", "m1-license.txt", "m1 license", "test-license", "test license v1"), "m2", Map.of("m2-license", "m2 license", "test-license", "test license v1"), "m3", Map.of("m3-license.md", "m3 license", "test-license", "test license v2") ); @BeforeTest private void setup() throws Exception { // build jmod files for (String mn : LICENSES.keySet()) { JmodFileBuilder builder = new JmodFileBuilder(mn); LICENSES.get(mn).entrySet() .stream() .forEach(e -> builder.licenseFile(e.getKey(), e.getValue())); builder.build(); } } private String imageDir(String dir) { return IMAGES_DIR.resolve(dir).toString(); } @DataProvider(name = "modules") public Object[][] jlinkoptions() { // options and expected header files & man pages return new Object[][] { { new String [] { "test1", "--add-modules=m1", }, Map.of( "m1/LICENSE", "m1 LICENSE", "m1/m1-license.txt", "m1 license", "m1/test-license", "test license v1") }, { new String [] { "test2", "--add-modules=m1,m2", }, Map.of( "m1/LICENSE", "m1 LICENSE", "m1/m1-license.txt", "m1 license", "m1/test-license", "test license v1", "m2/m2-license", "m2 license", "m2/test-license", symlinkContent(Paths.get("legal", "m2", "test-license"), Paths.get("legal", "m1", "test-license"), "test license v1")), }, { new String [] { "test3", "--add-modules=m2,m3", }, Map.of( "m2/m2-license", "m2 license", "m2/test-license", "test license v1", "m3/m3-license.md", "m3 license", "m3/test-license", "test license v2"), }, }; } private static String symlinkContent(Path source, Path target, String content) { String osName = System.getProperty("os.name"); if (osName.startsWith("Windows") || MODULE_PATH.getFileSystem() .supportedFileAttributeViews() .contains("posix")) { return content; } else { Path symlink = source.getParent().relativize(target); return String.format("Please see %s%n", symlink.toString()); } } @Test(dataProvider = "modules") public void test(String[] opts, Map expectedFiles) throws Exception { if (Files.notExists(MODULE_PATH)) { // exploded image return; } String dir = opts[0]; List options = new ArrayList<>(); for (int i = 1; i < opts.length; i++) { options.add(opts[i]); } String mpath = MODULE_PATH.toString() + File.pathSeparator + JMODS_DIR.toString(); Stream.of("--module-path", mpath, "--output", imageDir(dir)) .forEach(options::add); Path image = createImage(dir, options); Files.walk(image.resolve("legal"), Integer.MAX_VALUE) .filter(p -> Files.isRegularFile(p)) .filter(p -> p.getParent().endsWith("m1") || p.getParent().endsWith("m2") || p.getParent().endsWith("m3")) .forEach(p -> { String fn = image.resolve("legal").relativize(p).toString(); System.out.println(fn); if (!expectedFiles.containsKey(fn)) { throw new RuntimeException(fn + " should not be in the image"); } compareFileContent(p, expectedFiles.get(fn)); }); } @Test public void errorIfNotSameContent() { if (Files.notExists(MODULE_PATH)) { // exploded image return; } String dir = "test"; String mpath = MODULE_PATH.toString() + File.pathSeparator + JMODS_DIR.toString(); List options = Stream.of("--dedup-legal-notices", "error-if-not-same-content", "--module-path", mpath, "--add-modules=m2,m3", "--output", imageDir(dir)) .collect(Collectors.toList()); StringWriter writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); System.out.println("jlink " + options.stream().collect(Collectors.joining(" "))); int rc = JLINK_TOOL.run(pw, pw, options.toArray(new String[0])); assertTrue(rc != 0); assertTrue(writer.toString() .contains("Error: /m3/legal/m3/test-license /m2/legal/m2/test-license" + " contain different content")); } private void compareFileContent(Path file, String content) { try { byte[] bytes = Files.readAllBytes(file); byte[] expected = String.format("%s%n", content).getBytes(); assertEquals(bytes, expected, file.toString() + " not matched"); } catch (IOException e) { throw new UncheckedIOException(e); } } private Path createImage(String outputDir, List options) { System.out.println("jlink " + options.stream().collect(Collectors.joining(" "))); int rc = JLINK_TOOL.run(System.out, System.out, options.toArray(new String[0])); assertTrue(rc == 0); return IMAGES_DIR.resolve(outputDir); } private void deleteDirectory(Path dir) throws IOException { Files.walkFileTree(dir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } /** * Builder to create JMOD file */ class JmodFileBuilder { final String name; final Map licenses = new HashMap<>(); JmodFileBuilder(String name) throws IOException { this.name = name; Path msrc = SRC_DIR.resolve(name); if (Files.exists(msrc)) { deleteDirectory(msrc); } } JmodFileBuilder licenseFile(String filename, String content) { licenses.put(filename, content); return this; } Path build() throws IOException { compileModule(); Path mdir = LEGAL_DIR.resolve(name); for (Map.Entry e : licenses.entrySet()) { Files.createDirectories(mdir); String filename = e.getKey(); String content = e.getValue(); Path file = mdir.resolve(filename); try (BufferedWriter writer = Files.newBufferedWriter(file); PrintWriter pw = new PrintWriter(writer)) { pw.println(content); } } return createJmodFile(); } void compileModule() throws IOException { Path msrc = SRC_DIR.resolve(name); Files.createDirectories(msrc); Path minfo = msrc.resolve("module-info.java"); try (BufferedWriter bw = Files.newBufferedWriter(minfo); PrintWriter writer = new PrintWriter(bw)) { writer.format("module %s { }%n", name); } assertTrue(CompilerUtils.compile(msrc, MODS_DIR, "--module-source-path", SRC_DIR.toString())); } Path createJmodFile() throws IOException { Path mclasses = MODS_DIR.resolve(name); Files.createDirectories(JMODS_DIR); Path outfile = JMODS_DIR.resolve(name + ".jmod"); List args = new ArrayList<>(); args.add("create"); // add classes args.add("--class-path"); args.add(mclasses.toString()); if (licenses.size() > 0) { args.add("--legal-notices"); args.add(LEGAL_DIR.resolve(name).toString()); } args.add(outfile.toString()); if (Files.exists(outfile)) Files.delete(outfile); System.out.println("jmod " + args.stream().collect(Collectors.joining(" "))); int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()])); if (rc != 0) { throw new AssertionError("jmod failed: rc = " + rc); } return outfile; } } }