| /* | |
| * Copyright (c) 2016, 2017, 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 /test/lib | |
| * @modules jdk.compiler | |
| * jdk.jlink | |
| * @build jdk.test.lib.compiler.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.HashMap; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.spi.ToolProvider; | |
| import java.util.stream.Collectors; | |
| import java.util.stream.Stream; | |
| import jdk.test.lib.compiler.CompilerUtils; | |
| 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<List<String>, Map<String,String>> LICENSES = Map.of( | |
| // Key is module name and requires | |
| // Value is a map of filename to the file content | |
| List.of("m1"), Map.of("LICENSE", "m1 LICENSE", | |
| "m1-license.txt", "m1 license", | |
| "test-license", "test license v1"), | |
| List.of("m2", "m1"), Map.of("m2-license", "m2 license", | |
| "test-license", "test license v1"), | |
| List.of("m3"), Map.of("m3-license.md", "m3 license", | |
| "test-license", "test license v3"), | |
| List.of("m4"), Map.of("test-license", "test license v4") | |
| ); | |
| @BeforeTest | |
| private void setup() throws Exception { | |
| List<JmodFileBuilder> builders = new ArrayList<>(); | |
| for (Map.Entry<List<String>, Map<String,String>> e : LICENSES.entrySet()) { | |
| List<String> names = e.getKey(); | |
| String mn = names.get(0); | |
| JmodFileBuilder builder = new JmodFileBuilder(mn); | |
| builders.add(builder); | |
| if (names.size() > 1) { | |
| names.subList(1, names.size()) | |
| .stream() | |
| .forEach(builder::requires); | |
| } | |
| e.getValue().entrySet() | |
| .stream() | |
| .forEach(f -> builder.licenseFile(f.getKey(), f.getValue())); | |
| // generate source | |
| builder.writeModuleInfo(); | |
| } | |
| // create jmod file | |
| for (JmodFileBuilder builder: builders) { | |
| builder.build(); | |
| } | |
| } | |
| private String imageDir(String dir) { | |
| return IMAGES_DIR.resolve(dir).toString(); | |
| } | |
| @DataProvider(name = "modules") | |
| public Object[][] jlinkoptions() { | |
| String m2TestLicenseContent = | |
| symlinkContent(Paths.get("legal", "m2", "test-license"), | |
| Paths.get("legal", "m1", "test-license"), | |
| "test license v1"); | |
| // 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", m2TestLicenseContent), | |
| }, | |
| { new String [] { | |
| "test3", | |
| "--add-modules=m2,m3", | |
| }, | |
| 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", m2TestLicenseContent, | |
| "m3/m3-license.md", "m3 license", | |
| "m3/test-license", "test license v3"), | |
| }, | |
| }; | |
| } | |
| 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")) { | |
| // symlink created | |
| return content; | |
| } else { | |
| // tiny file is created | |
| Path symlink = source.getParent().relativize(target); | |
| return String.format("Please see %s", symlink.toString()); | |
| } | |
| } | |
| @Test(dataProvider = "modules") | |
| public void test(String[] opts, Map<String,String> expectedFiles) throws Exception { | |
| if (Files.notExists(MODULE_PATH)) { | |
| // exploded image | |
| return; | |
| } | |
| String dir = opts[0]; | |
| List<String> 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") || | |
| p.getParent().endsWith("m4")) | |
| .forEach(p -> { | |
| String fn = image.resolve("legal").relativize(p) | |
| .toString() | |
| .replace(File.separatorChar, '/'); | |
| 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<String> options = Stream.of("--dedup-legal-notices", | |
| "error-if-not-same-content", | |
| "--module-path", mpath, | |
| "--add-modules=m3,m4", | |
| "--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().trim() | |
| .matches("Error:.*/m4/legal/m4/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, String.format("%s not matched:%nfile: %s%nexpected:%s%n", | |
| file.toString(), new String(bytes), new String(expected))); | |
| } catch (IOException e) { | |
| throw new UncheckedIOException(e); | |
| } | |
| } | |
| private Path createImage(String outputDir, List<String> 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<Path>() { | |
| @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 Set<String> requires = new HashSet<>(); | |
| final Map<String, String> licenses = new HashMap<>(); | |
| JmodFileBuilder(String name) throws IOException { | |
| this.name = name; | |
| Path msrc = SRC_DIR.resolve(name); | |
| if (Files.exists(msrc)) { | |
| deleteDirectory(msrc); | |
| } | |
| } | |
| JmodFileBuilder writeModuleInfo()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); | |
| for (String req : requires) { | |
| writer.format(" requires %s;%n", req); | |
| } | |
| writer.format("}%n"); | |
| } | |
| return this; | |
| } | |
| JmodFileBuilder licenseFile(String filename, String content) { | |
| licenses.put(filename, content); | |
| return this; | |
| } | |
| JmodFileBuilder requires(String name) { | |
| requires.add(name); | |
| return this; | |
| } | |
| Path build() throws IOException { | |
| compileModule(); | |
| Path mdir = LEGAL_DIR.resolve(name); | |
| for (Map.Entry<String,String> 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); | |
| 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<String> 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; | |
| } | |
| } | |
| } |