ant-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bode...@apache.org
Subject [2/6] ant git commit: Added tasks for JDK's jmod and jlink tools.
Date Sat, 15 Dec 2018 16:28:12 GMT
http://git-wip-us.apache.org/repos/asf/ant/blob/343dff90/src/tests/junit/org/apache/tools/ant/taskdefs/modules/JmodTest.java
----------------------------------------------------------------------
diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/modules/JmodTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/modules/JmodTest.java
new file mode 100644
index 0000000..b8cecb9
--- /dev/null
+++ b/src/tests/junit/org/apache/tools/ant/taskdefs/modules/JmodTest.java
@@ -0,0 +1,690 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.tools.ant.taskdefs.modules;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.nio.file.Files;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+import java.util.spi.ToolProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.BuildFileRule;
+
+/**
+ * Tests the {@link Jmod} task.
+ */
+public class JmodTest {
+    @Rule
+    public final BuildFileRule buildRule = new BuildFileRule();
+
+    @Rule
+    public final ExpectedException expected = ExpectedException.none();
+
+    @Before
+    public void setUp() {
+        buildRule.configureProject("src/etc/testcases/taskdefs/jmod.xml");
+        buildRule.executeTarget("setUp");
+    }
+
+    @Test
+    public void testDestAndClasspathNoJmod() {
+        buildRule.executeTarget("destAndClasspathNoJmod");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testDestAndNestedClasspath() {
+        buildRule.executeTarget("classpath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testDestAndClasspathOlderThanJmod()
+    throws IOException {
+        buildRule.executeTarget("destAndClasspathOlderThanJmod");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        File jar = new File(buildRule.getProject().getProperty("hello.jar"));
+        Assert.assertTrue("Checking that newer jmod was not written "
+            + "when source files are older.",
+            Files.getLastModifiedTime(jmod.toPath()).toInstant().isAfter(
+                Instant.now().plus(30, ChronoUnit.MINUTES)));
+    }
+
+    @Test
+    public void testNoDestFile() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("noDestFile");
+    }
+
+    @Test
+    public void testNoClasspath() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("noClasspath");
+    }
+
+    @Test
+    public void testEmptyClasspath() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("emptyClasspath");
+    }
+
+    @Test
+    public void testClasspathEntirelyNonexistent() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("nonexistentClasspath");
+    }
+
+    @Test
+    public void testClasspathref() {
+        buildRule.executeTarget("classpathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testClasspathAttributeAndChildElement() {
+        buildRule.executeTarget("classpath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testModulepath() {
+        buildRule.executeTarget("modulepath");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testModulepathref() {
+        buildRule.executeTarget("modulepathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testModulepathNested() {
+        buildRule.executeTarget("modulepath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testModulepathNonDir() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("modulepathnondir");
+    }
+
+    @Test
+    public void testModulepathAttributeAndChildElement() {
+        buildRule.executeTarget("modulepath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+    }
+
+    @Test
+    public void testCommandPath() {
+        buildRule.executeTarget("commandpath");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains command.",
+            containsLine(output, l -> l.equals("bin/command1")));
+    }
+
+    @Test
+    public void testCommandPathref() {
+        buildRule.executeTarget("commandpathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains command.",
+            containsLine(output, l -> l.equals("bin/command2")));
+    }
+
+    @Test
+    public void testCommandPathNested() {
+        buildRule.executeTarget("commandpath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains command.",
+            containsLine(output, l -> l.equals("bin/command3")));
+    }
+
+    @Test
+    public void testCommandPathAttributeAndChildElement() {
+        buildRule.executeTarget("commandpath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains commands "
+            + "from both attribute and child element.",
+            containsAll(output,
+                l -> l.equals("bin/command4"),
+                l -> l.equals("bin/command5")));
+    }
+
+    @Test
+    public void testHeaderPath() {
+        buildRule.executeTarget("headerpath");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains header file.",
+            containsLine(output, l -> l.equals("include/header1.h")));
+    }
+
+    @Test
+    public void testHeaderPathref() {
+        buildRule.executeTarget("headerpathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains header file.",
+            containsLine(output, l -> l.equals("include/header2.h")));
+    }
+
+    @Test
+    public void testHeaderPathNested() {
+        buildRule.executeTarget("headerpath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains header file.",
+            containsLine(output, l -> l.equals("include/header3.h")));
+    }
+
+    @Test
+    public void testHeaderPathAttributeAndChildElement() {
+        buildRule.executeTarget("headerpath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains header files "
+            + "from both attribute and child element.",
+            containsAll(output,
+                l -> l.equals("include/header4.h"),
+                l -> l.equals("include/header5.h")));
+    }
+
+    @Test
+    public void testConfigPath() {
+        buildRule.executeTarget("configpath");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains config file.",
+            containsLine(output, l -> l.equals("conf/config1.properties")));
+    }
+
+    @Test
+    public void testConfigPathref() {
+        buildRule.executeTarget("configpathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains config file.",
+            containsLine(output, l -> l.equals("conf/config2.properties")));
+    }
+
+    @Test
+    public void testConfigPathNested() {
+        buildRule.executeTarget("configpath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains config file.",
+            containsLine(output, l -> l.equals("conf/config3.properties")));
+    }
+
+    @Test
+    public void testConfigPathAttributeAndChildElement() {
+        buildRule.executeTarget("configpath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains config files "
+            + "from both attribute and child element.",
+            containsAll(output,
+                l -> l.equals("conf/config4.properties"),
+                l -> l.equals("conf/config5.properties")));
+    }
+
+    @Test
+    public void testLegalPath() {
+        buildRule.executeTarget("legalpath");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains license file.",
+            containsLine(output, l -> l.equals("legal/legal1.txt")));
+    }
+
+    @Test
+    public void testLegalPathref() {
+        buildRule.executeTarget("legalpathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains license file.",
+            containsLine(output, l -> l.equals("legal/legal2.txt")));
+    }
+
+    @Test
+    public void testLegalPathNested() {
+        buildRule.executeTarget("legalpath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains license file.",
+            containsLine(output, l -> l.equals("legal/legal3.txt")));
+    }
+
+    @Test
+    public void testLegalPathAttributeAndChildElement() {
+        buildRule.executeTarget("legalpath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains legal files "
+            + "from both attribute and child element.",
+            containsAll(output,
+                l -> l.equals("legal/legal4.txt"),
+                l -> l.equals("legal/legal5.txt")));
+    }
+
+    @Test
+    public void testManPath() {
+        buildRule.executeTarget("manpath");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains man page.",
+            containsLine(output, l -> l.equals("man/man1.1")));
+    }
+
+    @Test
+    public void testManPathref() {
+        buildRule.executeTarget("manpathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains man page.",
+            containsLine(output, l -> l.equals("man/man2.1")));
+    }
+
+    @Test
+    public void testManPathNested() {
+        buildRule.executeTarget("manpath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains man page.",
+            containsLine(output, l -> l.equals("man/man3.1")));
+    }
+
+    @Test
+    public void testManPathAttributeAndChildElement() {
+        buildRule.executeTarget("manpath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains man pages "
+            + "from both attribute and child element.",
+            containsAll(output,
+                l -> l.equals("man/man4.1"),
+                l -> l.equals("man/man5.1")));
+    }
+
+    @Test
+    public void testNativeLibPath() {
+        buildRule.executeTarget("nativelibpath");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains native library.",
+            containsLine(output, l -> l.matches("lib/[^/]+\\.(dll|dylib|so)")));
+    }
+
+    @Test
+    public void testNativeLibPathref() {
+        buildRule.executeTarget("nativelibpathref");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains native library.",
+            containsLine(output, l -> l.matches("lib/[^/]+\\.(dll|dylib|so)")));
+    }
+
+    @Test
+    public void testNativeLibPathNested() {
+        buildRule.executeTarget("nativelibpath-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains native library.",
+            containsLine(output, l -> l.matches("lib/[^/]+\\.(dll|dylib|so)")));
+    }
+
+    @Test
+    public void testNativeLibPathAttributeAndChildElement() {
+        buildRule.executeTarget("nativelibpath-both");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("list", jmod.toString());
+        Assert.assertTrue("Checking that jmod contains native libraries "
+            + "from both attribute and child element.",
+            containsAll(output,
+                l -> l.matches("lib/(lib)?zip\\.(dll|dylib|so)"),
+                l -> l.matches("lib/(lib)?jvm\\.(dll|dylib|so)")));
+    }
+
+    @Test
+    public void testVersion() {
+        buildRule.executeTarget("version");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String version = buildRule.getProject().getProperty("version");
+        Assert.assertNotNull("Checking that 'version' property is set",
+            version);
+        Assert.assertFalse("Checking that 'version' property is not empty",
+            version.isEmpty());
+
+        String output = runJmod("describe", jmod.toString());
+        Assert.assertTrue("Checking that jmod has correct version.",
+            containsLine(output, l -> l.endsWith("@" + version)));
+    }
+
+    @Test
+    public void testNestedVersion() {
+        buildRule.executeTarget("version-nested");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("describe", jmod.toString());
+        Assert.assertTrue("Checking that jmod has correct version.",
+            containsLine(output, l -> l.matches(".*@1\\.0\\.1[-+]+99")));
+    }
+
+    @Test
+    public void testNestedVersionNumberOnly() {
+        buildRule.executeTarget("version-nested-number");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("describe", jmod.toString());
+        Assert.assertTrue("Checking that jmod has correct version.",
+            containsLine(output, l -> l.endsWith("@1.0.1")));
+    }
+
+    @Test
+    public void testNestedVersionNoNumber() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("version-nested-no-number");
+    }
+
+    @Test
+    public void testNestedVersionInvalidNumber() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("version-nested-invalid-number");
+    }
+
+    @Test
+    public void testNestedVersionInvalidPreRelease() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("version-nested-invalid-prerelease");
+    }
+
+    @Test
+    public void testVersionAttributeAndChildElement() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("version-both");
+    }
+
+    @Test
+    public void testMainClass() {
+        buildRule.executeTarget("mainclass");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String mainClass =
+            buildRule.getProject().getProperty("hello.main-class");
+        Assert.assertNotNull("Checking that 'main-class' property is set",
+            mainClass);
+        Assert.assertFalse("Checking that 'main-class' property is not empty",
+            mainClass.isEmpty());
+
+        String output = runJmod("describe", jmod.toString());
+
+        String mainClassPattern = "main-class\\s+" + Pattern.quote(mainClass);
+        Assert.assertTrue("Checking that jmod has correct main class.",
+            containsLine(output, l -> l.matches(mainClassPattern)));
+    }
+
+    @Test
+    public void testPlatform() {
+        buildRule.executeTarget("platform");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String platform = buildRule.getProject().getProperty("target-platform");
+        Assert.assertNotNull("Checking that 'target-platform' property is set",
+            platform);
+        Assert.assertFalse("Checking that 'target-platform' property "
+            + "is not empty", platform.isEmpty());
+
+        String output = runJmod("describe", jmod.toString());
+
+        String platformPattern = "platform\\s+" + Pattern.quote(platform);
+        Assert.assertTrue("Checking that jmod has correct main class.",
+            containsLine(output, l -> l.matches(platformPattern)));
+    }
+
+    @Test
+    public void testHashing() {
+        buildRule.executeTarget("hashing");
+
+        File jmod = new File(buildRule.getProject().getProperty("jmod"));
+        Assert.assertTrue("Checking that jmod was successfully created.",
+            jmod.exists());
+
+        String output = runJmod("describe", jmod.toString());
+
+        Assert.assertTrue("Checking that jmod has module hashes.",
+            containsLine(output, l -> l.startsWith("hashes")));
+    }
+
+    private String runJmod(final String... args) {
+        ToolProvider jmod = ToolProvider.findFirst("jmod").orElseThrow(
+            () -> new RuntimeException("jmod tool not found in JDK."));
+
+        ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+        ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+
+        int exitCode;
+        try (PrintStream out = new PrintStream(stdout);
+             PrintStream err = new PrintStream(stderr)) {
+
+            exitCode = jmod.run(out, err, args);
+        }
+
+        if (exitCode != 0) {
+            throw new RuntimeException(
+                "jmod failed, output is: " + stdout + ", error is: " + stderr);
+        }
+
+        return stdout.toString();
+    }
+
+    private boolean containsLine(final String lines,
+                                 final Predicate<? super String> test) {
+        try (BufferedReader reader =
+            new BufferedReader(new StringReader(lines))) {
+
+            return reader.lines().anyMatch(test);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private boolean containsAll(final String lines,
+                                final Predicate<? super String> test1,
+                                final Predicate<? super String> test2) {
+
+        try (BufferedReader reader =
+            new BufferedReader(new StringReader(lines))) {
+
+            boolean test1Matched = false;
+            boolean test2Matched = false;
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                test1Matched |= test1.test(line);
+                test2Matched |= test2.test(line);
+            }
+
+            return test1Matched && test2Matched;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/343dff90/src/tests/junit/org/apache/tools/ant/taskdefs/modules/LinkTest.java
----------------------------------------------------------------------
diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/modules/LinkTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/modules/LinkTest.java
new file mode 100644
index 0000000..9d89b77
--- /dev/null
+++ b/src/tests/junit/org/apache/tools/ant/taskdefs/modules/LinkTest.java
@@ -0,0 +1,984 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.tools.ant.taskdefs.modules;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.FileReader;
+import java.io.StringReader;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.spi.ToolProvider;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.BuildFileRule;
+
+/**
+ * Tests the {@link Link} task.
+ */
+public class LinkTest {
+    /*
+     * TODO:
+     * Test --order-resources (how?)
+     * Test --exclude-files (what does this actually do?)
+     * Test --endian (how?)
+     * Test --vm (how?)
+     */
+
+    @Rule
+    public final BuildFileRule buildRule = new BuildFileRule();
+
+    @Rule
+    public final ExpectedException expected = ExpectedException.none();
+
+    @Before
+    public void setUp() {
+        buildRule.configureProject("src/etc/testcases/taskdefs/link.xml");
+        buildRule.executeTarget("setUp");
+    }
+
+    private static boolean isWindows() {
+        return System.getProperty("os.name").contains("Windows");
+    }
+
+    private static boolean isEarlierThan(final Instant time,
+                                         final Path path) {
+        try {
+            return Files.getLastModifiedTime(path).toInstant().isBefore(time);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private static class ImageStructure {
+        final File root;
+        final File bin;
+        final File java;
+
+        ImageStructure(final File root) {
+            this.root = root;
+
+            bin = new File(root, "bin");
+            java = new File(bin, isWindows() ? "java.exe" : "java");
+        }
+    }
+
+    private ImageStructure verifyImageBuiltNormally() {
+        ImageStructure image = new ImageStructure(
+            new File(buildRule.getProject().getProperty("image")));
+
+        Assert.assertTrue("Checking that image was successfully created.",
+            image.root.exists());
+
+        Assert.assertTrue("Checking that image has java executable.",
+            image.java.exists());
+
+        return image;
+    }
+
+    @Test
+    public void testModulepath() {
+        buildRule.executeTarget("modulepath");
+        verifyImageBuiltNormally();
+    }
+
+    @Test
+    public void testImageNotRecreatedFromStaleJmods()
+    throws IOException {
+        buildRule.executeTarget("imageNewerThanJmods");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        Instant future = Instant.now().plus(30, ChronoUnit.MINUTES);
+        try (Stream<Path> imageFiles = Files.walk(image.root.toPath())) {
+
+            Assert.assertTrue("Checking that newer image was not written "
+                + "when source files are older.",
+                imageFiles.noneMatch(i -> isEarlierThan(future, i)));
+        }
+    }
+
+    @Test
+    public void testNoModulePath() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("nomodulepath");
+    }
+
+    @Test
+    public void testNoModules() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("nomodules");
+    }
+
+    @Test
+    public void testModulePathRef() {
+        buildRule.executeTarget("modulepathref");
+        verifyImageBuiltNormally();
+    }
+
+    @Test
+    public void testNestedModulePath() {
+        buildRule.executeTarget("modulepath-nested");
+        verifyImageBuiltNormally();
+    }
+
+    @Test
+    public void testModulePathInAttributeAndNested() {
+        buildRule.executeTarget("modulepath-both");
+        verifyImageBuiltNormally();
+    }
+
+    @Test
+    public void testNestedModules()
+    throws IOException,
+           InterruptedException {
+
+        buildRule.executeTarget("modules-nested");
+
+        ImageStructure image = verifyImageBuiltNormally();
+
+        ProcessBuilder builder = new ProcessBuilder(
+            image.java.toString(),
+            buildRule.getProject().getProperty("hello.main-class"));
+        builder.inheritIO();
+        int exitCode = builder.start().waitFor();
+        Assert.assertEquals(
+            "Checking that execution of first module succeeded.", 0, exitCode);
+
+        builder.command(
+            image.java.toString(),
+            buildRule.getProject().getProperty("smile.main-class"));
+        exitCode = builder.start().waitFor();
+        Assert.assertEquals(
+            "Checking that execution of second module succeeded.", 0, exitCode);
+    }
+
+    @Test
+    public void testNestedModuleMissingName() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("modules-nested-missing-name");
+    }
+
+    @Test
+    public void testModulesInAttributeAndNested() {
+        buildRule.executeTarget("modules-both");
+        verifyImageBuiltNormally();
+    }
+
+    @Test
+    public void testObservableModules() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("observable");
+    }
+
+    @Test
+    public void testNestedObservableModules() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("observable-nested");
+    }
+
+    @Test
+    public void testNestedObservableModuleMissingName() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("observable-nested-missing-name");
+    }
+
+    @Test
+    public void testObservableModulesInAttributeAndNested() {
+        buildRule.executeTarget("observable-both");
+        verifyImageBuiltNormally();
+    }
+
+    private void verifyLaunchersExist() {
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File launcher1 =
+            new File(image.bin, isWindows() ? "Hello.bat" : "Hello");
+        Assert.assertTrue("Checking that image has 'Hello' launcher.",
+            launcher1.exists());
+
+        File launcher2 =
+            new File(image.bin, isWindows() ? "Smile.bat" : "Smile");
+        Assert.assertTrue("Checking that image has 'Smile' launcher.",
+            launcher2.exists());
+    }
+
+    @Test
+    public void testLaunchers() {
+        buildRule.executeTarget("launchers");
+        verifyLaunchersExist();
+    }
+
+    @Test
+    public void testNestedLaunchers() {
+        buildRule.executeTarget("launchers-nested");
+        verifyLaunchersExist();
+    }
+
+    @Test
+    public void testNestedLauncherMissingName() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("launchers-nested-missing-name");
+    }
+
+    @Test
+    public void testNestedLauncherMissingModule() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("launchers-nested-missing-module");
+    }
+
+    @Test
+    public void testLaunchersInAttributeAndNested() {
+        buildRule.executeTarget("launchers-both");
+        verifyLaunchersExist();
+    }
+
+    private void verifyLocales()
+    throws IOException,
+           InterruptedException {
+
+        ImageStructure image = verifyImageBuiltNormally();
+
+        String mainClass =
+            buildRule.getProject().getProperty("localefinder.main-class");
+        Assert.assertNotNull("Checking that main-class property exists",
+            mainClass);
+
+        ProcessBuilder builder =
+            new ProcessBuilder(image.java.toString(), mainClass, "zh", "in");
+        builder.inheritIO();
+        int exitCode = builder.start().waitFor();
+
+        Assert.assertEquals("Verifying that image has access to locales "
+            + "specified during linking.", 0, exitCode);
+
+        builder.command(image.java.toString(), mainClass, "ja");
+        exitCode = builder.start().waitFor();
+
+        Assert.assertNotEquals(
+            "Verifying that image does not have access to locales "
+            + "not specified during linking.", 0, exitCode);
+    }
+
+    @Test
+    public void testLocales()
+    throws IOException,
+           InterruptedException {
+
+        buildRule.executeTarget("locales");
+        verifyLocales();
+    }
+
+    @Test
+    public void testNestedLocales()
+    throws IOException,
+           InterruptedException {
+
+        buildRule.executeTarget("locales-nested");
+        verifyLocales();
+    }
+
+    @Test
+    public void testNestedLocaleMissingName() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("locales-nested-missing-name");
+    }
+
+    @Test
+    public void testLocalesInAttributeAndNested()
+    throws IOException,
+           InterruptedException {
+
+        buildRule.executeTarget("locales-both");
+        verifyLocales();
+    }
+
+    @Test
+    public void testExcludeResources()
+    throws IOException {
+        buildRule.executeTarget("excluderesources");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        String mainClass =
+            buildRule.getProject().getProperty("hello.main-class");
+        Assert.assertNotNull("Checking that main-class property exists",
+            mainClass);
+
+        ProcessBuilder builder =
+            new ProcessBuilder(image.java.toString(), mainClass,
+                "resource1.txt", "resource2.txt");
+        builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        builder.redirectErrorStream(true);
+
+        Collection<String> outputLines;
+        Process process = builder.start();
+        try (BufferedReader reader = new BufferedReader(
+            new InputStreamReader(process.getInputStream()))) {
+
+            outputLines = reader.lines().collect(Collectors.toList());
+        }
+
+        Assert.assertTrue(
+            "Checking that excluded resource is actually excluded.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource1.txt absent")));
+
+        Assert.assertTrue(
+            "Checking that resource not excluded is present.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource2.txt present")));
+    }
+
+    @Test
+    public void testNestedExcludeResources()
+    throws IOException {
+        buildRule.executeTarget("excluderesources-nested");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        String mainClass =
+            buildRule.getProject().getProperty("hello.main-class");
+        Assert.assertNotNull("Checking that main-class property exists",
+            mainClass);
+
+        ProcessBuilder builder =
+            new ProcessBuilder(image.java.toString(), mainClass,
+                "resource1.txt", "resource2.txt");
+        builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        builder.redirectErrorStream(true);
+
+        Collection<String> outputLines;
+        Process process = builder.start();
+        try (BufferedReader reader = new BufferedReader(
+            new InputStreamReader(process.getInputStream()))) {
+
+            outputLines = reader.lines().collect(Collectors.toList());
+        }
+
+        Assert.assertTrue(
+            "Checking that excluded resource is actually excluded.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource1.txt absent")));
+
+        Assert.assertTrue(
+            "Checking that resource not excluded is present.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource2.txt present")));
+    }
+
+    @Test
+    public void testNestedExcludeResourcesFile()
+    throws IOException {
+        buildRule.executeTarget("excluderesources-nested-file");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        String mainClass =
+            buildRule.getProject().getProperty("hello.main-class");
+        Assert.assertNotNull("Checking that main-class property exists",
+            mainClass);
+
+        ProcessBuilder builder =
+            new ProcessBuilder(image.java.toString(), mainClass,
+                "resource1.txt", "resource2.txt");
+        builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        builder.redirectErrorStream(true);
+
+        Collection<String> outputLines;
+        Process process = builder.start();
+        try (BufferedReader reader = new BufferedReader(
+            new InputStreamReader(process.getInputStream()))) {
+
+            outputLines = reader.lines().collect(Collectors.toList());
+        }
+
+        Assert.assertTrue(
+            "Checking that excluded resource is actually excluded.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource1.txt absent")));
+
+        Assert.assertTrue(
+            "Checking that resource not excluded is present.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource2.txt present")));
+    }
+
+    @Test
+    public void testNestedExcludeResourcesNoAttributes() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("excluderesources-nested-no-attr");
+    }
+
+    @Test
+    public void testNestedExcludeResourcesFileAndPattern() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("excluderesources-nested-both");
+    }
+
+    @Test
+    public void testExcludeResourcesAttributeAndNested()
+    throws IOException {
+        buildRule.executeTarget("excluderesources-both");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        String mainClass =
+            buildRule.getProject().getProperty("hello.main-class");
+        Assert.assertNotNull("Checking that main-class property exists",
+            mainClass);
+
+        ProcessBuilder builder =
+            new ProcessBuilder(image.java.toString(), mainClass,
+                "resource1.txt", "resource2.txt");
+        builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        builder.redirectErrorStream(true);
+
+        Collection<String> outputLines;
+        Process process = builder.start();
+        try (BufferedReader reader = new BufferedReader(
+            new InputStreamReader(process.getInputStream()))) {
+
+            outputLines = reader.lines().collect(Collectors.toList());
+        }
+
+        Assert.assertTrue(
+            "Checking that first excluded resource is actually excluded.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource1.txt absent")));
+
+        Assert.assertTrue(
+            "Checking that second excluded resource is actually excluded.",
+            outputLines.stream().anyMatch(
+                l -> l.endsWith("resource2.txt absent")));
+    }
+
+    @Test
+    public void testExcludeFiles()
+    throws IOException {
+        buildRule.executeTarget("excludefiles");
+        verifyImageBuiltNormally();
+        // TODO: Test created image (what does --exclude-files actually do?)
+    }
+
+    @Test
+    public void testNestedExcludeFiles()
+    throws IOException {
+        buildRule.executeTarget("excludefiles-nested");
+        verifyImageBuiltNormally();
+        // TODO: Test created image (what does --exclude-files actually do?)
+    }
+
+    @Test
+    public void testNestedExcludeFilesFile()
+    throws IOException {
+        buildRule.executeTarget("excludefiles-nested-file");
+        ImageStructure image = verifyImageBuiltNormally();
+        // TODO: Test created image (what does --exclude-files actually do?)
+    }
+
+    @Test
+    public void testNestedExcludeFilesNoAttributes() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("excludefiles-nested-no-attr");
+    }
+
+    @Test
+    public void testNestedExcludeFilesFileAndPattern() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("excludefiles-nested-both");
+    }
+
+    @Test
+    public void testExcludeFilesAttributeAndNested()
+    throws IOException {
+        buildRule.executeTarget("excludefiles-both");
+        verifyImageBuiltNormally();
+        // TODO: Test created image (what does --exclude-files actually do?)
+    }
+
+    @Test
+    public void testOrdering()
+    throws IOException {
+        buildRule.executeTarget("ordering");
+        verifyImageBuiltNormally();
+        // TODO: Test resource order in created image (how?)
+    }
+
+    @Test
+    public void testNestedOrdering()
+    throws IOException {
+        buildRule.executeTarget("ordering-nested");
+        verifyImageBuiltNormally();
+        // TODO: Test resource order in created image (how?)
+    }
+
+    @Test
+    public void testNestedOrderingListFile()
+    throws IOException {
+        buildRule.executeTarget("ordering-nested-file");
+        ImageStructure image = verifyImageBuiltNormally();
+        // TODO: Test resource order in created image (how?)
+    }
+
+    @Test
+    public void testNestedOrderingNoAttributes() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("ordering-nested-no-attr");
+    }
+
+    @Test
+    public void testNestedOrderingFileAndPattern() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("ordering-nested-both");
+    }
+
+    @Test
+    public void testOrderingAttributeAndNested()
+    throws IOException {
+        buildRule.executeTarget("ordering-both");
+        verifyImageBuiltNormally();
+        // TODO: Test resource order in created image (how?)
+    }
+
+    @Test
+    public void testIncludeHeaders() {
+        buildRule.executeTarget("includeheaders");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File[] headers = new File(image.root, "include").listFiles();
+        Assert.assertTrue("Checking that include files were omitted.",
+            headers == null || headers.length == 0);
+    }
+
+    @Test
+    public void testIncludeManPages() {
+        buildRule.executeTarget("includemanpages");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File[] manPages = new File(image.root, "man").listFiles();
+        Assert.assertTrue("Checking that man pages were omitted.",
+            manPages == null || manPages.length == 0);
+    }
+
+    @Test
+    public void testIncludeNativeCommands() {
+        buildRule.executeTarget("includenativecommands");
+        ImageStructure image = new ImageStructure(
+            new File(buildRule.getProject().getProperty("image")));
+
+        Assert.assertTrue("Checking that image was successfully created.",
+            image.root.exists());
+
+        Assert.assertFalse(
+            "Checking that image was stripped of java executable.",
+            image.java.exists());
+    }
+
+    private long totalSizeOf(final Path path)
+    throws IOException {
+        if (Files.isDirectory(path)) {
+            long size = 0;
+            try (DirectoryStream<Path> children = Files.newDirectoryStream(path)) {
+                for (Path child : children) {
+                    size += totalSizeOf(child);
+                }
+            }
+            return size;
+        }
+
+        if (Files.isRegularFile(path)) {
+            return Files.size(path);
+        }
+
+        return 0;
+    }
+
+    @Test
+    public void testCompression()
+    throws IOException {
+        buildRule.executeTarget("compression");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File compressedImageRoot =
+            new File(buildRule.getProject().getProperty("compressed-image"));
+
+        long size = totalSizeOf(image.root.toPath());
+        long compressedSize = totalSizeOf(compressedImageRoot.toPath());
+
+        Assert.assertTrue("Checking that compression resulted in smaller image.",
+            compressedSize < size);
+    }
+
+    @Test
+    public void testNestedCompression()
+    throws IOException {
+        buildRule.executeTarget("compression-nested");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File compressedImageRoot =
+            new File(buildRule.getProject().getProperty("compressed-image"));
+
+        long size = totalSizeOf(image.root.toPath());
+        long compressedSize = totalSizeOf(compressedImageRoot.toPath());
+
+        Assert.assertTrue("Checking that compression resulted in smaller image.",
+            compressedSize < size);
+    }
+
+    @Test
+    public void testNestedCompressionNoAttributes() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("compression-nested-no-attr");
+    }
+
+    @Test
+    public void testNestedCompressionAttributeAndNested() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("compression-both");
+    }
+
+    @Test
+    public void testEndian() {
+        buildRule.executeTarget("endian");
+        verifyImageBuiltNormally();
+        // TODO: How can we test the created image?  Which files does --endian
+        // affect?
+    }
+
+    @Test
+    public void testVMType() {
+        buildRule.executeTarget("vm");
+        verifyImageBuiltNormally();
+        // TODO: How can we test the created image?  Which files does --vm
+        // affect?
+    }
+
+    @Test
+    public void testReleaseInfoFile()
+    throws IOException {
+        buildRule.executeTarget("releaseinfo-file");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File release = new File(image.root, "release");
+        try (BufferedReader reader =
+            Files.newBufferedReader(release.toPath())) {
+
+            Assert.assertTrue("Checking for 'test=true' in image release info.",
+                reader.lines().anyMatch(l -> l.equals("test=true")));
+        }
+    }
+
+    @Test
+    public void testReleaseInfoDelete()
+    throws IOException {
+        buildRule.executeTarget("releaseinfo-delete");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File release = new File(image.root, "release");
+        try (BufferedReader reader =
+            Files.newBufferedReader(release.toPath())) {
+
+            Assert.assertFalse("Checking that 'test' was deleted "
+                + "from image release info.",
+                reader.lines().anyMatch(l -> l.startsWith("test=")));
+        }
+    }
+
+    @Test
+    public void testReleaseInfoNestedDelete()
+    throws IOException {
+        buildRule.executeTarget("releaseinfo-nested-delete");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File release = new File(image.root, "release");
+        try (BufferedReader reader =
+            Files.newBufferedReader(release.toPath())) {
+
+            Assert.assertFalse("Checking that 'test' was deleted "
+                + "from image release info.",
+                reader.lines().anyMatch(l -> l.startsWith("test=")));
+        }
+    }
+
+    @Test
+    public void testReleaseInfoNestedDeleteNoKey() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("releaseinfo-nested-delete-no-key");
+    }
+
+    @Test
+    public void testReleaseInfoDeleteAttributeAndNested()
+    throws IOException {
+        buildRule.executeTarget("releaseinfo-nested-delete-both");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File release = new File(image.root, "release");
+        try (BufferedReader reader =
+            Files.newBufferedReader(release.toPath())) {
+
+            Assert.assertTrue(
+                "Checking that 'test' and 'foo' were deleted "
+                + "from image release info.",
+                reader.lines().noneMatch(l ->
+                    l.startsWith("test=") || l.startsWith("foo=")));
+        }
+    }
+
+    @Test
+    public void testReleaseInfoAddFile()
+    throws IOException {
+        buildRule.executeTarget("releaseinfo-add-file");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File release = new File(image.root, "release");
+        try (BufferedReader reader = new BufferedReader(
+            new FileReader(release))) {
+
+            Assert.assertTrue("Checking that 'test=s\u00ed' was added "
+                + "to image release info.",
+                reader.lines().anyMatch(l -> l.equals("test=s\u00ed")));
+        }
+    }
+
+    @Test
+    public void testReleaseInfoAddFileWithCharset()
+    throws IOException {
+        buildRule.executeTarget("releaseinfo-add-file-charset");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File release = new File(image.root, "release");
+        // Using FileReader here since 'release' file is in platform's charset.
+        try (BufferedReader reader = new BufferedReader(
+            new FileReader(release))) {
+
+            Assert.assertTrue("Checking that 'test=s\u00ed' was added "
+                + "to image release info.",
+                reader.lines().anyMatch(l -> l.equals("test=s\u00ed")));
+        }
+    }
+
+    @Test
+    public void testReleaseInfoAddKeyAndValue()
+    throws IOException {
+        buildRule.executeTarget("releaseinfo-add-key");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        File release = new File(image.root, "release");
+        try (BufferedReader reader =
+            Files.newBufferedReader(release.toPath())) {
+
+            Assert.assertTrue("Checking that 'test=true' was added "
+                + "to image release info.",
+                reader.lines().anyMatch(l -> l.equals("test=true")));
+        }
+    }
+
+    @Test
+    public void testReleaseInfoAddNoValue() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("releaseinfo-add-no-value");
+    }
+
+    @Test
+    public void testReleaseInfoAddNoKey() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("releaseinfo-add-no-key");
+    }
+
+    @Test
+    public void testReleaseInfoAddFileAndKey() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("releaseinfo-add-file-and-key");
+    }
+
+    @Test
+    public void testReleaseInfoAddFileAndValue() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("releaseinfo-add-file-and-value");
+    }
+
+    @Test
+    public void testDebugStripping()
+    throws IOException,
+           InterruptedException {
+
+        buildRule.executeTarget("debug");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        ProcessBuilder builder = new ProcessBuilder(
+            image.java.toString(),
+            buildRule.getProject().getProperty("thrower.main-class"));
+        builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        builder.redirectErrorStream(true);
+
+        Process process = builder.start();
+        try (BufferedReader linesReader = new BufferedReader(
+            new InputStreamReader(process.getInputStream()))) {
+
+            Assert.assertTrue(
+                "Checking that stack trace contains no debug information.",
+                linesReader.lines().noneMatch(
+                    l -> l.matches(".*\\([^)]*:[0-9]+\\)")));
+        }
+        process.waitFor();
+    }
+
+    @Test
+    public void testDeduplicationOfLicenses() {
+        buildRule.executeTarget("dedup");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        String helloModuleName =
+            buildRule.getProject().getProperty("hello.mod");
+        String smileModuleName =
+            buildRule.getProject().getProperty("smile.mod");
+
+        Assert.assertNotNull("Checking that 'hello.mod' property was set.",
+            helloModuleName);
+        Assert.assertNotNull("Checking that 'smile.mod' property was set.",
+            smileModuleName);
+
+        Assume.assumeFalse("Checking that this operating system"
+            + " supports symbolic links as a means of license de-duplication.",
+            System.getProperty("os.name").contains("Windows"));
+
+        Path legal = image.root.toPath().resolve("legal");
+
+        Path[] licenses = {
+            legal.resolve(helloModuleName).resolve("USELESSLICENSE"),
+            legal.resolve(smileModuleName).resolve("USELESSLICENSE"),
+        };
+
+        int nonLinkCount = 0;
+        for (Path license : licenses) {
+            if (!Files.isSymbolicLink(license)) {
+                nonLinkCount++;
+            }
+        }
+
+        Assert.assertEquals(
+            "Checking that USELESSLICENSE only exists once in image "
+            + "and all other instances are links to it.",
+            1, nonLinkCount);
+    }
+
+    @Test
+    public void testIgnoreSigning() {
+        buildRule.executeTarget("ignoresigning");
+        verifyImageBuiltNormally();
+    }
+
+    /**
+     * Should fail due to jlink rejecting identically named files whose
+     * contents are different.
+     */
+    @Test
+    public void testDeduplicationOfInconsistentLicenses() {
+        expected.expect(BuildException.class);
+        buildRule.executeTarget("dedup-identical");
+    }
+
+    @Test
+    public void testBindingOfServices()
+    throws IOException,
+           InterruptedException {
+        buildRule.executeTarget("bindservices");
+        ImageStructure image = verifyImageBuiltNormally();
+
+        String mainClass = buildRule.getProject().getProperty("inc.main-class");
+
+        ProcessBuilder builder = new ProcessBuilder(
+            image.java.toString(), mainClass);
+        builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        builder.redirectError(ProcessBuilder.Redirect.INHERIT);
+
+        Process process = builder.start();
+        try (BufferedReader linesReader = new BufferedReader(
+            new InputStreamReader(process.getInputStream()))) {
+
+            Assert.assertEquals(
+                "Checking that bindServices=false results in no providers in image.",
+                0, linesReader.lines().count());
+        }
+        process.waitFor();
+
+        image = new ImageStructure(
+            new File(buildRule.getProject().getProperty("image2")));
+
+        Assert.assertTrue("Checking that image2 was successfully created.",
+            image.root.exists());
+        Assert.assertTrue("Checking that image2 has java executable.",
+            image.java.exists());
+
+        builder = new ProcessBuilder(image.java.toString(), mainClass);
+        builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
+        builder.redirectError(ProcessBuilder.Redirect.INHERIT);
+
+        process = builder.start();
+        try (BufferedReader linesReader = new BufferedReader(
+            new InputStreamReader(process.getInputStream()))) {
+
+            Assert.assertEquals(
+                "Checking that bindServices=true results in image with provider.",
+                5, linesReader.lines().count());
+        }
+        process.waitFor();
+    }
+
+    private String runJlink(final String... args) {
+        ToolProvider jlink = ToolProvider.findFirst("jlink").orElseThrow(
+            () -> new RuntimeException("jlink tool not found in JDK."));
+
+        ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+        ByteArrayOutputStream stderr = new ByteArrayOutputStream();
+
+        int exitCode;
+        try (PrintStream out = new PrintStream(stdout);
+             PrintStream err = new PrintStream(stderr)) {
+
+            exitCode = jlink.run(out, err, args);
+        }
+
+        if (exitCode != 0) {
+            throw new RuntimeException(
+                "jlink failed, output is: " + stdout + ", error is: " + stderr);
+        }
+
+        return stdout.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/343dff90/src/tests/junit/org/apache/tools/ant/types/ModuleVersionTest.java
----------------------------------------------------------------------
diff --git a/src/tests/junit/org/apache/tools/ant/types/ModuleVersionTest.java b/src/tests/junit/org/apache/tools/ant/types/ModuleVersionTest.java
new file mode 100644
index 0000000..b3cf904
--- /dev/null
+++ b/src/tests/junit/org/apache/tools/ant/types/ModuleVersionTest.java
@@ -0,0 +1,115 @@
+package org.apache.tools.ant.types;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Tests {@link ModuleVersion} class.
+ */
+public class ModuleVersionTest {
+    @Rule
+    public final ExpectedException expected = ExpectedException.none();
+
+    @Test
+    public void testModuleVersionStringNumberPreBuild() {
+        ModuleVersion moduleVersion = new ModuleVersion();
+
+        moduleVersion.setNumber("1.1.3");
+        moduleVersion.setPreRelease("ea");
+        moduleVersion.setBuild("25");
+
+        String versionStr = moduleVersion.toModuleVersionString();
+
+        Assert.assertNotNull("Checking for non-null module version string.",
+            versionStr);
+        Assert.assertTrue("Checking for correct module version string.",
+            versionStr.matches("1\\.1\\.3[-+]ea\\+25"));
+    }
+
+    @Test
+    public void testModuleVersionStringNumberPre() {
+        ModuleVersion moduleVersion = new ModuleVersion();
+
+        moduleVersion.setNumber("1.1.3");
+        moduleVersion.setPreRelease("ea");
+
+        String versionStr = moduleVersion.toModuleVersionString();
+
+        Assert.assertNotNull("Checking for non-null module version string.",
+            versionStr);
+        Assert.assertTrue("Checking for correct module version string.",
+            versionStr.matches("1\\.1\\.3[-+]ea"));
+    }
+
+    @Test
+    public void testModuleVersionStringNumberBuild() {
+        ModuleVersion moduleVersion = new ModuleVersion();
+
+        moduleVersion.setNumber("1.1.3");
+        moduleVersion.setBuild("25");
+
+        String versionStr = moduleVersion.toModuleVersionString();
+
+        Assert.assertNotNull("Checking for non-null module version string.",
+            versionStr);
+        Assert.assertTrue("Checking for correct module version string.",
+            versionStr.matches("1\\.1\\.3[-+]\\+25"));
+    }
+
+    @Test
+    public void testModuleVersionStringNumberOnly() {
+        ModuleVersion moduleVersion = new ModuleVersion();
+
+        moduleVersion.setNumber("1.1.3");
+
+        String versionStr = moduleVersion.toModuleVersionString();
+
+        Assert.assertNotNull("Checking for non-null module version string.",
+            versionStr);
+        Assert.assertEquals("Checking for correct module version string.",
+            "1.1.3", versionStr);
+    }
+
+    @Test
+    public void testModuleVersionStringNullNumber() {
+        expected.expect(IllegalStateException.class);
+
+        ModuleVersion moduleVersion = new ModuleVersion();
+        moduleVersion.toModuleVersionString();
+    }
+
+    @Test
+    public void testNullNumber() {
+        expected.expect(NullPointerException.class);
+
+        ModuleVersion moduleVersion = new ModuleVersion();
+        moduleVersion.setNumber(null);
+    }
+
+    @Test
+    public void testInvalidNumber() {
+        expected.expect(IllegalArgumentException.class);
+
+        ModuleVersion moduleVersion = new ModuleVersion();
+        moduleVersion.setNumber("1-1-3");
+    }
+
+    @Test
+    public void testInvalidNumber2() {
+        expected.expect(IllegalArgumentException.class);
+
+        ModuleVersion moduleVersion = new ModuleVersion();
+        moduleVersion.setNumber("1.1+3");
+    }
+
+    @Test
+    public void testInvalidPreRelease() {
+        expected.expect(IllegalArgumentException.class);
+
+        ModuleVersion moduleVersion = new ModuleVersion();
+        moduleVersion.setNumber("1.1.3");
+        moduleVersion.setPreRelease("ea+interim");
+    }
+}


Mime
View raw message