mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-27 23:31:47 +00:00
8350013: Add a test for JDK-8150442
Reviewed-by: almatvee
This commit is contained in:
parent
a21302bb32
commit
3e86b3a879
@ -0,0 +1,894 @@
|
||||
/*
|
||||
* Copyright (c) 2025, 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.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import jdk.jpackage.internal.util.function.ThrowingBiConsumer;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplier;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.PackageTest.PackageHandlers;
|
||||
import jdk.jpackage.test.RunnablePackageTest.Action;
|
||||
|
||||
public class PackageTestTest extends JUnitAdapter {
|
||||
|
||||
private interface Verifiable {
|
||||
void verify();
|
||||
}
|
||||
|
||||
private static class CallbackFactory {
|
||||
|
||||
CallbackFactory(int tickCount) {
|
||||
this.tickCount = tickCount;
|
||||
}
|
||||
|
||||
CountingInstaller createInstaller(int exitCode) {
|
||||
return new CountingInstaller(tickCount, exitCode);
|
||||
}
|
||||
|
||||
CountingConsumer createUninstaller() {
|
||||
return new CountingConsumer(tickCount, "uninstall");
|
||||
}
|
||||
|
||||
CountingUnpacker createUnpacker() {
|
||||
return new CountingUnpacker(tickCount);
|
||||
}
|
||||
|
||||
CountingConsumer createInitializer() {
|
||||
return new CountingConsumer(tickCount, "init");
|
||||
}
|
||||
|
||||
CountingRunnable createRunOnceInitializer() {
|
||||
return new CountingRunnable(tickCount, "once-init");
|
||||
}
|
||||
|
||||
CountingConsumer createInstallVerifier() {
|
||||
return new CountingConsumer(tickCount, "on-install");
|
||||
}
|
||||
|
||||
CountingConsumer createUninstallVerifier() {
|
||||
return new CountingConsumer(tickCount, "on-uninstall");
|
||||
}
|
||||
|
||||
CountingConsumer createBundleVerifier() {
|
||||
return new CountingConsumer(tickCount, "on-bundle");
|
||||
}
|
||||
|
||||
CountingBundleVerifier createBundleVerifier(int jpackageExitCode) {
|
||||
return new CountingBundleVerifier(tickCount, jpackageExitCode);
|
||||
}
|
||||
|
||||
private final int tickCount;
|
||||
}
|
||||
|
||||
private final static int ERROR_EXIT_CODE_JPACKAGE = 35;
|
||||
private final static int ERROR_EXIT_CODE_INSTALL = 27;
|
||||
|
||||
private final static CallbackFactory NEVER = new CallbackFactory(0);
|
||||
private final static CallbackFactory ONCE = new CallbackFactory(1);
|
||||
private final static CallbackFactory TWICE = new CallbackFactory(2);
|
||||
|
||||
enum BundleVerifier {
|
||||
ONCE_SUCCESS(ONCE),
|
||||
ONCE_FAIL(ONCE),
|
||||
NEVER(PackageTestTest.NEVER),
|
||||
ONCE_SUCCESS_EXIT_CODE(ONCE, 0),
|
||||
ONCE_FAIL_EXIT_CODE(ONCE, ERROR_EXIT_CODE_JPACKAGE),
|
||||
NEVER_EXIT_CODE(PackageTestTest.NEVER, 0);
|
||||
|
||||
BundleVerifier(CallbackFactory factory) {
|
||||
specSupplier = () -> new BundleVerifierSpec(Optional.of(factory.createBundleVerifier()), Optional.empty());
|
||||
}
|
||||
|
||||
BundleVerifier(CallbackFactory factory, int jpackageExitCode) {
|
||||
specSupplier = () -> new BundleVerifierSpec(Optional.empty(),
|
||||
Optional.of(factory.createBundleVerifier(jpackageExitCode)));
|
||||
}
|
||||
|
||||
BundleVerifierSpec spec() {
|
||||
return specSupplier.get();
|
||||
}
|
||||
|
||||
private final Supplier<BundleVerifierSpec> specSupplier;
|
||||
}
|
||||
|
||||
private static class TickCounter implements Verifiable {
|
||||
|
||||
TickCounter(int expectedTicks) {
|
||||
this.expectedTicks = expectedTicks;
|
||||
}
|
||||
|
||||
void tick() {
|
||||
ticks++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify() {
|
||||
switch (expectedTicks) {
|
||||
case 0 -> {
|
||||
TKit.assertEquals(expectedTicks, ticks, String.format("%s: never called", this));
|
||||
}
|
||||
case 1 -> {
|
||||
TKit.assertEquals(expectedTicks, ticks, String.format("%s: called once", this));
|
||||
}
|
||||
case 2 -> {
|
||||
TKit.assertEquals(expectedTicks, ticks, String.format("%s: called twice", this));
|
||||
}
|
||||
default -> {
|
||||
TKit.assertEquals(expectedTicks, ticks, toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int tickCount() {
|
||||
return ticks;
|
||||
}
|
||||
|
||||
protected static String getDescription(TickCounter o) {
|
||||
return "tk=" + o.expectedTicks;
|
||||
}
|
||||
|
||||
private int ticks;
|
||||
protected final int expectedTicks;
|
||||
}
|
||||
|
||||
private static class CountingConsumer extends TickCounter implements ThrowingConsumer<JPackageCommand> {
|
||||
|
||||
@Override
|
||||
public void accept(JPackageCommand cmd) {
|
||||
tick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", label, TickCounter.getDescription(this));
|
||||
}
|
||||
|
||||
CountingConsumer(int expectedTicks, String label) {
|
||||
super(expectedTicks);
|
||||
this.label = Objects.requireNonNull(label);
|
||||
}
|
||||
|
||||
private final String label;
|
||||
}
|
||||
|
||||
private static class CountingRunnable extends TickCounter implements ThrowingRunnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", label, TickCounter.getDescription(this));
|
||||
}
|
||||
|
||||
CountingRunnable(int expectedTicks, String label) {
|
||||
super(expectedTicks);
|
||||
this.label = Objects.requireNonNull(label);
|
||||
}
|
||||
|
||||
private final String label;
|
||||
}
|
||||
|
||||
private static class CountingBundleVerifier extends TickCounter implements ThrowingBiConsumer<JPackageCommand, Executor.Result> {
|
||||
|
||||
@Override
|
||||
public void accept(JPackageCommand cmd, Executor.Result result) {
|
||||
tick();
|
||||
jpackageExitCode = result.exitCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify() {
|
||||
super.verify();
|
||||
if (expectedTicks > 0) {
|
||||
TKit.assertEquals(expectedJPackageExitCode, jpackageExitCode, String.format("%s: run jpackage", this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("on-bundle-ex(exit=%d, %s)", expectedJPackageExitCode, TickCounter.getDescription(this));
|
||||
}
|
||||
|
||||
CountingBundleVerifier(int expectedTicks, int expectedJPackageExitCode) {
|
||||
super(expectedTicks);
|
||||
this.expectedJPackageExitCode = expectedJPackageExitCode;
|
||||
}
|
||||
|
||||
private int jpackageExitCode;
|
||||
private final int expectedJPackageExitCode;
|
||||
}
|
||||
|
||||
private final static class CountingInstaller extends TickCounter implements Function<JPackageCommand, Integer> {
|
||||
|
||||
@Override
|
||||
public Integer apply(JPackageCommand cmd) {
|
||||
tick();
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("install(exit=%d, %s)", exitCode, TickCounter.getDescription(this));
|
||||
}
|
||||
|
||||
CountingInstaller(int expectedTicks, int exitCode) {
|
||||
super(expectedTicks);
|
||||
this.exitCode = exitCode;
|
||||
}
|
||||
|
||||
private final int exitCode;
|
||||
}
|
||||
|
||||
private static class CountingUnpacker extends TickCounter implements BiFunction<JPackageCommand, Path, Path> {
|
||||
|
||||
@Override
|
||||
public Path apply(JPackageCommand cmd, Path path) {
|
||||
tick();
|
||||
try {
|
||||
Files.createDirectories(path.resolve("mockup-installdir"));
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
unpackPaths.add(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("unpack(%s)", TickCounter.getDescription(this));
|
||||
}
|
||||
|
||||
CountingUnpacker(int expectedTicks) {
|
||||
super(expectedTicks);
|
||||
}
|
||||
|
||||
List<Path> unpackPaths() {
|
||||
return unpackPaths;
|
||||
}
|
||||
|
||||
private final List<Path> unpackPaths = new ArrayList<>();
|
||||
}
|
||||
|
||||
record BundleVerifierSpec(Optional<CountingConsumer> verifier, Optional<CountingBundleVerifier> verifierWithExitCode) {
|
||||
BundleVerifierSpec {
|
||||
if (verifier.isPresent() == verifierWithExitCode.isPresent()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
Verifiable apply(PackageTest test) {
|
||||
verifier.ifPresent(test::addBundleVerifier);
|
||||
verifierWithExitCode.ifPresent(test::addBundleVerifier);
|
||||
return verifier.map(Verifiable.class::cast).orElseGet(verifierWithExitCode::orElseThrow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return verifier.map(Verifiable.class::cast).orElseGet(verifierWithExitCode::orElseThrow).toString();
|
||||
}
|
||||
}
|
||||
|
||||
record PackageHandlersSpec(CountingInstaller installer, CountingConsumer uninstaller,
|
||||
Optional<CountingUnpacker> unpacker, int installExitCode) {
|
||||
|
||||
PackageHandlers createPackageHandlers(Consumer<Verifiable> verifiableAccumulator) {
|
||||
List.of(installer, uninstaller).forEach(verifiableAccumulator::accept);
|
||||
unpacker.ifPresent(verifiableAccumulator::accept);
|
||||
return new PackageHandlers(installer, uninstaller::accept, unpacker);
|
||||
}
|
||||
}
|
||||
|
||||
record TestSpec(PackageType type, PackageHandlersSpec handlersSpec,
|
||||
List<CountingConsumer> initializers, List<BundleVerifierSpec> bundleVerifierSpecs,
|
||||
List<CountingConsumer> installVerifiers, List<CountingConsumer> uninstallVerifiers,
|
||||
int expectedJPackageExitCode, int actualJPackageExitCode, List<Action> actions) {
|
||||
|
||||
PackageTest createTest(Consumer<Verifiable> verifiableAccumulator) {
|
||||
return createTest(handlersSpec.createPackageHandlers(verifiableAccumulator));
|
||||
}
|
||||
|
||||
PackageTest createTest(PackageHandlers handlers) {
|
||||
return new PackageTest().jpackageFactory(() -> {
|
||||
return new JPackageCommand() {
|
||||
@Override
|
||||
public Path outputBundle() {
|
||||
return outputDir().resolve("mockup-bundle" + super.packageType().getSuffix());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageType packageType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
JPackageCommand assertAppLayout() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
JPackageCommand createImmutableCopy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyIsOfType(PackageType ... types) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrintableCommandLine() {
|
||||
return "'mockup jpackage'";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor.Result execute(int expectedExitCode) {
|
||||
final var outputBundle = outputBundle();
|
||||
try {
|
||||
Files.createDirectories(outputBundle.getParent());
|
||||
if (actualJPackageExitCode == 0) {
|
||||
Files.createFile(outputBundle);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
return new Executor.Result(actualJPackageExitCode, null,
|
||||
this::getPrintableCommandLine).assertExitCodeIs(expectedExitCode);
|
||||
}
|
||||
};
|
||||
}).setExpectedExitCode(expectedJPackageExitCode)
|
||||
.setExpectedInstallExitCode(handlersSpec.installExitCode)
|
||||
.isPackageTypeSupported(type -> true)
|
||||
.forTypes().packageHandlers(handlers);
|
||||
}
|
||||
|
||||
void configureInitializers(PackageTest test, Consumer<Verifiable> verifiableAccumulator) {
|
||||
for (final var initializer : initializers) {
|
||||
verifiableAccumulator.accept(initializer);
|
||||
test.addInitializer(initializer);
|
||||
}
|
||||
}
|
||||
|
||||
void configureBundleVerifiers(PackageTest test, Consumer<Verifiable> verifiableAccumulator) {
|
||||
for (final var verifierSpec : bundleVerifierSpecs) {
|
||||
verifiableAccumulator.accept(verifierSpec.apply(test));
|
||||
}
|
||||
}
|
||||
|
||||
void configureInstallVerifiers(PackageTest test, Consumer<Verifiable> verifiableAccumulator) {
|
||||
for (final var verifier : installVerifiers) {
|
||||
verifiableAccumulator.accept(verifier);
|
||||
test.addInstallVerifier(verifier);
|
||||
}
|
||||
}
|
||||
|
||||
void configureUninstallVerifiers(PackageTest test, Consumer<Verifiable> verifiableAccumulator) {
|
||||
for (final var verifier : uninstallVerifiers) {
|
||||
verifiableAccumulator.accept(verifier);
|
||||
test.addUninstallVerifier(verifier);
|
||||
}
|
||||
}
|
||||
|
||||
void run(PackageTest test) {
|
||||
final boolean expectedSuccess = (expectedJPackageExitCode == actualJPackageExitCode);
|
||||
|
||||
TKit.assertAssert(expectedSuccess, () -> {
|
||||
test.run(actions.toArray(Action[]::new));
|
||||
});
|
||||
}
|
||||
|
||||
List<Verifiable> run() {
|
||||
return run(Optional.empty());
|
||||
}
|
||||
|
||||
List<Verifiable> run(Consumer<PackageTest> customConfigure) {
|
||||
return run(Optional.of(customConfigure));
|
||||
}
|
||||
|
||||
private List<Verifiable> run(Optional<Consumer<PackageTest>> customConfigure) {
|
||||
final List<Verifiable> verifiers = new ArrayList<>();
|
||||
|
||||
final var test = createTest(verifiers::add);
|
||||
test.forTypes(type);
|
||||
configureInitializers(test, verifiers::add);
|
||||
configureBundleVerifiers(test, verifiers::add);
|
||||
configureInstallVerifiers(test, verifiers::add);
|
||||
configureUninstallVerifiers(test, verifiers::add);
|
||||
customConfigure.ifPresent(callback -> callback.accept(test));
|
||||
run(test);
|
||||
verifiers.forEach(Verifiable::verify);
|
||||
return verifiers;
|
||||
}
|
||||
}
|
||||
|
||||
private final static class TestSpecBuilder {
|
||||
|
||||
TestSpecBuilder type(PackageType v) {
|
||||
type = Objects.requireNonNull(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder install(CallbackFactory v) {
|
||||
install = Objects.requireNonNull(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder uninstall(CallbackFactory v) {
|
||||
uninstall = Objects.requireNonNull(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder unpack(CallbackFactory v) {
|
||||
unpack = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder installExitCode(int v) {
|
||||
installExitCode = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder jpackageExitCode(int v) {
|
||||
return expectedJPackageExitCode(v).actualJPackageExitCode(v);
|
||||
}
|
||||
|
||||
TestSpecBuilder expectedJPackageExitCode(int v) {
|
||||
expectedJPackageExitCode = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder actualJPackageExitCode(int v) {
|
||||
actualJPackageExitCode = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder addActions(Action... v) {
|
||||
actions.addAll(List.of(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder actions(Action... v) {
|
||||
actions.clear();
|
||||
return addActions(v);
|
||||
}
|
||||
|
||||
TestSpecBuilder doCreateAndUnpack() {
|
||||
actions(Action.CREATE_AND_UNPACK);
|
||||
install(NEVER);
|
||||
uninstall(NEVER);
|
||||
if (willHaveBundle()) {
|
||||
overrideNonNullUnpack(ONCE);
|
||||
} else {
|
||||
overrideNonNullUnpack(NEVER);
|
||||
}
|
||||
initializers(ONCE);
|
||||
if (expectedJPackageExitCode != actualJPackageExitCode) {
|
||||
bundleVerifiers(BundleVerifier.NEVER.spec());
|
||||
} else if (expectedJPackageExitCode == 0) {
|
||||
bundleVerifiers(BundleVerifier.ONCE_SUCCESS.spec());
|
||||
bundleVerifiers(BundleVerifier.ONCE_SUCCESS_EXIT_CODE.spec());
|
||||
} else {
|
||||
bundleVerifiers(BundleVerifier.ONCE_FAIL.spec());
|
||||
if (expectedJPackageExitCode == ERROR_EXIT_CODE_JPACKAGE) {
|
||||
bundleVerifiers(BundleVerifier.ONCE_FAIL_EXIT_CODE.spec());
|
||||
}
|
||||
}
|
||||
uninstallVerifiers(NEVER);
|
||||
if (willVerifyUnpack()) {
|
||||
installVerifiers(ONCE);
|
||||
} else {
|
||||
installVerifiers(NEVER);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder doCreateUnpackInstallUninstall() {
|
||||
actions(Action.CREATE, Action.UNPACK, Action.VERIFY_INSTALL, Action.INSTALL,
|
||||
Action.VERIFY_INSTALL, Action.UNINSTALL, Action.VERIFY_UNINSTALL);
|
||||
initializers(ONCE);
|
||||
uninstallVerifiers(NEVER);
|
||||
if (willHaveBundle()) {
|
||||
overrideNonNullUnpack(ONCE);
|
||||
install(ONCE);
|
||||
if (installExitCode == 0) {
|
||||
uninstall(ONCE);
|
||||
uninstallVerifiers(ONCE);
|
||||
} else {
|
||||
uninstall(NEVER);
|
||||
}
|
||||
} else {
|
||||
overrideNonNullUnpack(NEVER);
|
||||
install(NEVER);
|
||||
uninstall(NEVER);
|
||||
}
|
||||
|
||||
if (expectedJPackageExitCode != actualJPackageExitCode) {
|
||||
bundleVerifiers(BundleVerifier.NEVER.spec());
|
||||
installVerifiers(NEVER);
|
||||
} else if (expectedJPackageExitCode == 0) {
|
||||
bundleVerifiers(BundleVerifier.ONCE_SUCCESS.spec());
|
||||
bundleVerifiers(BundleVerifier.ONCE_SUCCESS_EXIT_CODE.spec());
|
||||
if (installExitCode == 0) {
|
||||
if (willVerifyUnpack()) {
|
||||
installVerifiers(TWICE);
|
||||
} else {
|
||||
installVerifiers(ONCE);
|
||||
}
|
||||
} else {
|
||||
if (willVerifyUnpack()) {
|
||||
installVerifiers(ONCE);
|
||||
} else {
|
||||
installVerifiers(NEVER);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bundleVerifiers(BundleVerifier.ONCE_FAIL.spec());
|
||||
if (expectedJPackageExitCode == ERROR_EXIT_CODE_JPACKAGE) {
|
||||
bundleVerifiers(BundleVerifier.ONCE_FAIL_EXIT_CODE.spec());
|
||||
}
|
||||
installVerifiers(NEVER);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder addInitializers(CallbackFactory... v) {
|
||||
initializers.addAll(List.of(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder addBundleVerifiers(BundleVerifierSpec... v) {
|
||||
bundleVerifiers.addAll(List.of(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder addInstallVerifiers(CallbackFactory... v) {
|
||||
installVerifiers.addAll(List.of(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder addUninstallVerifiers(CallbackFactory... v) {
|
||||
uninstallVerifiers.addAll(List.of(v));
|
||||
return this;
|
||||
}
|
||||
|
||||
TestSpecBuilder initializers(CallbackFactory... v) {
|
||||
initializers.clear();
|
||||
return addInitializers(v);
|
||||
}
|
||||
|
||||
TestSpecBuilder bundleVerifiers(BundleVerifierSpec... v) {
|
||||
bundleVerifiers.clear();
|
||||
return addBundleVerifiers(v);
|
||||
}
|
||||
|
||||
TestSpecBuilder installVerifiers(CallbackFactory... v) {
|
||||
installVerifiers.clear();
|
||||
return addInstallVerifiers(v);
|
||||
}
|
||||
|
||||
TestSpecBuilder uninstallVerifiers(CallbackFactory... v) {
|
||||
uninstallVerifiers.clear();
|
||||
return addUninstallVerifiers(v);
|
||||
}
|
||||
|
||||
TestSpec create() {
|
||||
final var handlersSpec = new PackageHandlersSpec(
|
||||
install.createInstaller(installExitCode), uninstall.createUninstaller(),
|
||||
Optional.ofNullable(unpack).map(CallbackFactory::createUnpacker), installExitCode);
|
||||
return new TestSpec(type, handlersSpec,
|
||||
initializers.stream().map(CallbackFactory::createInitializer).toList(),
|
||||
bundleVerifiers,
|
||||
installVerifiers.stream().map(CallbackFactory::createInstallVerifier).toList(),
|
||||
uninstallVerifiers.stream().map(CallbackFactory::createUninstallVerifier).toList(),
|
||||
expectedJPackageExitCode,
|
||||
actualJPackageExitCode, actions);
|
||||
}
|
||||
|
||||
boolean willVerifyCreate() {
|
||||
return actions.contains(Action.CREATE) && actualJPackageExitCode == 0 && expectedJPackageExitCode == actualJPackageExitCode;
|
||||
}
|
||||
|
||||
boolean willHaveBundle() {
|
||||
return !actions.contains(Action.CREATE) || willVerifyCreate();
|
||||
}
|
||||
|
||||
boolean willVerifyUnpack() {
|
||||
return actions.contains(Action.UNPACK) && willHaveBundle() && unpack != null;
|
||||
}
|
||||
|
||||
boolean willVerifyInstall() {
|
||||
return (actions.contains(Action.INSTALL) && installExitCode == 0) && willHaveBundle();
|
||||
}
|
||||
|
||||
private void overrideNonNullUnpack(CallbackFactory v) {
|
||||
if (unpack != null) {
|
||||
unpack(v);
|
||||
}
|
||||
}
|
||||
|
||||
private PackageType type = PackageType.LINUX_RPM;
|
||||
private CallbackFactory install = NEVER;
|
||||
private CallbackFactory uninstall = NEVER;
|
||||
private CallbackFactory unpack = NEVER;
|
||||
private int installExitCode;
|
||||
private final List<CallbackFactory> initializers = new ArrayList<>();
|
||||
private final List<BundleVerifierSpec> bundleVerifiers = new ArrayList<>();
|
||||
private final List<CallbackFactory> installVerifiers = new ArrayList<>();
|
||||
private final List<CallbackFactory> uninstallVerifiers = new ArrayList<>();
|
||||
private int expectedJPackageExitCode;
|
||||
private int actualJPackageExitCode;
|
||||
private final List<Action> actions = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ParameterSupplier
|
||||
public void test(TestSpec spec) {
|
||||
spec.run();
|
||||
}
|
||||
|
||||
public static List<Object[]> test() {
|
||||
List<TestSpec> data = new ArrayList<>();
|
||||
|
||||
for (boolean withUnpack : List.of(false, true)) {
|
||||
for (int actualJPackageExitCode : List.of(0, 1, ERROR_EXIT_CODE_INSTALL)) {
|
||||
for (int expectedJPackageExitCode : List.of(0, 1, ERROR_EXIT_CODE_INSTALL)) {
|
||||
data.add(new TestSpecBuilder()
|
||||
.unpack(withUnpack ? ONCE : null)
|
||||
.actualJPackageExitCode(actualJPackageExitCode)
|
||||
.expectedJPackageExitCode(expectedJPackageExitCode)
|
||||
.doCreateAndUnpack().create());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (boolean withUnpack : List.of(false, true)) {
|
||||
for (int installExitCode : List.of(0, 1, ERROR_EXIT_CODE_INSTALL)) {
|
||||
for (int actualJPackageExitCode : List.of(0, 1, ERROR_EXIT_CODE_JPACKAGE)) {
|
||||
for (int expectedJPackageExitCode : List.of(0, 1, ERROR_EXIT_CODE_JPACKAGE)) {
|
||||
data.add(new TestSpecBuilder()
|
||||
.unpack(withUnpack ? ONCE : null)
|
||||
.installExitCode(installExitCode)
|
||||
.actualJPackageExitCode(actualJPackageExitCode)
|
||||
.expectedJPackageExitCode(expectedJPackageExitCode)
|
||||
.doCreateUnpackInstallUninstall().create());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.add(new TestSpecBuilder()
|
||||
.actions(Action.VERIFY_INSTALL, Action.UNINSTALL, Action.VERIFY_INSTALL, Action.VERIFY_UNINSTALL)
|
||||
.uninstall(ONCE)
|
||||
.initializers(ONCE)
|
||||
.bundleVerifiers(BundleVerifier.NEVER.spec())
|
||||
.installVerifiers(TWICE)
|
||||
.uninstallVerifiers(ONCE)
|
||||
.create());
|
||||
|
||||
return data.stream().map(v -> {
|
||||
return new Object[] {v};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ParameterSupplier
|
||||
public void testDisableInstallerUninstaller(TestSpec spec, boolean disableInstaller, boolean disableUninstaller) {
|
||||
spec.run(test -> {
|
||||
if (disableInstaller) {
|
||||
test.disablePackageInstaller();
|
||||
}
|
||||
if (disableUninstaller) {
|
||||
test.disablePackageUninstaller();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static List<Object[]> testDisableInstallerUninstaller() {
|
||||
List<Object[]> data = new ArrayList<>();
|
||||
|
||||
for (boolean disableInstaller : List.of(true, false)) {
|
||||
for (boolean disableUninstaller : List.of(true, false)) {
|
||||
if (disableInstaller || disableUninstaller) {
|
||||
final var builder = new TestSpecBuilder().doCreateUnpackInstallUninstall();
|
||||
if (disableInstaller) {
|
||||
builder.install(NEVER);
|
||||
}
|
||||
if (disableUninstaller) {
|
||||
builder.uninstall(NEVER);
|
||||
}
|
||||
data.add(new Object[] { builder.create(), disableInstaller, disableUninstaller });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static List<Path> getUnpackPaths(Collection<Verifiable> verifiers) {
|
||||
return verifiers.stream()
|
||||
.filter(CountingUnpacker.class::isInstance)
|
||||
.map(CountingUnpacker.class::cast)
|
||||
.map(CountingUnpacker::unpackPaths)
|
||||
.reduce((x , y) -> {
|
||||
throw new UnsupportedOperationException();
|
||||
}).orElseThrow();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnpackTwice() {
|
||||
final var testSpec = new TestSpecBuilder()
|
||||
.actions(Action.CREATE, Action.UNPACK, Action.VERIFY_INSTALL, Action.UNPACK, Action.VERIFY_INSTALL)
|
||||
.unpack(TWICE)
|
||||
.initializers(ONCE)
|
||||
.installVerifiers(TWICE)
|
||||
.create();
|
||||
|
||||
final var unpackPaths = getUnpackPaths(testSpec.run());
|
||||
|
||||
TKit.assertEquals(2, unpackPaths.size(), "Check the bundle was unpacked in different directories");
|
||||
|
||||
unpackPaths.forEach(dir -> {
|
||||
TKit.assertTrue(dir.startsWith(TKit.workDir()), "Check unpack directory is inside of the test work directory");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteUnpackDirs() {
|
||||
final int unpackActionCount = 4;
|
||||
final var testSpec = new TestSpecBuilder()
|
||||
.actions(Action.UNPACK, Action.UNPACK, Action.UNPACK, Action.UNPACK)
|
||||
.unpack(new CallbackFactory(unpackActionCount) {
|
||||
@Override
|
||||
CountingUnpacker createUnpacker() {
|
||||
return new CountingUnpacker(unpackActionCount) {
|
||||
@Override
|
||||
public Path apply(JPackageCommand cmd, Path path) {
|
||||
switch (tickCount()) {
|
||||
case 0 -> {
|
||||
}
|
||||
|
||||
case 2 -> {
|
||||
path = path.resolve("foo");
|
||||
}
|
||||
|
||||
case 1, 3 -> {
|
||||
try {
|
||||
path = Files.createTempDirectory("jpackage-test");
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
default -> {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
return super.apply(cmd, path);
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
.initializers(ONCE)
|
||||
.create();
|
||||
|
||||
final var unpackPaths = getUnpackPaths(testSpec.run());
|
||||
|
||||
TKit.assertEquals(unpackActionCount, unpackPaths.size(), "Check the bundle was unpacked in different directories");
|
||||
|
||||
// Unpack directories within the test work directory must exist.
|
||||
TKit.assertDirectoryExists(unpackPaths.get(0));
|
||||
TKit.assertDirectoryExists(unpackPaths.get(2));
|
||||
|
||||
// Unpack directories outside of the test work directory must be deleted.
|
||||
TKit.assertPathExists(unpackPaths.get(1), false);
|
||||
TKit.assertPathExists(unpackPaths.get(3), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunOnceInitializer() {
|
||||
final var testSpec = new TestSpecBuilder().doCreateAndUnpack().unpack(TWICE).create();
|
||||
|
||||
final var initializer = TWICE.createInitializer();
|
||||
final var runOnceInitializer = ONCE.createRunOnceInitializer();
|
||||
testSpec.run(test -> {
|
||||
test.forTypes(PackageType.LINUX_RPM, PackageType.WIN_MSI)
|
||||
.addRunOnceInitializer(runOnceInitializer)
|
||||
.addInitializer(initializer);
|
||||
});
|
||||
|
||||
initializer.verify();
|
||||
runOnceInitializer.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameter("0")
|
||||
@Parameter("1")
|
||||
public void testPurge(int jpackageExitCode) {
|
||||
|
||||
Path[] outputBundle = new Path[1];
|
||||
|
||||
final var builder = new TestSpecBuilder();
|
||||
|
||||
builder.actions(Action.CREATE).initializers(new CallbackFactory(1) {
|
||||
@Override
|
||||
CountingConsumer createInitializer() {
|
||||
return new CountingConsumer(1, "custom-init") {
|
||||
@Override
|
||||
public void accept(JPackageCommand cmd) {
|
||||
outputBundle[0] = cmd.outputBundle();
|
||||
super.accept(cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
}).create().run();
|
||||
TKit.assertFileExists(outputBundle[0]);
|
||||
|
||||
builder.actions(Action.PURGE).initializers(ONCE).jpackageExitCode(jpackageExitCode).create().run();
|
||||
TKit.assertPathExists(outputBundle[0], false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageTestOrder() {
|
||||
|
||||
Set<PackageType> packageTypes = new LinkedHashSet<>();
|
||||
|
||||
final var initializer = new CountingConsumer(PackageType.NATIVE.size(), "custom-init") {
|
||||
@Override
|
||||
public void accept(JPackageCommand cmd) {
|
||||
packageTypes.add(new JPackageCommand().setArgumentValue(
|
||||
"--type", cmd.getArgumentValue("--type")).packageType());
|
||||
super.accept(cmd);
|
||||
}
|
||||
};
|
||||
|
||||
new TestSpecBuilder().actions(Action.CREATE).create().run(test -> {
|
||||
test.forTypes().addInitializer(initializer);
|
||||
});
|
||||
|
||||
initializer.verify();
|
||||
|
||||
final var expectedOrder = PackageType.NATIVE.stream()
|
||||
.sorted().map(PackageType::name).toList();
|
||||
final var actualOrder = packageTypes.stream().map(PackageType::name).toList();
|
||||
|
||||
TKit.assertStringListEquals(expectedOrder, actualOrder, "Check the order or packaging");
|
||||
}
|
||||
}
|
||||
@ -25,6 +25,7 @@ package jdk.jpackage.test;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
@ -77,6 +78,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
appLayoutAsserts = cmd.appLayoutAsserts;
|
||||
outputValidator = cmd.outputValidator;
|
||||
executeInDirectory = cmd.executeInDirectory;
|
||||
winMsiLogFile = cmd.winMsiLogFile;
|
||||
}
|
||||
|
||||
JPackageCommand createImmutableCopy() {
|
||||
@ -998,6 +1000,22 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return this;
|
||||
}
|
||||
|
||||
JPackageCommand winMsiLogFile(Path v) {
|
||||
this.winMsiLogFile = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Optional<Path> winMsiLogFile() {
|
||||
return Optional.ofNullable(winMsiLogFile);
|
||||
}
|
||||
|
||||
public Optional<Stream<String>> winMsiLogFileContents() {
|
||||
return winMsiLogFile().map(ThrowingFunction.toFunction(msiLog -> {
|
||||
// MSI log files are UTF16LE-encoded
|
||||
return Files.lines(msiLog, StandardCharsets.UTF_16LE);
|
||||
}));
|
||||
}
|
||||
|
||||
private JPackageCommand adjustArgumentsBeforeExecution() {
|
||||
if (!isWithToolProvider()) {
|
||||
// if jpackage is launched as a process then set the jlink.debug system property
|
||||
@ -1175,6 +1193,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
private final Actions prerequisiteActions;
|
||||
private final Actions verifyActions;
|
||||
private Path executeInDirectory;
|
||||
private Path winMsiLogFile;
|
||||
private Set<AppLayoutAssert> appLayoutAsserts = Set.of(AppLayoutAssert.values());
|
||||
private Consumer<Stream<String>> outputValidator;
|
||||
private static boolean defaultWithToolProvider;
|
||||
|
||||
@ -195,58 +195,61 @@ public final class LinuxHelper {
|
||||
}
|
||||
|
||||
static PackageHandlers createDebPackageHandlers() {
|
||||
PackageHandlers deb = new PackageHandlers();
|
||||
deb.installHandler = cmd -> {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_DEB);
|
||||
Executor.of("sudo", "dpkg", "-i")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.execute();
|
||||
};
|
||||
deb.uninstallHandler = cmd -> {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_DEB);
|
||||
var packageName = getPackageName(cmd);
|
||||
String script = String.format("! dpkg -s %s || sudo dpkg -r %s",
|
||||
packageName, packageName);
|
||||
Executor.of("sh", "-c", script).execute();
|
||||
};
|
||||
deb.unpackHandler = (cmd, destinationDir) -> {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_DEB);
|
||||
Executor.of("dpkg", "-x")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.addArgument(destinationDir)
|
||||
.execute();
|
||||
return destinationDir;
|
||||
};
|
||||
return deb;
|
||||
return new PackageHandlers(LinuxHelper::installDeb, LinuxHelper::uninstallDeb, LinuxHelper::unpackDeb);
|
||||
}
|
||||
|
||||
private static int installDeb(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_DEB);
|
||||
return Executor.of("sudo", "dpkg", "-i")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.execute().getExitCode();
|
||||
}
|
||||
|
||||
private static void uninstallDeb(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_DEB);
|
||||
var packageName = getPackageName(cmd);
|
||||
String script = String.format("! dpkg -s %s || sudo dpkg -r %s",
|
||||
packageName, packageName);
|
||||
Executor.of("sh", "-c", script).execute();
|
||||
}
|
||||
|
||||
private static Path unpackDeb(JPackageCommand cmd, Path destinationDir) {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_DEB);
|
||||
Executor.of("dpkg", "-x")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.addArgument(destinationDir)
|
||||
.execute(0);
|
||||
return destinationDir;
|
||||
}
|
||||
|
||||
static PackageHandlers createRpmPackageHandlers() {
|
||||
PackageHandlers rpm = new PackageHandlers();
|
||||
rpm.installHandler = cmd -> {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_RPM);
|
||||
Executor.of("sudo", "rpm", "-U")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.execute();
|
||||
};
|
||||
rpm.uninstallHandler = cmd -> {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_RPM);
|
||||
var packageName = getPackageName(cmd);
|
||||
String script = String.format("! rpm -q %s || sudo rpm -e %s",
|
||||
packageName, packageName);
|
||||
Executor.of("sh", "-c", script).execute();
|
||||
};
|
||||
rpm.unpackHandler = (cmd, destinationDir) -> {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_RPM);
|
||||
Executor.of("sh", "-c", String.format(
|
||||
"rpm2cpio '%s' | cpio -idm --quiet",
|
||||
JPackageCommand.escapeAndJoin(
|
||||
cmd.outputBundle().toAbsolutePath().toString())))
|
||||
.setDirectory(destinationDir)
|
||||
.execute();
|
||||
return destinationDir;
|
||||
};
|
||||
return new PackageHandlers(LinuxHelper::installRpm, LinuxHelper::uninstallRpm, LinuxHelper::unpackRpm);
|
||||
}
|
||||
|
||||
return rpm;
|
||||
private static int installRpm(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_RPM);
|
||||
return Executor.of("sudo", "rpm", "-U")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.execute().getExitCode();
|
||||
}
|
||||
|
||||
private static void uninstallRpm(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_RPM);
|
||||
var packageName = getPackageName(cmd);
|
||||
String script = String.format("! rpm -q %s || sudo rpm -e %s",
|
||||
packageName, packageName);
|
||||
Executor.of("sh", "-c", script).execute();
|
||||
}
|
||||
|
||||
private static Path unpackRpm(JPackageCommand cmd, Path destinationDir) {
|
||||
cmd.verifyIsOfType(PackageType.LINUX_RPM);
|
||||
Executor.of("sh", "-c", String.format(
|
||||
"rpm2cpio '%s' | cpio -idm --quiet",
|
||||
JPackageCommand.escapeAndJoin(
|
||||
cmd.outputBundle().toAbsolutePath().toString())))
|
||||
.setDirectory(destinationDir)
|
||||
.execute(0);
|
||||
return destinationDir;
|
||||
}
|
||||
|
||||
static Path getLauncherPath(JPackageCommand cmd) {
|
||||
|
||||
@ -150,113 +150,115 @@ public final class MacHelper {
|
||||
}
|
||||
|
||||
static PackageHandlers createDmgPackageHandlers() {
|
||||
PackageHandlers dmg = new PackageHandlers();
|
||||
return new PackageHandlers(MacHelper::installDmg, MacHelper::uninstallDmg, MacHelper::unpackDmg);
|
||||
}
|
||||
|
||||
dmg.installHandler = cmd -> {
|
||||
withExplodedDmg(cmd, dmgImage -> {
|
||||
Executor.of("sudo", "cp", "-r")
|
||||
.addArgument(dmgImage)
|
||||
.addArgument(getInstallationDirectory(cmd).getParent())
|
||||
.execute();
|
||||
});
|
||||
};
|
||||
dmg.unpackHandler = (cmd, destinationDir) -> {
|
||||
Path unpackDir = destinationDir.resolve(
|
||||
TKit.removeRootFromAbsolutePath(
|
||||
getInstallationDirectory(cmd)).getParent());
|
||||
try {
|
||||
Files.createDirectories(unpackDir);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
private static int installDmg(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.MAC_DMG);
|
||||
withExplodedDmg(cmd, dmgImage -> {
|
||||
Executor.of("sudo", "cp", "-r")
|
||||
.addArgument(dmgImage)
|
||||
.addArgument(getInstallationDirectory(cmd).getParent())
|
||||
.execute(0);
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
withExplodedDmg(cmd, dmgImage -> {
|
||||
Executor.of("cp", "-r")
|
||||
.addArgument(dmgImage)
|
||||
.addArgument(unpackDir)
|
||||
.execute();
|
||||
});
|
||||
return destinationDir;
|
||||
};
|
||||
dmg.uninstallHandler = cmd -> {
|
||||
cmd.verifyIsOfType(PackageType.MAC_DMG);
|
||||
Executor.of("sudo", "rm", "-rf")
|
||||
.addArgument(cmd.appInstallationDirectory())
|
||||
private static void uninstallDmg(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.MAC_DMG);
|
||||
Executor.of("sudo", "rm", "-rf")
|
||||
.addArgument(cmd.appInstallationDirectory())
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static Path unpackDmg(JPackageCommand cmd, Path destinationDir) {
|
||||
cmd.verifyIsOfType(PackageType.MAC_DMG);
|
||||
Path unpackDir = destinationDir.resolve(
|
||||
TKit.removeRootFromAbsolutePath(
|
||||
getInstallationDirectory(cmd)).getParent());
|
||||
try {
|
||||
Files.createDirectories(unpackDir);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
withExplodedDmg(cmd, dmgImage -> {
|
||||
Executor.of("cp", "-r")
|
||||
.addArgument(dmgImage)
|
||||
.addArgument(unpackDir)
|
||||
.execute();
|
||||
};
|
||||
|
||||
return dmg;
|
||||
});
|
||||
return destinationDir;
|
||||
}
|
||||
|
||||
static PackageHandlers createPkgPackageHandlers() {
|
||||
PackageHandlers pkg = new PackageHandlers();
|
||||
return new PackageHandlers(MacHelper::installPkg, MacHelper::uninstallPkg, MacHelper::unpackPkg);
|
||||
}
|
||||
|
||||
pkg.installHandler = cmd -> {
|
||||
cmd.verifyIsOfType(PackageType.MAC_PKG);
|
||||
Executor.of("sudo", "/usr/sbin/installer", "-allowUntrusted", "-pkg")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.addArguments("-target", "/")
|
||||
private static int installPkg(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.MAC_PKG);
|
||||
return Executor.of("sudo", "/usr/sbin/installer", "-allowUntrusted", "-pkg")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.addArguments("-target", "/")
|
||||
.execute().getExitCode();
|
||||
}
|
||||
|
||||
private static void uninstallPkg(JPackageCommand cmd) {
|
||||
cmd.verifyIsOfType(PackageType.MAC_PKG);
|
||||
if (Files.exists(getUninstallCommand(cmd))) {
|
||||
Executor.of("sudo", "/bin/sh",
|
||||
getUninstallCommand(cmd).toString()).execute();
|
||||
} else {
|
||||
Executor.of("sudo", "rm", "-rf")
|
||||
.addArgument(cmd.appInstallationDirectory())
|
||||
.execute();
|
||||
};
|
||||
pkg.unpackHandler = (cmd, destinationDir) -> {
|
||||
cmd.verifyIsOfType(PackageType.MAC_PKG);
|
||||
}
|
||||
}
|
||||
|
||||
var dataDir = destinationDir.resolve("data");
|
||||
private static Path unpackPkg(JPackageCommand cmd, Path destinationDir) {
|
||||
cmd.verifyIsOfType(PackageType.MAC_PKG);
|
||||
|
||||
Executor.of("pkgutil", "--expand")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.addArgument(dataDir) // We need non-existing folder
|
||||
.execute();
|
||||
var dataDir = destinationDir.resolve("data");
|
||||
|
||||
final Path unpackRoot = destinationDir.resolve("unpacked");
|
||||
Executor.of("pkgutil", "--expand")
|
||||
.addArgument(cmd.outputBundle())
|
||||
.addArgument(dataDir) // We need non-existing folder
|
||||
.execute();
|
||||
|
||||
// Unpack all ".pkg" files from $dataDir folder in $unpackDir folder
|
||||
try (var dataListing = Files.list(dataDir)) {
|
||||
dataListing.filter(file -> {
|
||||
return ".pkg".equals(PathUtils.getSuffix(file.getFileName()));
|
||||
}).forEach(ThrowingConsumer.toConsumer(pkgDir -> {
|
||||
// Installation root of the package is stored in
|
||||
// /pkg-info@install-location attribute in $pkgDir/PackageInfo xml file
|
||||
var doc = createDocumentBuilder().parse(
|
||||
new ByteArrayInputStream(Files.readAllBytes(
|
||||
pkgDir.resolve("PackageInfo"))));
|
||||
var xPath = XPathFactory.newInstance().newXPath();
|
||||
final Path unpackRoot = destinationDir.resolve("unpacked");
|
||||
|
||||
final String installRoot = (String) xPath.evaluate(
|
||||
"/pkg-info/@install-location", doc,
|
||||
XPathConstants.STRING);
|
||||
// Unpack all ".pkg" files from $dataDir folder in $unpackDir folder
|
||||
try (var dataListing = Files.list(dataDir)) {
|
||||
dataListing.filter(file -> {
|
||||
return ".pkg".equals(PathUtils.getSuffix(file.getFileName()));
|
||||
}).forEach(ThrowingConsumer.toConsumer(pkgDir -> {
|
||||
// Installation root of the package is stored in
|
||||
// /pkg-info@install-location attribute in $pkgDir/PackageInfo xml file
|
||||
var doc = createDocumentBuilder().parse(
|
||||
new ByteArrayInputStream(Files.readAllBytes(
|
||||
pkgDir.resolve("PackageInfo"))));
|
||||
var xPath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
final Path unpackDir = unpackRoot.resolve(
|
||||
TKit.removeRootFromAbsolutePath(Path.of(installRoot)));
|
||||
final String installRoot = (String) xPath.evaluate(
|
||||
"/pkg-info/@install-location", doc,
|
||||
XPathConstants.STRING);
|
||||
|
||||
Files.createDirectories(unpackDir);
|
||||
final Path unpackDir = unpackRoot.resolve(
|
||||
TKit.removeRootFromAbsolutePath(Path.of(installRoot)));
|
||||
|
||||
Executor.of("tar", "-C")
|
||||
.addArgument(unpackDir)
|
||||
.addArgument("-xvf")
|
||||
.addArgument(pkgDir.resolve("Payload"))
|
||||
.execute();
|
||||
}));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
Files.createDirectories(unpackDir);
|
||||
|
||||
return unpackRoot;
|
||||
};
|
||||
pkg.uninstallHandler = cmd -> {
|
||||
cmd.verifyIsOfType(PackageType.MAC_PKG);
|
||||
|
||||
if (Files.exists(getUninstallCommand(cmd))) {
|
||||
Executor.of("sudo", "/bin/sh",
|
||||
getUninstallCommand(cmd).toString()).execute();
|
||||
} else {
|
||||
Executor.of("sudo", "rm", "-rf")
|
||||
.addArgument(cmd.appInstallationDirectory())
|
||||
Executor.of("tar", "-C")
|
||||
.addArgument(unpackDir)
|
||||
.addArgument("-xvf")
|
||||
.addArgument(pkgDir.resolve("Payload"))
|
||||
.execute();
|
||||
}
|
||||
};
|
||||
}));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
return unpackRoot;
|
||||
}
|
||||
|
||||
static void verifyBundleStructure(JPackageCommand cmd) {
|
||||
|
||||
@ -22,6 +22,15 @@
|
||||
*/
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingBiConsumer.toBiConsumer;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||
import static jdk.jpackage.test.PackageType.LINUX;
|
||||
import static jdk.jpackage.test.PackageType.MAC_PKG;
|
||||
import static jdk.jpackage.test.PackageType.NATIVE;
|
||||
import static jdk.jpackage.test.PackageType.WINDOWS;
|
||||
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -29,6 +38,7 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -40,27 +50,15 @@ import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import jdk.jpackage.internal.util.function.ThrowingBiConsumer;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingBiConsumer.toBiConsumer;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||
import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked;
|
||||
import static jdk.jpackage.test.PackageType.LINUX;
|
||||
import static jdk.jpackage.test.PackageType.LINUX_DEB;
|
||||
import static jdk.jpackage.test.PackageType.LINUX_RPM;
|
||||
import static jdk.jpackage.test.PackageType.MAC_DMG;
|
||||
import static jdk.jpackage.test.PackageType.MAC_PKG;
|
||||
import static jdk.jpackage.test.PackageType.NATIVE;
|
||||
import static jdk.jpackage.test.PackageType.WINDOWS;
|
||||
import static jdk.jpackage.test.PackageType.WIN_EXE;
|
||||
import static jdk.jpackage.test.PackageType.WIN_MSI;
|
||||
|
||||
|
||||
/**
|
||||
@ -73,13 +71,18 @@ import static jdk.jpackage.test.PackageType.WIN_MSI;
|
||||
public final class PackageTest extends RunnablePackageTest {
|
||||
|
||||
public PackageTest() {
|
||||
isPackageTypeSupported = PackageType::isSupported;
|
||||
jpackageFactory = JPackageCommand::new;
|
||||
packageHandlers = new HashMap<>();
|
||||
disabledInstallers = new HashSet<>();
|
||||
disabledUninstallers = new HashSet<>();
|
||||
excludeTypes = new HashSet<>();
|
||||
forTypes();
|
||||
setExpectedExitCode(0);
|
||||
setExpectedInstallExitCode(0);
|
||||
namedInitializers = new HashSet<>();
|
||||
handlers = currentTypes.stream()
|
||||
handlers = NATIVE.stream()
|
||||
.collect(Collectors.toMap(v -> v, v -> new Handler()));
|
||||
packageHandlers = createDefaultPackageHandlers();
|
||||
}
|
||||
|
||||
public PackageTest excludeTypes(PackageType... types) {
|
||||
@ -93,13 +96,13 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
|
||||
public PackageTest forTypes(PackageType... types) {
|
||||
Collection<PackageType> newTypes;
|
||||
if (types == null || types.length == 0) {
|
||||
if (types.length == 0) {
|
||||
newTypes = NATIVE;
|
||||
} else {
|
||||
newTypes = Stream.of(types).collect(Collectors.toSet());
|
||||
}
|
||||
currentTypes = newTypes.stream()
|
||||
.filter(PackageType::isSupported)
|
||||
.filter(isPackageTypeSupported)
|
||||
.filter(Predicate.not(excludeTypes::contains))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
return this;
|
||||
@ -124,6 +127,11 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PackageTest setExpectedInstallExitCode(int v) {
|
||||
expectedInstallExitCode = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PackageTest ignoreBundleOutputDir() {
|
||||
return ignoreBundleOutputDir(true);
|
||||
}
|
||||
@ -133,8 +141,8 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
private PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v,
|
||||
String id) {
|
||||
private PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v, String id) {
|
||||
Objects.requireNonNull(v);
|
||||
if (id != null) {
|
||||
if (namedInitializers.contains(id)) {
|
||||
return this;
|
||||
@ -142,12 +150,12 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
|
||||
namedInitializers.add(id);
|
||||
}
|
||||
currentTypes.forEach(type -> handlers.get(type).addInitializer(
|
||||
toConsumer(v)));
|
||||
currentTypes.forEach(type -> handlers.get(type).addInitializer(toConsumer(v)));
|
||||
return this;
|
||||
}
|
||||
|
||||
private PackageTest addRunOnceInitializer(ThrowingRunnable v, String id) {
|
||||
Objects.requireNonNull(v);
|
||||
return addInitializer(new ThrowingConsumer<JPackageCommand>() {
|
||||
@Override
|
||||
public void accept(JPackageCommand unused) throws Throwable {
|
||||
@ -169,24 +177,26 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
return addRunOnceInitializer(v, null);
|
||||
}
|
||||
|
||||
public PackageTest addBundleVerifier(
|
||||
ThrowingBiConsumer<JPackageCommand, Executor.Result> v) {
|
||||
currentTypes.forEach(type -> handlers.get(type).addBundleVerifier(
|
||||
toBiConsumer(v)));
|
||||
public PackageTest addBundleVerifier(ThrowingBiConsumer<JPackageCommand, Executor.Result> v) {
|
||||
Objects.requireNonNull(v);
|
||||
currentTypes.forEach(type -> handlers.get(type).addBundleVerifier(toBiConsumer(v)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PackageTest addBundleVerifier(ThrowingConsumer<JPackageCommand> v) {
|
||||
Objects.requireNonNull(v);
|
||||
return addBundleVerifier((cmd, unused) -> toConsumer(v).accept(cmd));
|
||||
}
|
||||
|
||||
public PackageTest addBundlePropertyVerifier(String propertyName,
|
||||
Predicate<String> pred, String predLabel) {
|
||||
Objects.requireNonNull(propertyName);
|
||||
Objects.requireNonNull(pred);
|
||||
return addBundleVerifier(cmd -> {
|
||||
final String value;
|
||||
if (TKit.isLinux()) {
|
||||
if (isOfType(cmd, LINUX)) {
|
||||
value = LinuxHelper.getBundleProperty(cmd, propertyName);
|
||||
} else if (TKit.isWindows()) {
|
||||
} else if (isOfType(cmd, WINDOWS)) {
|
||||
value = WindowsHelper.getMsiProperty(cmd, propertyName);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
@ -223,19 +233,23 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
}
|
||||
|
||||
public PackageTest disablePackageInstaller() {
|
||||
currentTypes.forEach(
|
||||
type -> packageHandlers.get(type).installHandler = cmd -> {});
|
||||
currentTypes.forEach(disabledInstallers::add);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PackageTest disablePackageUninstaller() {
|
||||
currentTypes.forEach(
|
||||
type -> packageHandlers.get(type).uninstallHandler = cmd -> {});
|
||||
currentTypes.forEach(disabledUninstallers::add);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PackageTest createMsiLog(boolean v) {
|
||||
createMsiLog = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
static void withFileAssociationsTestRuns(FileAssociations fa,
|
||||
ThrowingBiConsumer<FileAssociations.TestRun, List<Path>> consumer) {
|
||||
Objects.requireNonNull(consumer);
|
||||
for (var testRun : fa.getTestRuns()) {
|
||||
TKit.withTempDirectory("fa-test-files", tempDir -> {
|
||||
List<Path> testFiles = StreamSupport.stream(testRun.getFileNames().spliterator(), false).map(fname -> {
|
||||
@ -254,6 +268,7 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
}
|
||||
|
||||
PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa) {
|
||||
Objects.requireNonNull(fa);
|
||||
|
||||
// Setup test app to have valid jpackage command line before
|
||||
// running check of type of environment.
|
||||
@ -290,7 +305,7 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
Collections.emptyMap());
|
||||
});
|
||||
|
||||
if (TKit.isWindows()) {
|
||||
if (isOfType(cmd, WINDOWS)) {
|
||||
// Verify context menu label in registry.
|
||||
String progId = WindowsHelper.queryRegistryValue(
|
||||
String.format("HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\%s", fa.getSuffix()), "");
|
||||
@ -307,8 +322,7 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
}
|
||||
|
||||
public PackageTest forTypes(Collection<PackageType> types, Runnable action) {
|
||||
Set<PackageType> oldTypes = Set.of(currentTypes.toArray(
|
||||
PackageType[]::new));
|
||||
final var oldTypes = Set.of(currentTypes.toArray(PackageType[]::new));
|
||||
try {
|
||||
forTypes(types);
|
||||
action.run();
|
||||
@ -374,10 +388,59 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
private final List<Consumer<Action>> handlers;
|
||||
}
|
||||
|
||||
static final class PackageHandlers {
|
||||
Consumer<JPackageCommand> installHandler;
|
||||
Consumer<JPackageCommand> uninstallHandler;
|
||||
BiFunction<JPackageCommand, Path, Path> unpackHandler;
|
||||
PackageTest packageHandlers(PackageHandlers v) {
|
||||
Objects.requireNonNull(v);
|
||||
currentTypes.forEach(type -> packageHandlers.put(type, v));
|
||||
return this;
|
||||
}
|
||||
|
||||
PackageTest isPackageTypeSupported(Predicate<PackageType> v) {
|
||||
Objects.requireNonNull(v);
|
||||
isPackageTypeSupported = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
PackageTest jpackageFactory(Supplier<JPackageCommand> v) {
|
||||
Objects.requireNonNull(v);
|
||||
jpackageFactory = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
record PackageHandlers(Function<JPackageCommand, Integer> installHandler,
|
||||
Consumer<JPackageCommand> uninstallHandler,
|
||||
Optional<? extends BiFunction<JPackageCommand, Path, Path>> unpackHandler) {
|
||||
|
||||
PackageHandlers(Function<JPackageCommand, Integer> installHandler,
|
||||
Consumer<JPackageCommand> uninstallHandler,
|
||||
BiFunction<JPackageCommand, Path, Path> unpackHandler) {
|
||||
this(installHandler, uninstallHandler, Optional.of(unpackHandler));
|
||||
}
|
||||
|
||||
PackageHandlers {
|
||||
Objects.requireNonNull(installHandler);
|
||||
Objects.requireNonNull(uninstallHandler);
|
||||
Objects.requireNonNull(unpackHandler);
|
||||
}
|
||||
|
||||
PackageHandlers copyWithNopInstaller() {
|
||||
return new PackageHandlers(cmd -> 0, uninstallHandler, unpackHandler);
|
||||
}
|
||||
|
||||
PackageHandlers copyWithNopUninstaller() {
|
||||
return new PackageHandlers(installHandler, cmd -> {}, unpackHandler);
|
||||
}
|
||||
|
||||
int install(JPackageCommand cmd) {
|
||||
return installHandler.apply(cmd);
|
||||
}
|
||||
|
||||
Path unpack(JPackageCommand cmd, Path unpackDir) {
|
||||
return unpackHandler.orElseThrow().apply(cmd, unpackDir);
|
||||
}
|
||||
|
||||
void uninstall(JPackageCommand cmd) {
|
||||
uninstallHandler.accept(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -393,164 +456,197 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
}
|
||||
|
||||
private List<Consumer<Action>> createPackageTypeHandlers() {
|
||||
return NATIVE.stream()
|
||||
.map(type -> {
|
||||
Handler handler = handlers.entrySet().stream()
|
||||
.filter(entry -> !entry.getValue().isVoid())
|
||||
.filter(entry -> entry.getKey() == type)
|
||||
.map(entry -> entry.getValue())
|
||||
.findAny().orElse(null);
|
||||
Map.Entry<PackageType, Handler> result = null;
|
||||
if (handler != null) {
|
||||
result = Map.entry(type, handler);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(entry -> createPackageTypeHandler(
|
||||
entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
return handlers.entrySet().stream()
|
||||
.filter(entry -> !entry.getValue().isVoid())
|
||||
.filter(entry -> NATIVE.contains(entry.getKey()))
|
||||
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
.map(entry -> {
|
||||
return createPackageTypeHandler(entry.getKey(), entry.getValue());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private Consumer<Action> createPackageTypeHandler(
|
||||
PackageType type, Handler handler) {
|
||||
return toConsumer(new ThrowingConsumer<Action>() {
|
||||
@Override
|
||||
public void accept(Action action) throws Throwable {
|
||||
if (terminated) {
|
||||
throw new IllegalStateException();
|
||||
private record PackageTypePipeline(PackageType type, int expectedJPackageExitCode,
|
||||
int expectedInstallExitCode, PackageHandlers packageHandlers, Handler handler,
|
||||
JPackageCommand cmd, State state) implements Consumer<Action> {
|
||||
|
||||
PackageTypePipeline {
|
||||
Objects.requireNonNull(type);
|
||||
Objects.requireNonNull(packageHandlers);
|
||||
Objects.requireNonNull(handler);
|
||||
Objects.requireNonNull(cmd);
|
||||
Objects.requireNonNull(state);
|
||||
}
|
||||
|
||||
PackageTypePipeline(PackageType type, int expectedJPackageExitCode,
|
||||
int expectedInstallExitCode, PackageHandlers packageHandlers,
|
||||
Handler handler, JPackageCommand cmd) {
|
||||
this(type, expectedJPackageExitCode, expectedInstallExitCode,
|
||||
packageHandlers, handler, cmd, new State());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Action action) {
|
||||
switch(analizeAction(action)) {
|
||||
case SKIP_NO_PACKAGE_HANDLER -> {
|
||||
TKit.trace(String.format("No handler of [%s] action for %s command",
|
||||
action, cmd.getPrintableCommandLine()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == Action.FINALIZE) {
|
||||
if (unpackDir != null) {
|
||||
if (Files.isDirectory(unpackDir)
|
||||
&& !unpackDir.startsWith(TKit.workDir())) {
|
||||
TKit.deleteDirectoryRecursive(unpackDir);
|
||||
}
|
||||
unpackDir = null;
|
||||
}
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
boolean skip = false;
|
||||
|
||||
if (unhandledAction != null) {
|
||||
switch (unhandledAction) {
|
||||
case CREATE:
|
||||
skip = true;
|
||||
break;
|
||||
case UNPACK:
|
||||
case INSTALL:
|
||||
skip = (action == Action.VERIFY_INSTALL);
|
||||
break;
|
||||
case UNINSTALL:
|
||||
skip = (action == Action.VERIFY_UNINSTALL);
|
||||
break;
|
||||
default: // NOP
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
case SKIP -> {
|
||||
TKit.trace(String.format("Skip [%s] action of %s command",
|
||||
action, cmd.getPrintableCommandLine()));
|
||||
return;
|
||||
}
|
||||
|
||||
final Supplier<JPackageCommand> curCmd = () -> {
|
||||
if (Set.of(Action.INITIALIZE, Action.CREATE).contains(action)) {
|
||||
return cmd;
|
||||
} else {
|
||||
return cmd.createImmutableCopy();
|
||||
}
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case UNPACK: {
|
||||
cmd.setUnpackedPackageLocation(null);
|
||||
handleAction(action,
|
||||
packageHandlers.get(type).unpackHandler,
|
||||
handler -> {
|
||||
unpackDir = TKit.createTempDirectory(
|
||||
String.format("unpacked-%s",
|
||||
type.getName()));
|
||||
unpackDir = handler.apply(cmd, unpackDir);
|
||||
cmd.setUnpackedPackageLocation(unpackDir);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case INSTALL: {
|
||||
cmd.setUnpackedPackageLocation(null);
|
||||
handleAction(action,
|
||||
packageHandlers.get(type).installHandler,
|
||||
handler -> {
|
||||
handler.accept(curCmd.get());
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case UNINSTALL: {
|
||||
handleAction(action,
|
||||
packageHandlers.get(type).uninstallHandler,
|
||||
handler -> {
|
||||
handler.accept(curCmd.get());
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case CREATE:
|
||||
cmd.setUnpackedPackageLocation(null);
|
||||
handler.accept(action, curCmd.get());
|
||||
handleAction(action,
|
||||
(expectedJPackageExitCode == 0) ? Boolean.TRUE : null,
|
||||
handler -> {
|
||||
});
|
||||
return;
|
||||
|
||||
default:
|
||||
handler.accept(action, curCmd.get());
|
||||
break;
|
||||
}
|
||||
|
||||
Optional.ofNullable(unhandledAction).ifPresent(v -> {
|
||||
TKit.trace(String.format(
|
||||
"No handler of [%s] action for %s command", v,
|
||||
cmd.getPrintableCommandLine()));
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void handleAction(Action action, T handler,
|
||||
ThrowingConsumer<T> consumer) throws Throwable {
|
||||
if (handler == null) {
|
||||
unhandledAction = action;
|
||||
} else {
|
||||
unhandledAction = null;
|
||||
consumer.accept(handler);
|
||||
case PROCESS -> {
|
||||
}
|
||||
}
|
||||
|
||||
private Path unpackDir;
|
||||
private Action unhandledAction;
|
||||
private boolean terminated;
|
||||
private final JPackageCommand cmd = Functional.identity(() -> {
|
||||
JPackageCommand result = new JPackageCommand();
|
||||
result.setDefaultInputOutput().setDefaultAppName();
|
||||
if (BUNDLE_OUTPUT_DIR != null && !ignoreBundleOutputDir) {
|
||||
result.setArgumentValue("--dest", BUNDLE_OUTPUT_DIR.toString());
|
||||
switch (action) {
|
||||
case UNPACK -> {
|
||||
cmd.setUnpackedPackageLocation(null);
|
||||
final var unpackRootDir = TKit.createTempDirectory(
|
||||
String.format("unpacked-%s", type.getName()));
|
||||
final Path unpackDir = packageHandlers.unpack(cmd, unpackRootDir);
|
||||
if (!unpackDir.startsWith(TKit.workDir())) {
|
||||
state.deleteUnpackDirs.add(unpackDir);
|
||||
}
|
||||
cmd.setUnpackedPackageLocation(unpackDir);
|
||||
}
|
||||
type.applyTo(result);
|
||||
return result;
|
||||
}).get();
|
||||
});
|
||||
|
||||
case INSTALL -> {
|
||||
cmd.setUnpackedPackageLocation(null);
|
||||
final int installExitCode = packageHandlers.install(cmd);
|
||||
TKit.assertEquals(expectedInstallExitCode, installExitCode,
|
||||
String.format("Check installer exited with %d code", expectedInstallExitCode));
|
||||
}
|
||||
|
||||
case UNINSTALL -> {
|
||||
cmd.setUnpackedPackageLocation(null);
|
||||
packageHandlers.uninstall(cmd);
|
||||
}
|
||||
|
||||
case CREATE -> {
|
||||
cmd.setUnpackedPackageLocation(null);
|
||||
handler.processAction(action, cmd, expectedJPackageExitCode);
|
||||
}
|
||||
|
||||
case INITIALIZE -> {
|
||||
handler.processAction(action, cmd, expectedJPackageExitCode);
|
||||
}
|
||||
|
||||
case FINALIZE -> {
|
||||
state.deleteUnpackDirs.forEach(TKit::deleteDirectoryRecursive);
|
||||
state.deleteUnpackDirs.clear();
|
||||
}
|
||||
|
||||
default -> {
|
||||
handler.processAction(action, cmd.createImmutableCopy(), expectedJPackageExitCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ActionAction {
|
||||
PROCESS,
|
||||
SKIP,
|
||||
SKIP_NO_PACKAGE_HANDLER
|
||||
}
|
||||
|
||||
private ActionAction analizeAction(Action action) {
|
||||
Objects.requireNonNull(action);
|
||||
|
||||
if (jpackageFailed()) {
|
||||
return ActionAction.SKIP;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case CREATE -> {
|
||||
state.packageActions.add(action);
|
||||
}
|
||||
case INSTALL -> {
|
||||
state.packageActions.add(action);
|
||||
state.packageActions.remove(Action.UNPACK);
|
||||
}
|
||||
case UNINSTALL -> {
|
||||
state.packageActions.add(action);
|
||||
if (installFailed()) {
|
||||
return ActionAction.SKIP;
|
||||
}
|
||||
}
|
||||
case UNPACK -> {
|
||||
state.packageActions.add(action);
|
||||
state.packageActions.remove(Action.INSTALL);
|
||||
if (unpackNotSupported()) {
|
||||
return ActionAction.SKIP_NO_PACKAGE_HANDLER;
|
||||
}
|
||||
}
|
||||
case VERIFY_INSTALL -> {
|
||||
if (unpackNotSupported()) {
|
||||
return ActionAction.SKIP;
|
||||
}
|
||||
|
||||
if (installFailed()) {
|
||||
return ActionAction.SKIP;
|
||||
}
|
||||
}
|
||||
case VERIFY_UNINSTALL -> {
|
||||
if (installFailed() && processed(Action.UNINSTALL)) {
|
||||
return ActionAction.SKIP;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
|
||||
return ActionAction.PROCESS;
|
||||
}
|
||||
|
||||
private boolean processed(Action action) {
|
||||
Objects.requireNonNull(action);
|
||||
return state.packageActions.contains(action);
|
||||
}
|
||||
|
||||
private boolean installFailed() {
|
||||
return processed(Action.INSTALL) && expectedInstallExitCode != 0;
|
||||
}
|
||||
|
||||
private boolean jpackageFailed() {
|
||||
return processed(Action.CREATE) && expectedJPackageExitCode != 0;
|
||||
}
|
||||
|
||||
private boolean unpackNotSupported() {
|
||||
return processed(Action.UNPACK) && packageHandlers.unpackHandler().isEmpty();
|
||||
}
|
||||
|
||||
private final static class State {
|
||||
private final Set<Action> packageActions = new HashSet<>();
|
||||
private final List<Path> deleteUnpackDirs = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private class Handler implements BiConsumer<Action, JPackageCommand> {
|
||||
private Consumer<Action> createPackageTypeHandler(PackageType type, Handler handler) {
|
||||
final var cmd = jpackageFactory.get();
|
||||
cmd.setDefaultInputOutput().setDefaultAppName();
|
||||
if (BUNDLE_OUTPUT_DIR != null && !ignoreBundleOutputDir) {
|
||||
cmd.setArgumentValue("--dest", BUNDLE_OUTPUT_DIR.toString());
|
||||
}
|
||||
type.applyTo(cmd);
|
||||
return new PackageTypePipeline(type, expectedJPackageExitCode,
|
||||
expectedInstallExitCode, getPackageHandlers(type), handler.copy(), cmd);
|
||||
}
|
||||
|
||||
private record Handler(List<Consumer<JPackageCommand>> initializers,
|
||||
List<BiConsumer<JPackageCommand, Executor.Result>> bundleVerifiers,
|
||||
List<Consumer<JPackageCommand>> installVerifiers,
|
||||
List<Consumer<JPackageCommand>> uninstallVerifiers) {
|
||||
|
||||
Handler() {
|
||||
initializers = new ArrayList<>();
|
||||
bundleVerifiers = new ArrayList<>();
|
||||
installVerifiers = new ArrayList<>();
|
||||
uninstallVerifiers = new ArrayList<>();
|
||||
this(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
Handler copy() {
|
||||
return new Handler(List.copyOf(initializers), List.copyOf(bundleVerifiers),
|
||||
List.copyOf(installVerifiers), List.copyOf(uninstallVerifiers));
|
||||
}
|
||||
|
||||
boolean isVoid() {
|
||||
@ -573,18 +669,17 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
uninstallVerifiers.add(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Action action, JPackageCommand cmd) {
|
||||
public void processAction(Action action, JPackageCommand cmd, int expectedJPackageExitCode) {
|
||||
switch (action) {
|
||||
case INITIALIZE:
|
||||
case INITIALIZE -> {
|
||||
initializers.forEach(v -> v.accept(cmd));
|
||||
if (cmd.isImagePackageType()) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
cmd.executePrerequisiteActions();
|
||||
break;
|
||||
}
|
||||
|
||||
case CREATE:
|
||||
case CREATE -> {
|
||||
Executor.Result result = cmd.execute(expectedJPackageExitCode);
|
||||
if (expectedJPackageExitCode == 0) {
|
||||
TKit.assertFileExists(cmd.outputBundle());
|
||||
@ -593,39 +688,38 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
TKit.assertPathExists(outputBundle, false);
|
||||
});
|
||||
}
|
||||
verifyPackageBundle(cmd, result);
|
||||
break;
|
||||
verifyPackageBundle(cmd, result, expectedJPackageExitCode);
|
||||
}
|
||||
|
||||
case VERIFY_INSTALL:
|
||||
case VERIFY_INSTALL -> {
|
||||
if (expectedJPackageExitCode == 0) {
|
||||
verifyPackageInstalled(cmd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case VERIFY_UNINSTALL:
|
||||
case VERIFY_UNINSTALL -> {
|
||||
if (expectedJPackageExitCode == 0) {
|
||||
verifyPackageUninstalled(cmd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PURGE:
|
||||
if (expectedJPackageExitCode == 0) {
|
||||
var bundle = cmd.outputBundle();
|
||||
if (toSupplier(() -> TKit.deleteIfExists(bundle)).get()) {
|
||||
TKit.trace(String.format("Deleted [%s] package",
|
||||
bundle));
|
||||
}
|
||||
case PURGE -> {
|
||||
var bundle = cmd.outputBundle();
|
||||
if (toSupplier(() -> TKit.deleteIfExists(bundle)).get()) {
|
||||
TKit.trace(String.format("Deleted [%s] package", bundle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: // NOP
|
||||
default -> {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyPackageBundle(JPackageCommand cmd,
|
||||
Executor.Result result) {
|
||||
Executor.Result result, int expectedJPackageExitCode) {
|
||||
if (expectedJPackageExitCode == 0) {
|
||||
if (LINUX.contains(cmd.packageType())) {
|
||||
if (isOfType(cmd, LINUX)) {
|
||||
LinuxHelper.verifyPackageBundleEssential(cmd);
|
||||
}
|
||||
}
|
||||
@ -647,9 +741,7 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
});
|
||||
|
||||
if (!cmd.isRuntime()) {
|
||||
if (WINDOWS.contains(cmd.packageType())
|
||||
&& !cmd.isPackageUnpacked(
|
||||
"Not verifying desktop integration")) {
|
||||
if (isOfType(cmd, WINDOWS) && !cmd.isPackageUnpacked("Not verifying desktop integration")) {
|
||||
// Check main launcher
|
||||
WindowsHelper.verifyDesktopIntegration(cmd, null);
|
||||
// Check additional launchers
|
||||
@ -659,8 +751,7 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
}
|
||||
}
|
||||
|
||||
if (LauncherAsServiceVerifier.SUPPORTED_PACKAGES.contains(
|
||||
cmd.packageType())) {
|
||||
if (isOfType(cmd, LauncherAsServiceVerifier.SUPPORTED_PACKAGES)) {
|
||||
LauncherAsServiceVerifier.verify(cmd);
|
||||
}
|
||||
|
||||
@ -676,13 +767,13 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
&& !LauncherAsServiceVerifier.getLaunchersAsServices(cmd).isEmpty();
|
||||
|
||||
final long expectedRootCount;
|
||||
if (WINDOWS.contains(cmd.packageType())) {
|
||||
if (isOfType(cmd, WINDOWS)) {
|
||||
// On Windows it is always two entries:
|
||||
// installation home directory and MSI file
|
||||
expectedRootCount = 2;
|
||||
} else if (withServices && MAC_PKG.equals(cmd.packageType())) {
|
||||
} else if (withServices && isOfType(cmd, MAC_PKG)) {
|
||||
expectedRootCount = 2;
|
||||
} else if (LINUX.contains(cmd.packageType())) {
|
||||
} else if (isOfType(cmd, LINUX)) {
|
||||
Set<Path> roots = new HashSet<>();
|
||||
roots.add(Path.of("/").resolve(Path.of(cmd.getArgumentValue(
|
||||
"--install-dir", () -> "/opt")).getName(0)));
|
||||
@ -732,7 +823,7 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
if (!cmd.isRuntime()) {
|
||||
TKit.assertPathExists(cmd.appLauncherPath(), false);
|
||||
|
||||
if (WINDOWS.contains(cmd.packageType())) {
|
||||
if (isOfType(cmd, WINDOWS)) {
|
||||
// Check main launcher
|
||||
WindowsHelper.verifyDesktopIntegration(cmd, null);
|
||||
// Check additional launchers
|
||||
@ -743,54 +834,94 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
}
|
||||
|
||||
Path appInstallDir = cmd.appInstallationDirectory();
|
||||
if (TKit.isLinux() && Path.of("/").equals(appInstallDir)) {
|
||||
if (isOfType(cmd, LINUX) && Path.of("/").equals(appInstallDir)) {
|
||||
ApplicationLayout appLayout = cmd.appLayout();
|
||||
TKit.assertPathExists(appLayout.runtimeDirectory(), false);
|
||||
} else {
|
||||
TKit.assertPathExists(appInstallDir, false);
|
||||
}
|
||||
|
||||
if (LauncherAsServiceVerifier.SUPPORTED_PACKAGES.contains(
|
||||
cmd.packageType())) {
|
||||
if (isOfType(cmd, LauncherAsServiceVerifier.SUPPORTED_PACKAGES)) {
|
||||
LauncherAsServiceVerifier.verifyUninstalled(cmd);
|
||||
}
|
||||
|
||||
uninstallVerifiers.forEach(v -> v.accept(cmd));
|
||||
}
|
||||
|
||||
private final List<Consumer<JPackageCommand>> initializers;
|
||||
private final List<BiConsumer<JPackageCommand, Executor.Result>> bundleVerifiers;
|
||||
private final List<Consumer<JPackageCommand>> installVerifiers;
|
||||
private final List<Consumer<JPackageCommand>> uninstallVerifiers;
|
||||
}
|
||||
|
||||
private static Map<PackageType, PackageHandlers> createDefaultPackageHandlers() {
|
||||
HashMap<PackageType, PackageHandlers> handlers = new HashMap<>();
|
||||
if (TKit.isLinux()) {
|
||||
handlers.put(LINUX_DEB, LinuxHelper.createDebPackageHandlers());
|
||||
handlers.put(LINUX_RPM, LinuxHelper.createRpmPackageHandlers());
|
||||
private PackageHandlers getDefaultPackageHandlers(PackageType type) {
|
||||
switch (type) {
|
||||
case LINUX_DEB -> {
|
||||
return LinuxHelper.createDebPackageHandlers();
|
||||
}
|
||||
case LINUX_RPM -> {
|
||||
return LinuxHelper.createRpmPackageHandlers();
|
||||
}
|
||||
case WIN_MSI -> {
|
||||
return WindowsHelper.createMsiPackageHandlers(createMsiLog);
|
||||
}
|
||||
case WIN_EXE -> {
|
||||
return WindowsHelper.createExePackageHandlers(createMsiLog);
|
||||
}
|
||||
case MAC_DMG -> {
|
||||
return MacHelper.createDmgPackageHandlers();
|
||||
}
|
||||
case MAC_PKG -> {
|
||||
return MacHelper.createPkgPackageHandlers();
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PackageHandlers getPackageHandlers(PackageType type) {
|
||||
Objects.requireNonNull(type);
|
||||
|
||||
var reply = Optional.ofNullable(packageHandlers.get(type)).orElseGet(() -> {
|
||||
if (TKit.isLinux() && !PackageType.LINUX.contains(type)) {
|
||||
throw new IllegalArgumentException();
|
||||
} else if (TKit.isWindows() && !PackageType.WINDOWS.contains(type)) {
|
||||
throw new IllegalArgumentException();
|
||||
} else if (TKit.isOSX() && !PackageType.MAC.contains(type)) {
|
||||
throw new IllegalArgumentException();
|
||||
} else {
|
||||
return getDefaultPackageHandlers(type);
|
||||
}
|
||||
});
|
||||
|
||||
if (disabledInstallers.contains(type)) {
|
||||
reply = reply.copyWithNopInstaller();
|
||||
}
|
||||
|
||||
if (TKit.isWindows()) {
|
||||
handlers.put(WIN_MSI, WindowsHelper.createMsiPackageHandlers());
|
||||
handlers.put(WIN_EXE, WindowsHelper.createExePackageHandlers());
|
||||
if (disabledUninstallers.contains(type)) {
|
||||
reply = reply.copyWithNopUninstaller();
|
||||
}
|
||||
|
||||
if (TKit.isOSX()) {
|
||||
handlers.put(MAC_DMG, MacHelper.createDmgPackageHandlers());
|
||||
handlers.put(MAC_PKG, MacHelper.createPkgPackageHandlers());
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
return handlers;
|
||||
private static boolean isOfType(JPackageCommand cmd, PackageType packageTypes) {
|
||||
return isOfType(cmd, Set.of(packageTypes));
|
||||
}
|
||||
|
||||
private static boolean isOfType(JPackageCommand cmd, Set<PackageType> packageTypes) {
|
||||
return Optional.ofNullable(cmd.packageType()).map(packageTypes::contains).orElse(false);
|
||||
}
|
||||
|
||||
private Collection<PackageType> currentTypes;
|
||||
private Set<PackageType> excludeTypes;
|
||||
private int expectedJPackageExitCode;
|
||||
private Map<PackageType, Handler> handlers;
|
||||
private Set<String> namedInitializers;
|
||||
private Map<PackageType, PackageHandlers> packageHandlers;
|
||||
private int expectedInstallExitCode;
|
||||
private final Map<PackageType, Handler> handlers;
|
||||
private final Set<String> namedInitializers;
|
||||
private final Map<PackageType, PackageHandlers> packageHandlers;
|
||||
private final Set<PackageType> disabledInstallers;
|
||||
private final Set<PackageType> disabledUninstallers;
|
||||
private Predicate<PackageType> isPackageTypeSupported;
|
||||
private Supplier<JPackageCommand> jpackageFactory;
|
||||
private boolean ignoreBundleOutputDir;
|
||||
private boolean createMsiLog;
|
||||
|
||||
private static final Path BUNDLE_OUTPUT_DIR;
|
||||
|
||||
|
||||
@ -22,24 +22,25 @@
|
||||
*/
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
|
||||
import jdk.jpackage.test.PackageTest.PackageHandlers;
|
||||
|
||||
public class WindowsHelper {
|
||||
@ -67,21 +68,24 @@ public class WindowsHelper {
|
||||
return Path.of(cmd.getArgumentValue("--install-dir", cmd::name));
|
||||
}
|
||||
|
||||
private static void runMsiexecWithRetries(Executor misexec) {
|
||||
private static int runMsiexecWithRetries(Executor misexec, Optional<Path> msiLog) {
|
||||
Executor.Result result = null;
|
||||
final boolean isUnpack = misexec.getExecutable().orElseThrow().equals(Path.of("cmd"));
|
||||
final List<String> origArgs = msiLog.isPresent() ? misexec.getAllArguments() : null;
|
||||
for (int attempt = 0; attempt < 8; ++attempt) {
|
||||
msiLog.ifPresent(v -> misexec.clearArguments().addArguments(origArgs).addArgument("/L*v").addArgument(v));
|
||||
result = misexec.executeWithoutExitCodeCheck();
|
||||
|
||||
if (result.exitCode() == 1605) {
|
||||
// ERROR_UNKNOWN_PRODUCT, attempt to uninstall not installed
|
||||
// package
|
||||
return;
|
||||
return result.exitCode();
|
||||
}
|
||||
|
||||
// The given Executor may either be of an msiexec command or an
|
||||
// unpack.bat script containing the msiexec command. In the later
|
||||
// case, when misexec returns 1618, the unpack.bat may return 1603
|
||||
if ((result.exitCode() == 1618) || (result.exitCode() == 1603)) {
|
||||
if ((result.exitCode() == 1618) || (result.exitCode() == 1603 && isUnpack)) {
|
||||
// Another installation is already in progress.
|
||||
// Wait a little and try again.
|
||||
Long timeout = 1000L * (attempt + 3); // from 3 to 10 seconds
|
||||
@ -91,74 +95,123 @@ public class WindowsHelper {
|
||||
break;
|
||||
}
|
||||
|
||||
result.assertExitCodeIsZero();
|
||||
return result.exitCode();
|
||||
}
|
||||
|
||||
static PackageHandlers createMsiPackageHandlers() {
|
||||
BiConsumer<JPackageCommand, Boolean> installMsi = (cmd, install) -> {
|
||||
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
||||
var msiPath = TransientMsi.create(cmd).path();
|
||||
runMsiexecWithRetries(Executor.of("msiexec", "/qn", "/norestart",
|
||||
install ? "/i" : "/x").addArgument(msiPath));
|
||||
};
|
||||
static PackageHandlers createMsiPackageHandlers(boolean createMsiLog) {
|
||||
return new PackageHandlers(cmd -> installMsi(cmd, createMsiLog),
|
||||
cmd -> uninstallMsi(cmd, createMsiLog), WindowsHelper::unpackMsi);
|
||||
}
|
||||
|
||||
PackageHandlers msi = new PackageHandlers();
|
||||
msi.installHandler = cmd -> installMsi.accept(cmd, true);
|
||||
msi.uninstallHandler = cmd -> {
|
||||
if (Files.exists(cmd.outputBundle())) {
|
||||
installMsi.accept(cmd, false);
|
||||
private static Optional<Path> configureMsiLogFile(JPackageCommand cmd, boolean createMsiLog) {
|
||||
final Optional<Path> msiLogFile;
|
||||
if (createMsiLog) {
|
||||
msiLogFile = Optional.of(TKit.createTempFile(String.format("logs\\%s-msi.log",
|
||||
cmd.packageType().getName())));
|
||||
} else {
|
||||
msiLogFile = Optional.empty();
|
||||
}
|
||||
|
||||
cmd.winMsiLogFile(msiLogFile.orElse(null));
|
||||
|
||||
return msiLogFile;
|
||||
}
|
||||
|
||||
private static int runMsiInstaller(JPackageCommand cmd, boolean createMsiLog, boolean install) {
|
||||
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
||||
final var msiPath = TransientMsi.create(cmd).path();
|
||||
return runMsiexecWithRetries(Executor.of("msiexec", "/qn", "/norestart",
|
||||
install ? "/i" : "/x").addArgument(msiPath), configureMsiLogFile(cmd, createMsiLog));
|
||||
}
|
||||
|
||||
private static int installMsi(JPackageCommand cmd, boolean createMsiLog) {
|
||||
return runMsiInstaller(cmd, createMsiLog, true);
|
||||
}
|
||||
|
||||
private static void uninstallMsi(JPackageCommand cmd, boolean createMsiLog) {
|
||||
if (Files.exists(cmd.outputBundle())) {
|
||||
runMsiInstaller(cmd, createMsiLog, false);
|
||||
} else {
|
||||
configureMsiLogFile(cmd, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path unpackMsi(JPackageCommand cmd, Path destinationDir) {
|
||||
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
||||
configureMsiLogFile(cmd, false);
|
||||
final Path unpackBat = destinationDir.resolve("unpack.bat");
|
||||
final Path unpackDir = destinationDir.resolve(
|
||||
TKit.removeRootFromAbsolutePath(
|
||||
getInstallationRootDirectory(cmd)));
|
||||
|
||||
final Path msiPath = TransientMsi.create(cmd).path();
|
||||
|
||||
// Put msiexec in .bat file because can't pass value of TARGETDIR
|
||||
// property containing spaces through ProcessBuilder properly.
|
||||
// Set folder permissions to allow msiexec unpack msi bundle.
|
||||
TKit.createTextFile(unpackBat, List.of(
|
||||
String.format("icacls \"%s\" /inheritance:e /grant Users:M",
|
||||
destinationDir),
|
||||
String.join(" ", List.of(
|
||||
"msiexec",
|
||||
"/a",
|
||||
String.format("\"%s\"", msiPath),
|
||||
"/qn",
|
||||
String.format("TARGETDIR=\"%s\"",
|
||||
unpackDir.toAbsolutePath().normalize())))));
|
||||
runMsiexecWithRetries(Executor.of("cmd", "/c", unpackBat.toString()), Optional.empty());
|
||||
|
||||
//
|
||||
// WiX3 uses "." as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table
|
||||
// WiX4 uses "PFiles64" as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table
|
||||
// msiexec creates "Program Files/./<App Installation Directory>" from WiX3 msi which translates to "Program Files/<App Installation Directory>"
|
||||
// msiexec creates "Program Files/PFiles64/<App Installation Directory>" from WiX4 msi
|
||||
// So for WiX4 msi we need to transform "Program Files/PFiles64/<App Installation Directory>" into "Program Files/<App Installation Directory>"
|
||||
//
|
||||
// WiX4 does the same thing for %LocalAppData%.
|
||||
//
|
||||
for (var extraPathComponent : List.of("PFiles64", "LocalApp")) {
|
||||
if (Files.isDirectory(unpackDir.resolve(extraPathComponent))) {
|
||||
Path installationSubDirectory = getInstallationSubDirectory(cmd);
|
||||
Path from = Path.of(extraPathComponent).resolve(installationSubDirectory);
|
||||
Path to = installationSubDirectory;
|
||||
TKit.trace(String.format("Convert [%s] into [%s] in [%s] directory", from, to,
|
||||
unpackDir));
|
||||
ThrowingRunnable.toRunnable(() -> {
|
||||
Files.createDirectories(unpackDir.resolve(to).getParent());
|
||||
Files.move(unpackDir.resolve(from), unpackDir.resolve(to));
|
||||
TKit.deleteDirectoryRecursive(unpackDir.resolve(extraPathComponent));
|
||||
}).run();
|
||||
}
|
||||
};
|
||||
msi.unpackHandler = (cmd, destinationDir) -> {
|
||||
cmd.verifyIsOfType(PackageType.WIN_MSI);
|
||||
final Path unpackBat = destinationDir.resolve("unpack.bat");
|
||||
final Path unpackDir = destinationDir.resolve(
|
||||
TKit.removeRootFromAbsolutePath(
|
||||
getInstallationRootDirectory(cmd)));
|
||||
}
|
||||
return destinationDir;
|
||||
}
|
||||
|
||||
final Path msiPath = TransientMsi.create(cmd).path();
|
||||
static PackageHandlers createExePackageHandlers(boolean createMsiLog) {
|
||||
return new PackageHandlers(cmd -> installExe(cmd, createMsiLog), WindowsHelper::uninstallExe, Optional.empty());
|
||||
}
|
||||
|
||||
// Put msiexec in .bat file because can't pass value of TARGETDIR
|
||||
// property containing spaces through ProcessBuilder properly.
|
||||
// Set folder permissions to allow msiexec unpack msi bundle.
|
||||
TKit.createTextFile(unpackBat, List.of(
|
||||
String.format("icacls \"%s\" /inheritance:e /grant Users:M",
|
||||
destinationDir),
|
||||
String.join(" ", List.of(
|
||||
"msiexec",
|
||||
"/a",
|
||||
String.format("\"%s\"", msiPath),
|
||||
"/qn",
|
||||
String.format("TARGETDIR=\"%s\"",
|
||||
unpackDir.toAbsolutePath().normalize())))));
|
||||
runMsiexecWithRetries(Executor.of("cmd", "/c", unpackBat.toString()));
|
||||
private static int runExeInstaller(JPackageCommand cmd, boolean createMsiLog, boolean install) {
|
||||
cmd.verifyIsOfType(PackageType.WIN_EXE);
|
||||
Executor exec = new Executor().setExecutable(cmd.outputBundle());
|
||||
if (install) {
|
||||
exec.addArgument("/qn").addArgument("/norestart");
|
||||
} else {
|
||||
exec.addArgument("uninstall");
|
||||
}
|
||||
return runMsiexecWithRetries(exec, configureMsiLogFile(cmd, createMsiLog));
|
||||
}
|
||||
|
||||
//
|
||||
// WiX3 uses "." as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table
|
||||
// WiX4 uses "PFiles64" as the value of "DefaultDir" field for "ProgramFiles64Folder" folder in msi's Directory table
|
||||
// msiexec creates "Program Files/./<App Installation Directory>" from WiX3 msi which translates to "Program Files/<App Installation Directory>"
|
||||
// msiexec creates "Program Files/PFiles64/<App Installation Directory>" from WiX4 msi
|
||||
// So for WiX4 msi we need to transform "Program Files/PFiles64/<App Installation Directory>" into "Program Files/<App Installation Directory>"
|
||||
//
|
||||
// WiX4 does the same thing for %LocalAppData%.
|
||||
//
|
||||
for (var extraPathComponent : List.of("PFiles64", "LocalApp")) {
|
||||
if (Files.isDirectory(unpackDir.resolve(extraPathComponent))) {
|
||||
Path installationSubDirectory = getInstallationSubDirectory(cmd);
|
||||
Path from = Path.of(extraPathComponent).resolve(installationSubDirectory);
|
||||
Path to = installationSubDirectory;
|
||||
TKit.trace(String.format("Convert [%s] into [%s] in [%s] directory", from, to,
|
||||
unpackDir));
|
||||
ThrowingRunnable.toRunnable(() -> {
|
||||
Files.createDirectories(unpackDir.resolve(to).getParent());
|
||||
Files.move(unpackDir.resolve(from), unpackDir.resolve(to));
|
||||
TKit.deleteDirectoryRecursive(unpackDir.resolve(extraPathComponent));
|
||||
}).run();
|
||||
}
|
||||
}
|
||||
return destinationDir;
|
||||
};
|
||||
return msi;
|
||||
private static int installExe(JPackageCommand cmd, boolean createMsiLog) {
|
||||
return runExeInstaller(cmd, createMsiLog, true);
|
||||
}
|
||||
|
||||
private static void uninstallExe(JPackageCommand cmd) {
|
||||
if (Files.exists(cmd.outputBundle())) {
|
||||
runExeInstaller(cmd, false, false);
|
||||
} else {
|
||||
configureMsiLogFile(cmd, false);
|
||||
}
|
||||
}
|
||||
|
||||
record TransientMsi(Path path) {
|
||||
@ -204,28 +257,6 @@ public class WindowsHelper {
|
||||
}
|
||||
}
|
||||
|
||||
static PackageHandlers createExePackageHandlers() {
|
||||
BiConsumer<JPackageCommand, Boolean> installExe = (cmd, install) -> {
|
||||
cmd.verifyIsOfType(PackageType.WIN_EXE);
|
||||
Executor exec = new Executor().setExecutable(cmd.outputBundle());
|
||||
if (install) {
|
||||
exec.addArgument("/qn").addArgument("/norestart");
|
||||
} else {
|
||||
exec.addArgument("uninstall");
|
||||
}
|
||||
runMsiexecWithRetries(exec);
|
||||
};
|
||||
|
||||
PackageHandlers exe = new PackageHandlers();
|
||||
exe.installHandler = cmd -> installExe.accept(cmd, true);
|
||||
exe.uninstallHandler = cmd -> {
|
||||
if (Files.exists(cmd.outputBundle())) {
|
||||
installExe.accept(cmd, false);
|
||||
}
|
||||
};
|
||||
return exe;
|
||||
}
|
||||
|
||||
static void verifyDesktopIntegration(JPackageCommand cmd,
|
||||
String launcherName) {
|
||||
new DesktopIntegrationVerifier(cmd, launcherName);
|
||||
@ -415,14 +446,12 @@ public class WindowsHelper {
|
||||
}
|
||||
|
||||
private void verifySystemDesktopShortcut(boolean exists) {
|
||||
Path dir = Path.of(queryRegistryValueCache(
|
||||
SYSTEM_SHELL_FOLDERS_REGKEY, "Common Desktop"));
|
||||
Path dir = SpecialFolder.COMMON_DESKTOP.getPath();
|
||||
verifyShortcut(dir.resolve(desktopShortcutPath), exists);
|
||||
}
|
||||
|
||||
private void verifyUserLocalDesktopShortcut(boolean exists) {
|
||||
Path dir = Path.of(
|
||||
queryRegistryValueCache(USER_SHELL_FOLDERS_REGKEY, "Desktop"));
|
||||
Path dir = SpecialFolder.USER_DESKTOP.getPath();
|
||||
verifyShortcut(dir.resolve(desktopShortcutPath), exists);
|
||||
}
|
||||
|
||||
@ -445,19 +474,22 @@ public class WindowsHelper {
|
||||
Path shortcutPath = shortcutsRoot.resolve(startMenuShortcutPath);
|
||||
verifyShortcut(shortcutPath, exists);
|
||||
if (!exists) {
|
||||
TKit.assertDirectoryNotEmpty(shortcutPath.getParent());
|
||||
final var parentDir = shortcutPath.getParent();
|
||||
if (Files.isDirectory(parentDir)) {
|
||||
TKit.assertDirectoryNotEmpty(parentDir);
|
||||
} else {
|
||||
TKit.assertPathExists(parentDir, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void verifySystemStartMenuShortcut(boolean exists) {
|
||||
verifyStartMenuShortcut(Path.of(queryRegistryValueCache(
|
||||
SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs")), exists);
|
||||
verifyStartMenuShortcut(SpecialFolder.COMMON_START_MENU_PROGRAMS.getPath(), exists);
|
||||
|
||||
}
|
||||
|
||||
private void verifyUserLocalStartMenuShortcut(boolean exists) {
|
||||
verifyStartMenuShortcut(Path.of(queryRegistryValueCache(
|
||||
USER_SHELL_FOLDERS_REGKEY, "Programs")), exists);
|
||||
verifyStartMenuShortcut(SpecialFolder.USER_START_MENU_PROGRAMS.getPath(), exists);
|
||||
}
|
||||
|
||||
private void verifyFileAssociationsRegistry(Path faFile) {
|
||||
@ -565,16 +597,66 @@ public class WindowsHelper {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String queryRegistryValueCache(String keyPath,
|
||||
String valueName) {
|
||||
String key = String.format("[%s][%s]", keyPath, valueName);
|
||||
String value = REGISTRY_VALUES.get(key);
|
||||
if (value == null) {
|
||||
value = queryRegistryValue(keyPath, valueName);
|
||||
REGISTRY_VALUES.put(key, value);
|
||||
// See .NET special folders
|
||||
private enum SpecialFolderDotNet {
|
||||
Desktop,
|
||||
CommonDesktop,
|
||||
|
||||
Programs,
|
||||
CommonPrograms;
|
||||
|
||||
Path getPath() {
|
||||
final var str = Executor.of("powershell", "-NoLogo", "-NoProfile",
|
||||
"-NonInteractive", "-Command",
|
||||
String.format("[Environment]::GetFolderPath('%s')", name())
|
||||
).saveFirstLineOfOutput().execute().getFirstLineOfOutput();
|
||||
|
||||
TKit.trace(String.format("Value of .NET special folder '%s' is [%s]", name(), str));
|
||||
|
||||
return Path.of(str);
|
||||
}
|
||||
}
|
||||
|
||||
private record RegValuePath(String keyPath, String valueName) {
|
||||
RegValuePath {
|
||||
Objects.requireNonNull(keyPath);
|
||||
Objects.requireNonNull(valueName);
|
||||
}
|
||||
|
||||
return value;
|
||||
Optional<String> findValue() {
|
||||
return Optional.ofNullable(queryRegistryValue(keyPath, valueName));
|
||||
}
|
||||
}
|
||||
|
||||
private enum SpecialFolder {
|
||||
COMMON_START_MENU_PROGRAMS(SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs", SpecialFolderDotNet.CommonPrograms),
|
||||
USER_START_MENU_PROGRAMS(USER_SHELL_FOLDERS_REGKEY, "Programs", SpecialFolderDotNet.Programs),
|
||||
|
||||
COMMON_DESKTOP(SYSTEM_SHELL_FOLDERS_REGKEY, "Common Desktop", SpecialFolderDotNet.CommonDesktop),
|
||||
USER_DESKTOP(USER_SHELL_FOLDERS_REGKEY, "Desktop", SpecialFolderDotNet.Desktop);
|
||||
|
||||
SpecialFolder(String keyPath, String valueName) {
|
||||
reg = new RegValuePath(keyPath, valueName);
|
||||
alt = Optional.empty();
|
||||
}
|
||||
|
||||
SpecialFolder(String keyPath, String valueName, SpecialFolderDotNet alt) {
|
||||
reg = new RegValuePath(keyPath, valueName);
|
||||
this.alt = Optional.of(alt);
|
||||
}
|
||||
|
||||
Path getPath() {
|
||||
return CACHE.computeIfAbsent(this, k -> reg.findValue().map(Path::of).orElseGet(() -> {
|
||||
return alt.map(SpecialFolderDotNet::getPath).orElseThrow(() -> {
|
||||
return new NoSuchElementException(String.format("Failed to find path to %s folder", name()));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private final RegValuePath reg;
|
||||
private final Optional<SpecialFolderDotNet> alt;
|
||||
|
||||
private final static Map<SpecialFolder, Path> CACHE = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
private static final class ShortPathUtils {
|
||||
@ -617,7 +699,5 @@ public class WindowsHelper {
|
||||
private static final String SYSTEM_SHELL_FOLDERS_REGKEY = "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
|
||||
private static final String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
|
||||
|
||||
private static final Map<String, String> REGISTRY_VALUES = new HashMap<>();
|
||||
|
||||
private static final int WIN_MAX_PATH = 260;
|
||||
}
|
||||
|
||||
32
test/jdk/tools/jpackage/resources/fail-os-condition.wxf
Normal file
32
test/jdk/tools/jpackage/resources/fail-os-condition.wxf
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (c) 2025, 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Fragment>
|
||||
<Condition Message="Not supported on this version of Windows">0</Condition>
|
||||
<ComponentGroup Id="FragmentOsCondition"/>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
77
test/jdk/tools/jpackage/windows/WinOSConditionTest.java
Normal file
77
test/jdk/tools/jpackage/windows/WinOSConditionTest.java
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2025, 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.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.RunnablePackageTest.Action;
|
||||
import jdk.jpackage.test.TKit;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary jpackage test that installer blocks on Windows of older version
|
||||
* @library /test/jdk/tools/jpackage/helpers
|
||||
* @key jpackagePlatformPackage
|
||||
* @build jdk.jpackage.test.*
|
||||
* @compile -Xlint:all -Werror WinOSConditionTest.java
|
||||
* @requires (os.family == "windows")
|
||||
* @requires (jpackage.test.SQETest == null)
|
||||
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
|
||||
* --jpt-run=WinOSConditionTest
|
||||
*/
|
||||
public class WinOSConditionTest {
|
||||
|
||||
@Test
|
||||
public static void test() throws IOException {
|
||||
// Use custom always failing condition. Installation is expected to fail.
|
||||
// This way the test covers:
|
||||
// 1. If jpackage picks custom OS version condition from the resource directory;
|
||||
// 2. If the installer created by jpackage uses OS version condition.
|
||||
new PackageTest().ignoreBundleOutputDir()
|
||||
.forTypes(PackageType.WINDOWS)
|
||||
.configureHelloApp()
|
||||
.addInitializer(JPackageCommand::setFakeRuntime)
|
||||
.addInitializer(cmd -> {
|
||||
final var resourceDir = TKit.createTempDirectory("resource-dir");
|
||||
Files.copy(TKit.TEST_SRC_ROOT.resolve("resources/fail-os-condition.wxf"), resourceDir.resolve("os-condition.wxf"));
|
||||
// Create a per-user installer to let user without admin privileges install it.
|
||||
cmd.addArguments("--win-per-user-install",
|
||||
"--resource-dir", resourceDir.toString()).setFakeRuntime();
|
||||
})
|
||||
.addUninstallVerifier(cmd -> {
|
||||
// MSI error code 1603 is generic.
|
||||
// Dig into the last msi log file for log messages specific to failed condition.
|
||||
try (final var lines = cmd.winMsiLogFileContents().orElseThrow()) {
|
||||
TKit.assertTextStream("Doing action: LaunchConditions").predicate(String::endsWith)
|
||||
.andThen(TKit.assertTextStream("Not supported on this version of Windows").predicate(String::endsWith)).apply(lines);
|
||||
}
|
||||
})
|
||||
.createMsiLog(true)
|
||||
.setExpectedInstallExitCode(1603)
|
||||
// Create, try install the package (installation should fail) and verify it is not installed.
|
||||
.run(Action.CREATE, Action.INSTALL, Action.VERIFY_UNINSTALL);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user