mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-23 21:30:26 +00:00
8379345: jpackage: Fix issues in tests to improve their flexibility
Reviewed-by: almatvee
This commit is contained in:
parent
ad0f078669
commit
a1b4ad097e
@ -67,6 +67,7 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import jdk.internal.util.OSVersion;
|
||||
import jdk.jpackage.internal.util.MemoizingSupplier;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
@ -233,7 +234,7 @@ import jdk.jpackage.internal.util.function.ThrowingSupplier;
|
||||
* An untrusted certificate can NOT be used with /usr/bin/codesign. Use
|
||||
*
|
||||
* <pre>
|
||||
* /usr/bin/security security add-trusted-cert -k foo.keychain cert.pem
|
||||
* /usr/bin/security add-trusted-cert -k foo.keychain cert.pem
|
||||
* </pre>
|
||||
*
|
||||
* command to add trusted certificate from "cert.pem" file to "foo.keychain"
|
||||
@ -440,7 +441,21 @@ public final class MacSign {
|
||||
}
|
||||
|
||||
Keychain unlock() {
|
||||
createExecutor("unlock-keychain").execute();
|
||||
var exec = createExecutor("unlock-keychain");
|
||||
|
||||
exec.execute();
|
||||
|
||||
if (UnlockKeychainWithOsascript.VALUE) {
|
||||
exec = Executor.of("osascript")
|
||||
.addArguments(SIGN_UTILS_SCRIPT.toString(), "run-shell-script")
|
||||
.addArgument(Stream.concat(
|
||||
Stream.of(exec.getExecutable().orElseThrow().toString()),
|
||||
exec.getAllArguments().stream()
|
||||
).collect(joining(" ")));
|
||||
|
||||
exec.execute();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -576,29 +591,43 @@ public final class MacSign {
|
||||
}
|
||||
|
||||
private static CertificateStats create(KeychainWithCertsSpec spec) {
|
||||
final var allCertificates = spec.keychain().findCertificates();
|
||||
final List<ResolvedCertificateRequest> allResolvedCertificateRequests = new ArrayList<>();
|
||||
final Map<X509Certificate, Exception> unmappedCertificates = new HashMap<>();
|
||||
|
||||
withTempDirectory(workDir -> {
|
||||
for (final var cert : allCertificates) {
|
||||
ResolvedCertificateRequest resolvedCertificateRequest;
|
||||
try {
|
||||
resolvedCertificateRequest = new ResolvedCertificateRequest(cert);
|
||||
} catch (RuntimeException ex) {
|
||||
unmappedCertificates.put(cert, ExceptionBox.unbox(ex));
|
||||
continue;
|
||||
}
|
||||
final Runnable workload = () -> {
|
||||
final var allCertificates = spec.keychain().findCertificates();
|
||||
withTempDirectory(workDir -> {
|
||||
for (final var cert : allCertificates) {
|
||||
ResolvedCertificateRequest resolvedCertificateRequest;
|
||||
try {
|
||||
resolvedCertificateRequest = new ResolvedCertificateRequest(cert);
|
||||
} catch (RuntimeException ex) {
|
||||
unmappedCertificates.put(cert, ExceptionBox.unbox(ex));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (spec.certificateRequests().stream().anyMatch(resolvedCertificateRequest.installed()::match)) {
|
||||
final var certFile = workDir.resolve(CertificateHash.of(cert).toString() + ".pem");
|
||||
final var verifyStatus = verifyCertificate(resolvedCertificateRequest, spec.keychain(), certFile);
|
||||
resolvedCertificateRequest = resolvedCertificateRequest.copyVerified(verifyStatus);
|
||||
}
|
||||
if (spec.certificateRequests().stream().anyMatch(resolvedCertificateRequest.installed()::match)) {
|
||||
final var certFile = workDir.resolve(CertificateHash.of(cert).toString() + ".pem");
|
||||
final var verifyStatus = verifyCertificate(resolvedCertificateRequest, spec.keychain(), certFile);
|
||||
resolvedCertificateRequest = resolvedCertificateRequest.copyVerified(verifyStatus);
|
||||
}
|
||||
|
||||
allResolvedCertificateRequests.add(resolvedCertificateRequest);
|
||||
}
|
||||
});
|
||||
allResolvedCertificateRequests.add(resolvedCertificateRequest);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Starting from some macOS version, it is no longer necessary to have the keychain
|
||||
// in the list of keychains when running the "/usr/bin/security verify-cert ..." command to verify its certificate(s).
|
||||
// The exact version when they relaxed this constraint is unknown, but it is still required on Catalina 10.15.7.
|
||||
// On Catalina, if the keychain is not in the list of keychains, "/usr/bin/security verify-cert ..." command
|
||||
// executed on the certificates of this keychain returns "untrusted" result.
|
||||
if (OSVersion.current().compareTo(new OSVersion(10, 16)) < 0) {
|
||||
// macOS Catalina or older
|
||||
withKeychains(spec).addToSearchList().run(workload);
|
||||
} else {
|
||||
workload.run();
|
||||
}
|
||||
|
||||
return new CertificateStats(allResolvedCertificateRequests,
|
||||
List.copyOf(spec.certificateRequests()), unmappedCertificates);
|
||||
@ -1272,7 +1301,11 @@ public final class MacSign {
|
||||
for (final var quite : List.of(true, false)) {
|
||||
result = security("verify-cert", "-L", "-n",
|
||||
quite ? "-q" : "-v",
|
||||
"-c", certFile.normalize().toString(),
|
||||
// Use "-r" option to verify the certificate against itself.
|
||||
// "-c" option works on newer macOS versions, but on older ones (at least on Catalina 10.15.7),
|
||||
// in case there are two self-signed certificates with the same name in the given keychain,
|
||||
// it links them in the certificate list and fails verification.
|
||||
"-r", certFile.normalize().toString(),
|
||||
"-k", keychain.name(),
|
||||
"-p", resolvedCertificateRequest.installed().type().verifyPolicy()).saveOutput(!quite).executeWithoutExitCodeCheck();
|
||||
if (result.getExitCode() == 0) {
|
||||
@ -1463,4 +1496,10 @@ public final class MacSign {
|
||||
// faketime is not a standard macOS command.
|
||||
// One way to get it is with Homebrew.
|
||||
private static final Path FAKETIME = Path.of(Optional.ofNullable(TKit.getConfigProperty("faketime")).orElse("/usr/local/bin/faketime"));
|
||||
|
||||
// Run the "/usr/bin/system unlock-keychain" command in Terminal.app if
|
||||
// the current process runs in an SSH session and the OS version is macOS Catalina or older.
|
||||
private static final class UnlockKeychainWithOsascript {
|
||||
static final boolean VALUE = System.getenv("SSH_CONNECTION") != null && OSVersion.current().compareTo(new OSVersion(10, 16)) < 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,8 +93,11 @@ public final class MacSignVerify {
|
||||
final var exec = Executor.of(
|
||||
"/usr/bin/codesign",
|
||||
"-d",
|
||||
"--entitlements", "-",
|
||||
"--xml", path.toString()).saveOutput().dumpOutput().binaryOutput();
|
||||
// `--entitlements :-` will print entitlements as XML plist in the stdout and "Executable=..." message to the stderr.
|
||||
// Prefer this option combination to `--entitlements - --xml` as
|
||||
// the latter doesn't work on older macOS releases (Proved unsupported on Catalina 10.15.7).
|
||||
"--entitlements", ":-",
|
||||
path.toString()).saveOutput().dumpOutput().binaryOutput();
|
||||
final var result = exec.execute();
|
||||
var xml = result.byteStdout();
|
||||
if (xml.length == 0) {
|
||||
|
||||
@ -37,7 +37,6 @@ import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
@ -606,25 +605,6 @@ public final class TKit {
|
||||
return JtregSkippedExceptionClass.INSTANCE.isInstance(t);
|
||||
}
|
||||
|
||||
public static Path createRelativePathCopy(final Path file) {
|
||||
Path fileCopy = ThrowingSupplier.toSupplier(() -> {
|
||||
Path localPath = createTempFile(file.getFileName());
|
||||
Files.copy(file, localPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
return localPath;
|
||||
}).get().toAbsolutePath().normalize();
|
||||
|
||||
final Path basePath = Path.of(".").toAbsolutePath().normalize();
|
||||
try {
|
||||
return basePath.relativize(fileCopy);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root
|
||||
trace(String.format("Failed to relativize [%s] at [%s]", fileCopy,
|
||||
basePath));
|
||||
printStackTrace(ex);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public static void waitForFileCreated(Path fileToWaitFor,
|
||||
Duration timeout, Duration afterCreatedTimeout) throws IOException {
|
||||
waitForFileCreated(fileToWaitFor, timeout);
|
||||
|
||||
@ -154,6 +154,7 @@ public class MainTest extends JUnitAdapter {
|
||||
|
||||
JPackageCommand.helloAppImage()
|
||||
.ignoreDefaultVerbose(true)
|
||||
.ignoreDefaultRuntime(true)
|
||||
.useToolProvider(jpackageToolProviderMock)
|
||||
.execute(jpackageExitCode);
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ public class SigningRuntimeImagePackageTest {
|
||||
// This way we can test if jpackage keeps or replaces the signature of
|
||||
// the predefined runtime bundle when backing it in the pkg or dmg installer.
|
||||
return new SignKeyOptionWithKeychain(
|
||||
SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME,
|
||||
SignKeyOption.Type.SIGN_KEY_IDENTITY_APP_IMAGE,
|
||||
SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD,
|
||||
SigningBase.StandardKeychain.MAIN.keychain());
|
||||
}
|
||||
|
||||
@ -23,6 +23,8 @@
|
||||
|
||||
import static java.util.Map.entry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
@ -84,7 +86,7 @@ public class FileAssociationsTest {
|
||||
@Test
|
||||
@Parameter("true")
|
||||
@Parameter("false")
|
||||
public static void test(boolean includeDescription) {
|
||||
public static void test(boolean includeDescription) throws IOException {
|
||||
PackageTest packageTest = new PackageTest();
|
||||
|
||||
// Not supported
|
||||
@ -96,10 +98,8 @@ public class FileAssociationsTest {
|
||||
}
|
||||
fa.applyTo(packageTest);
|
||||
|
||||
Path icon = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "icon"
|
||||
+ TKit.ICON_SUFFIX));
|
||||
|
||||
icon = TKit.createRelativePathCopy(icon);
|
||||
var icon = TKit.createTempDirectory("icon-dir").resolve(ICON.getFileName());
|
||||
Files.copy(ICON, icon);
|
||||
|
||||
new FileAssociations("jptest2")
|
||||
.setFilename("fa2")
|
||||
@ -151,4 +151,6 @@ public class FileAssociationsTest {
|
||||
.addInitializer(JPackageCommand::setFakeRuntime)
|
||||
.setExpectedExitCode(1);
|
||||
}
|
||||
|
||||
private static final Path ICON = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "icon" + TKit.ICON_SUFFIX));
|
||||
}
|
||||
|
||||
@ -21,21 +21,23 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import static jdk.internal.util.OperatingSystem.LINUX;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import static jdk.internal.util.OperatingSystem.LINUX;
|
||||
import jdk.jpackage.internal.util.Slot;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.LinuxHelper;
|
||||
import jdk.jpackage.test.Executor;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.LinuxHelper;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.TKit;
|
||||
|
||||
/**
|
||||
@ -93,11 +95,7 @@ public class LicenseTest {
|
||||
|
||||
@Test
|
||||
public static void testCommon() {
|
||||
PackageTest test = new PackageTest().configureHelloApp()
|
||||
.addInitializer(cmd -> {
|
||||
cmd.addArguments("--license-file", TKit.createRelativePathCopy(
|
||||
LICENSE_FILE));
|
||||
});
|
||||
PackageTest test = new PackageTest().configureHelloApp().mutate(LicenseTest::setLicenseFile);
|
||||
|
||||
initMacDmgLicenseVerifier(test.forTypes(PackageType.MAC_DMG));
|
||||
initLinuxLicenseVerifier(test.forTypes(PackageType.LINUX));
|
||||
@ -127,19 +125,16 @@ public class LicenseTest {
|
||||
|
||||
@Test(ifOS = LINUX)
|
||||
public static void testCustomDebianCopyright() {
|
||||
new CustomDebianCopyrightTest().run();
|
||||
new CustomDebianCopyrightTest(false).run();
|
||||
}
|
||||
|
||||
@Test(ifOS = LINUX)
|
||||
public static void testCustomDebianCopyrightSubst() {
|
||||
new CustomDebianCopyrightTest().withSubstitution(true).run();
|
||||
new CustomDebianCopyrightTest(true).run();
|
||||
}
|
||||
|
||||
private static PackageTest initMacDmgLicenseVerifier(PackageTest test) {
|
||||
return test
|
||||
.addBundleVerifier(cmd -> {
|
||||
verifyLicenseFileInDMGPackage(cmd);
|
||||
});
|
||||
return test.addBundleVerifier(LicenseTest::verifyLicenseFileInDMGPackage);
|
||||
}
|
||||
|
||||
private static void verifyLicenseFileInDMGPackage(JPackageCommand cmd)
|
||||
@ -179,10 +174,9 @@ public class LicenseTest {
|
||||
PackageTest test = new PackageTest()
|
||||
.forTypes(PackageType.LINUX)
|
||||
.configureHelloApp()
|
||||
.addInitializer(JPackageCommand::setFakeRuntime)
|
||||
.mutate(LicenseTest::setLicenseFile)
|
||||
.addInitializer(cmd -> {
|
||||
cmd.setFakeRuntime();
|
||||
cmd.addArguments("--license-file", TKit.createRelativePathCopy(
|
||||
LICENSE_FILE));
|
||||
cmd.addArguments("--install-dir", installDir);
|
||||
});
|
||||
|
||||
@ -191,6 +185,18 @@ public class LicenseTest {
|
||||
test.run();
|
||||
}
|
||||
|
||||
private static void setLicenseFile(PackageTest test) {
|
||||
var inputLicenseFile = Slot.<Path>createEmpty();
|
||||
|
||||
test.addRunOnceInitializer(() -> {
|
||||
var dir = TKit.createTempDirectory("license-dir");
|
||||
inputLicenseFile.set(dir.resolve(LICENSE_FILE.getFileName()));
|
||||
Files.copy(LICENSE_FILE, inputLicenseFile.get());
|
||||
}).addInitializer(cmd -> {
|
||||
cmd.setArgumentValue("--license-file", inputLicenseFile.get());
|
||||
});
|
||||
}
|
||||
|
||||
private static Path rpmLicenseFile(JPackageCommand cmd) {
|
||||
final Path licenseRoot = Path.of(
|
||||
new Executor()
|
||||
@ -296,12 +302,36 @@ public class LicenseTest {
|
||||
TKit.assertPathExists(licenseFile.getParent(), false);
|
||||
}
|
||||
|
||||
private static class CustomDebianCopyrightTest {
|
||||
CustomDebianCopyrightTest() {
|
||||
withSubstitution(false);
|
||||
private record CustomDebianCopyrightTest(boolean withSubstitution) {
|
||||
|
||||
private String copyright() {
|
||||
// Different values just to make easy to figure out from the test log which test was executed.
|
||||
if (withSubstitution) {
|
||||
return "Duke (C)";
|
||||
} else {
|
||||
return "Java (C)";
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> licenseFileText(String copyright, String licenseText) {
|
||||
private String licenseText() {
|
||||
// Different values just to make easy to figure out from the test log which test was executed.
|
||||
if (withSubstitution) {
|
||||
return "The quick brown fox\n jumps over the lazy dog";
|
||||
} else {
|
||||
return "How vexingly quick daft zebras jump!";
|
||||
}
|
||||
}
|
||||
|
||||
private String name() {
|
||||
// Different values just to make easy to figure out from the test log which test was executed.
|
||||
if (withSubstitution) {
|
||||
return "CustomDebianCopyrightWithSubst";
|
||||
} else {
|
||||
return "CustomDebianCopyright";
|
||||
}
|
||||
}
|
||||
|
||||
static private List<String> licenseFileText(String copyright, String licenseText) {
|
||||
List<String> lines = new ArrayList<>(List.of(
|
||||
String.format("Copyright=%s", copyright),
|
||||
"Foo",
|
||||
@ -313,28 +343,14 @@ public class LicenseTest {
|
||||
|
||||
private List<String> licenseFileText() {
|
||||
if (withSubstitution) {
|
||||
return licenseFileText("APPLICATION_COPYRIGHT",
|
||||
"APPLICATION_LICENSE_TEXT");
|
||||
return licenseFileText("APPLICATION_COPYRIGHT", "APPLICATION_LICENSE_TEXT");
|
||||
} else {
|
||||
return expectedLicenseFileText();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> expectedLicenseFileText() {
|
||||
return licenseFileText(copyright, licenseText);
|
||||
}
|
||||
|
||||
CustomDebianCopyrightTest withSubstitution(boolean v) {
|
||||
withSubstitution = v;
|
||||
// Different values just to make easy to figure out from the test log which test was executed.
|
||||
if (v) {
|
||||
copyright = "Duke (C)";
|
||||
licenseText = "The quick brown fox\n jumps over the lazy dog";
|
||||
} else {
|
||||
copyright = "Java (C)";
|
||||
licenseText = "How vexingly quick daft zebras jump!";
|
||||
}
|
||||
return this;
|
||||
return licenseFileText(copyright(), licenseText());
|
||||
}
|
||||
|
||||
void run() {
|
||||
@ -343,20 +359,19 @@ public class LicenseTest {
|
||||
.addInitializer(cmd -> {
|
||||
// Create source license file.
|
||||
Files.write(srcLicenseFile, List.of(
|
||||
licenseText.split("\\R", -1)));
|
||||
licenseText().split("\\R", -1)));
|
||||
|
||||
cmd.setFakeRuntime();
|
||||
cmd.setArgumentValue("--name", String.format("%s%s",
|
||||
withSubstitution ? "CustomDebianCopyrightWithSubst" : "CustomDebianCopyright",
|
||||
cmd.name()));
|
||||
cmd.setArgumentValue("--name", String.format("%s%s", name(), cmd.name()));
|
||||
cmd.addArguments("--license-file", srcLicenseFile);
|
||||
cmd.addArguments("--copyright", copyright);
|
||||
cmd.addArguments("--resource-dir", RESOURCE_DIR);
|
||||
cmd.addArguments("--copyright", copyright());
|
||||
|
||||
var resourceDir = TKit.createTempDirectory("resources");
|
||||
|
||||
cmd.addArguments("--resource-dir", resourceDir);
|
||||
|
||||
// Create copyright template file in a resource dir.
|
||||
Files.createDirectories(RESOURCE_DIR);
|
||||
Files.write(RESOURCE_DIR.resolve("copyright"),
|
||||
licenseFileText());
|
||||
Files.write(resourceDir.resolve("copyright"), licenseFileText());
|
||||
})
|
||||
.addInstallVerifier(cmd -> {
|
||||
Path installedLicenseFile = linuxLicenseFile(cmd);
|
||||
@ -368,12 +383,6 @@ public class LicenseTest {
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
private boolean withSubstitution;
|
||||
private String copyright;
|
||||
private String licenseText;
|
||||
|
||||
private final Path RESOURCE_DIR = TKit.workDir().resolve("resources");
|
||||
}
|
||||
|
||||
private static final Path LICENSE_FILE = TKit.TEST_SRC_ROOT.resolve(
|
||||
|
||||
@ -21,13 +21,15 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
import java.util.List;
|
||||
import jdk.jpackage.internal.util.Slot;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
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.TKit;
|
||||
|
||||
@ -108,11 +110,7 @@ public class WinInstallerUiTest {
|
||||
}
|
||||
|
||||
if (withLicense) {
|
||||
test.addInitializer(cmd -> {
|
||||
cmd.addArguments("--license-file", TKit.createRelativePathCopy(
|
||||
TKit.TEST_SRC_ROOT.resolve(Path.of("resources",
|
||||
"license.txt"))));
|
||||
});
|
||||
setLicenseFile(test);
|
||||
}
|
||||
|
||||
test.run();
|
||||
@ -133,7 +131,21 @@ public class WinInstallerUiTest {
|
||||
cmd.setArgumentValue("--name", sb.toString());
|
||||
}
|
||||
|
||||
private static void setLicenseFile(PackageTest test) {
|
||||
var inputLicenseFile = Slot.<Path>createEmpty();
|
||||
|
||||
test.addRunOnceInitializer(() -> {
|
||||
var dir = TKit.createTempDirectory("license-dir");
|
||||
inputLicenseFile.set(dir.resolve(LICENSE_FILE.getFileName()));
|
||||
Files.copy(LICENSE_FILE, inputLicenseFile.get());
|
||||
}).addInitializer(cmd -> {
|
||||
cmd.setArgumentValue("--license-file", inputLicenseFile.get());
|
||||
});
|
||||
}
|
||||
|
||||
private final boolean withDirChooser;
|
||||
private final boolean withLicense;
|
||||
private final boolean withShortcutPrompt;
|
||||
|
||||
private static final Path LICENSE_FILE = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "license.txt"));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user