test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java
author herrick
Wed, 16 Oct 2019 10:32:08 -0400
branchJDK-8200758-branch
changeset 58648 3bf53ffa9ae7
child 58695 64adf683bc7b
permissions -rw-r--r--
8232279 : Improve test helpers #2 Submitted-by: asemenyuk Reviewed-by: aherrick, almatvee

/*
 * Copyright (c) 2019, 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.
 */

package jdk.jpackage.tests;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.*;
import jdk.jpackage.test.Functional.ThrowingConsumer;
import static jdk.jpackage.tests.MainClassTest.Script.MainClassType.*;


/*
 * @test
 * @summary test different settings of main class name for jpackage
 * @library ../../../../helpers
 * @build jdk.jpackage.test.*
 * @modules jdk.jpackage/jdk.jpackage.internal
 * @compile MainClassTest.java
 * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
 *  --jpt-run=jdk.jpackage.tests.MainClassTest
 *  --jpt-space-subst=_
 *  --jpt-exclude=modular=y;_main-class=n;_jar-main-class=b;_jlink=y
 *  --jpt-exclude=modular=y;_main-class=n;_jar-main-class=n;_jlink=n
 */

public final class MainClassTest {

    static final class Script {
        Script() {
            appDesc = JavaAppDesc.parse("test.Hello");
        }

        Script modular(boolean v) {
            appDesc.setModuleName(v ? null : "com.other");
            return this;
        }

        Script withJLink(boolean v) {
            withJLink = v;
            return this;
        }

        Script withMainClass(MainClassType v) {
            mainClass = v;
            return this;
        }

        Script withJarMainClass(MainClassType v) {
            appDesc.setJarWithMainClass(v != NotSet);
            jarMainClass = v;
            return this;
        }

        Script expectedErrorMessage(String v) {
            expectedErrorMessage = v;
            return this;
        }

        @Override
        public String toString() {
            return Stream.of(
                    format("modular", appDesc.moduleName() != null ? 'y' : 'n'),
                    format("main-class", mainClass),
                    format("jar-main-class", jarMainClass),
                    format("jlink", withJLink ? 'y' : 'n'),
                    format("error", expectedErrorMessage)
            ).filter(Objects::nonNull).collect(Collectors.joining("; "));
        }

        private static String format(String key, Object value) {
            if (value == null) {
                return null;
            }
            return String.join("=", key, value.toString());
        }

        enum MainClassType {
            NotSet("n"),
            SetWrong("b"),
            SetRight("y");

            MainClassType(String label) {
                this.label = label;
            }

            @Override
            public String toString() {
                return label;
            }

            private final String label;
        };

        private JavaAppDesc appDesc;
        private boolean withJLink;
        private MainClassType mainClass;
        private MainClassType jarMainClass;
        private String expectedErrorMessage;
    }

    public MainClassTest(Script script) {
        this.script = script;

        nonExistingMainClass = Stream.of(
                script.appDesc.packageName(), "ThereIsNoSuchClass").filter(
                Objects::nonNull).collect(Collectors.joining("."));

        cmd = JPackageCommand.helloAppImage(script.appDesc);
        if (!script.withJLink) {
            cmd.setFakeRuntime();
        }

        final String moduleName = script.appDesc.moduleName();
        switch (script.mainClass) {
            case NotSet:
                if (moduleName != null) {
                    // Don't specify class name, only module name.
                    cmd.setArgumentValue("--module", moduleName);
                } else {
                    cmd.removeArgumentWithValue("--main-class");
                }
                break;

            case SetWrong:
                if (moduleName != null) {
                    cmd.setArgumentValue("--module",
                            String.join("/", moduleName, nonExistingMainClass));
                } else {
                    cmd.setArgumentValue("--main-class", nonExistingMainClass);
                }
        }
    }

    @Parameters
    public static Collection scripts() {
        final var withMainClass = Set.of(SetWrong, SetRight);

        List<Script[]> scripts = new ArrayList<>();
        for (var withJLink : List.of(true, false)) {
            for (var modular : List.of(true, false)) {
                for (var mainClass : Script.MainClassType.values()) {
                    for (var jarMainClass : Script.MainClassType.values()) {
                        if (!withJLink && (jarMainClass == SetWrong || mainClass
                                == SetWrong)) {
                            // Without runtime can't run app to verify it will fail, so
                            // there is no point in such testing.
                            continue;
                        }

                        Script script = new Script()
                            .modular(modular)
                            .withJLink(withJLink)
                            .withMainClass(mainClass)
                            .withJarMainClass(jarMainClass);

                        if (withMainClass.contains(jarMainClass)
                                || withMainClass.contains(mainClass)) {
                        } else if (modular) {
                            script.expectedErrorMessage(
                                    "A main class was not specified nor was one found in the jar");
                        } else {
                            script.expectedErrorMessage(
                                    "Error: Main application class is missing");
                        }

                        scripts.add(new Script[]{script});
                    }
                }
            }
        }
        return scripts;
    }

    @Test
    public void test() throws IOException {
        if (script.jarMainClass == SetWrong) {
            initJarWithWrongMainClass();
        }

        if (script.expectedErrorMessage != null) {
            // This is the case when main class is not found nor in jar
            // file nor on command line.
            List<String> output = cmd
                    .saveConsoleOutput(true)
                    .execute()
                    .assertExitCodeIs(1)
                    .getOutput();
            TKit.assertTextStream(script.expectedErrorMessage).apply(output.stream());
            return;
        }

        // Get here only if main class is specified.
        boolean appShouldSucceed = false;

        // Should succeed if valid main class is set on the command line.
        appShouldSucceed |= (script.mainClass == SetRight);

        // Should succeed if main class is not set on the command line but set
        // to valid value in the jar.
        appShouldSucceed |= (script.mainClass == NotSet && script.jarMainClass == SetRight);

        if (appShouldSucceed) {
            cmd.executeAndAssertHelloAppImageCreated();
        } else {
            cmd.executeAndAssertImageCreated();
            if (!cmd.isFakeRuntime(String.format("Not running [%s]",
                    cmd.appLauncherPath()))) {
                List<String> output = new Executor()
                    .setDirectory(cmd.outputDir())
                    .setExecutable(cmd.appLauncherPath())
                    .dumpOutput().saveOutput()
                    .execute().assertExitCodeIs(1).getOutput();
                TKit.assertTextStream(String.format(
                        "Error: Could not find or load main class %s",
                        nonExistingMainClass)).apply(output.stream());
            }
        }
    }

    private void initJarWithWrongMainClass() {
        // Call JPackageCommand.executePrerequisiteActions() to build app's jar.
        // executePrerequisiteActions() is called by JPackageCommand instance
        // only once.
        cmd.executePrerequisiteActions();

        Path jarFile;
        if (script.appDesc.moduleName() != null) {
            jarFile = Path.of(cmd.getArgumentValue("--module-path"),
                    script.appDesc.jarFileName());
        } else {
            jarFile = cmd.inputDir().resolve(cmd.getArgumentValue("--main-jar"));
        }

        // Create jar file with the main class attribute in manifest set to
        // non-existing class.
        TKit.withTempDirectory("repack-jar", workDir -> {
            Path manifestFile = workDir.resolve("META-INF/MANIFEST.MF");
            try (var jar = new JarFile(jarFile.toFile())) {
                jar.stream()
                .filter(Predicate.not(JarEntry::isDirectory))
                .sequential().forEachOrdered(ThrowingConsumer.toConsumer(
                    jarEntry -> {
                        try (var in = jar.getInputStream(jarEntry)) {
                            Path fileName = workDir.resolve(jarEntry.getName());
                            Files.createDirectories(fileName.getParent());
                            Files.copy(in, fileName);
                        }
                    }));
            }

            Files.delete(jarFile);

            // Adjust manifest.
            TKit.createTextFile(manifestFile, Files.readAllLines(
                    manifestFile).stream().map(line -> line.replace(
                    script.appDesc.className(), nonExistingMainClass)));

            new Executor().setToolProvider(JavaTool.JAR)
            .addArguments("-c", "-M", "-f", jarFile.toString())
            .addArguments("-C", workDir.toString(), ".")
            .execute().assertExitCodeIsZero();
        });
    }

    private final JPackageCommand cmd;
    private final Script script;
    private final String nonExistingMainClass;
}