diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java index 000db1d8a10..f818e5d966e 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java @@ -1555,7 +1555,9 @@ public final class CommandOutputControl { } Optional bufferContents() { - return buf.map(ByteArrayOutputStream::toString); + return buf.map(in -> { + return in.toString(ps.charset()); + }); } static Builder build(Charset charset) { @@ -1600,7 +1602,7 @@ public final class CommandOutputControl { final PrintStream ps; if (buf.isPresent() && dumpStream != null) { - ps = new PrintStream(new TeeOutputStream(List.of(buf.get(), dumpStream)), true, dumpStream.charset()); + ps = new PrintStream(new TeeOutputStream(List.of(buf.get(), dumpStream)), true, charset); } else if (!discard) { ps = buf.map(in -> { return new PrintStream(in, false, charset); diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 639bd295e57..2dc2351be3c 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -27,7 +27,9 @@ import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; import java.io.IOException; import java.io.UncheckedIOException; +import java.lang.ref.Reference; import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; @@ -35,11 +37,13 @@ import java.time.Instant; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.Properties; +import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @@ -259,9 +263,19 @@ public class WindowsHelper { } public static WixType getWixTypeFromVerboseJPackageOutput(Executor.Result result) { + return getWixTypeFromVerboseJPackageOutput(Locale.getDefault(), result); + } - var summaryWixVersion = JPackageStringBundle.MAIN.cannedFormattedString( - "summary.property.win-wix-version").getValue() + ": "; + public static WixType getWixTypeFromVerboseJPackageOutput(Locale resultLocale, Executor.Result result) { + + final String summaryWixVersion; + if (resultLocale.equals(Locale.getDefault())) { + summaryWixVersion = JPackageStringBundle.MAIN.cannedFormattedString( + "summary.property.win-wix-version").getValue() + ": "; + } else { + summaryWixVersion = JPackageResourceBundleCache.INSTANCE.get(resultLocale).getString( + "summary.property.win-wix-version") + ": "; + } return result.stdout().stream().filter(str -> { return str.startsWith(summaryWixVersion); @@ -646,6 +660,26 @@ public class WindowsHelper { } + private static final class JPackageResourceBundleCache { + + ResourceBundle get(Locale locale) { + synchronized (items) { + var value = Optional.ofNullable(items.get(locale)).map(Reference::get).orElse(null); + if (value == null) { + value = ResourceBundle.getBundle("jdk.jpackage.internal.resources.WinResources", + locale, ModuleLayer.boot().findModule("jdk.jpackage").orElseThrow()); + items.put(locale, new WeakReference<>(value)); + } + return value; + } + } + + private final Map> items = new HashMap<>(); + + static final JPackageResourceBundleCache INSTANCE = new JPackageResourceBundleCache(); + } + + static final Set CRITICAL_RUNTIME_FILES = Set.of(Path.of( "bin\\server\\jvm.dll")); diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java index 4f67909bd47..a64d642f633 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java @@ -890,15 +890,18 @@ public class CommandOutputControlTest { for (boolean toolProvider : BOOLEAN_VALUES) { for (var redirectStderr : withAndWithout(OutputControl.REDIRECT_STDERR)) { - for (var charset : withAndWithout(OutputControl.CHARSET_UTF16LE)) { - var stdoutSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.US_ASCII, OutputStreams.STDOUT); - var stderrSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.UTF_32LE, OutputStreams.STDERR); - var outputControl = new HashSet(); - redirectStderr.ifPresent(outputControl::add); - charset.ifPresent(outputControl::add); - outputControl.add(stdoutSink); - outputControl.add(stderrSink); - testCases.add(new CharsetTestSpec(toolProvider, new CommandOutputControlSpec(outputControl))); + for (var saveOutput : withAndWithout(OutputControl.SAVE_ALL)) { + for (var charset : withAndWithout(OutputControl.CHARSET_UTF16LE)) { + var stdoutSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.US_ASCII, OutputStreams.STDOUT); + var stderrSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.UTF_32LE, OutputStreams.STDERR); + var outputControl = new HashSet(); + redirectStderr.ifPresent(outputControl::add); + saveOutput.ifPresent(outputControl::add); + charset.ifPresent(outputControl::add); + outputControl.add(stdoutSink); + outputControl.add(stderrSink); + testCases.add(new CharsetTestSpec(toolProvider, new CommandOutputControlSpec(outputControl))); + } } } } @@ -1731,37 +1734,46 @@ public class CommandOutputControlTest { record CharsetTestSpec(boolean toolProvider, CommandOutputControlSpec cocSpec) { - void test() throws IOException, InterruptedException { - if (cocSpec.outputControl().stream().noneMatch(DumpOutputSink.class::isInstance)) { + CharsetTestSpec(boolean toolProvider, CommandOutputControlSpec cocSpec) { + this.toolProvider = toolProvider; + this.cocSpec = Objects.requireNonNull(cocSpec); + + // Sinks must be specified for stdout and stderr streams. + if (cocSpec.outputControl().stream().filter(DumpOutputSink.class::isInstance).count() != 2) { throw new IllegalArgumentException(); } + } - final var expectedString = "veni-vidi-vici"; + void test() throws IOException, InterruptedException { + + final var writeToStdout = "veni-vidi-vici"; + final var writeToStderr = "iciv-idiv-inev"; var coc = cocSpec.create().dumpOutput(true); CommandOutputControl.Executable exec; if (toolProvider) { - var tp = Command.createToolProvider(Stream.of(expectedString).mapMulti((str, sink) -> { - sink.accept(CommandAction.echoStdout(str)); - sink.accept(CommandAction.echoStderr(str)); - }).toList()); + var tp = Command.createToolProvider(List.of( + CommandAction.echoStdout(writeToStdout), + CommandAction.echoStderr(writeToStderr) + )); exec = coc.createExecutable(tp); } else { - var cmdline = Command.createShellCommandLine(Stream.of(expectedString).map(str -> { + Function conv = str -> { return (str + System.lineSeparator()).getBytes(coc.charset()); - }).mapMulti((bytes, sink) -> { - sink.accept(CommandAction.writeStdout(bytes)); - sink.accept(CommandAction.writeStderr(bytes)); - }).toList()); + }; + var cmdline = Command.createShellCommandLine(List.of( + CommandAction.writeStdout(conv.apply(writeToStdout)), + CommandAction.writeStderr(conv.apply(writeToStderr)) + )); exec = coc.createExecutable(new ProcessBuilder(cmdline)); } - exec.execute(); + final var execResult = exec.execute(); for (var outputContolMutator : cocSpec.outputControl()) { if (outputContolMutator instanceof DumpOutputSink sink) { - var actual = sink.lines(); + var actual = sink.lines(coc); List expected; if (cocSpec.redirectStderr()) { switch (sink.streams()) { @@ -1769,13 +1781,22 @@ public class CommandOutputControlTest { expected = List.of(); } default -> { - expected = List.of(expectedString, expectedString); + expected = List.of(writeToStdout, writeToStderr); } } } else { - expected = List.of(expectedString); + switch (sink.streams()) { + case STDERR -> { + expected = List.of(writeToStderr); + } + default -> { + expected = List.of(writeToStdout); + } + } } assertEquals(expected, actual); + } else if (outputContolMutator == OutputControl.SAVE_ALL) { + assertEquals(List.of(writeToStdout, writeToStderr), execResult.content()); } } @@ -1792,8 +1813,8 @@ public class CommandOutputControlTest { this(charset, new ByteArrayOutputStream(), streams); } - List lines() { - var str = buffer.toString(charset); + List lines(CommandOutputControl coc) { + var str = buffer.toString((coc.isSaveOutput() || coc.isSaveFirstLineOfOutput()) ? coc.charset() : charset); return new BufferedReader(new StringReader(str)).lines().toList(); } diff --git a/test/jdk/tools/jpackage/windows/WinL10nTest.java b/test/jdk/tools/jpackage/windows/WinL10nTest.java index d63057adc44..6075b30a3b2 100644 --- a/test/jdk/tools/jpackage/windows/WinL10nTest.java +++ b/test/jdk/tools/jpackage/windows/WinL10nTest.java @@ -25,8 +25,11 @@ import static jdk.jpackage.test.WindowsHelper.getWixTypeFromVerboseJPackageOutpu import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -55,64 +58,103 @@ import jdk.jpackage.test.WindowsHelper.WixType; * --jpt-run=WinL10nTest */ -public class WinL10nTest { +public record WinL10nTest( + Collection wxlFileInitializers, + Collection expectedCultures, + Optional locale, + boolean enableWixUIExtension) { - public WinL10nTest(WixFileInitializer wxlFileInitializers[], - String[] expectedCultures, String expectedErrorMessage, - String userLanguage, String userCountry, - boolean enableWixUIExtension) { - this.wxlFileInitializers = wxlFileInitializers; - this.expectedCultures = expectedCultures; - this.expectedErrorMessage = expectedErrorMessage; - this.userLanguage = userLanguage; - this.userCountry = userCountry; - this.enableWixUIExtension = enableWixUIExtension; + public WinL10nTest { + Objects.requireNonNull(wxlFileInitializers); + Objects.requireNonNull(expectedCultures); + Objects.requireNonNull(locale); + } + + public WinL10nTest(Collection expectedCultures, Locale locale, boolean enableWixUIExtension) { + this(List.of(), expectedCultures, Optional.of(locale), enableWixUIExtension); + } + + public WinL10nTest(Collection wxlFileInitializers, Collection expectedCultures) { + this(wxlFileInitializers, expectedCultures, Optional.empty(), false); + } + + public WinL10nTest(WinL10nTest other) { + this(other.wxlFileInitializers, other.expectedCultures, other.locale, other.enableWixUIExtension); + } + + @Override + public String toString() { + var tokens = new ArrayList(); + if (!wxlFileInitializers.isEmpty()) { + tokens.add(String.format("wxlFileInitializers=%s", wxlFileInitializers)); + } + if (!expectedCultures.isEmpty()) { + tokens.add(String.format("expectedCultures=%s", expectedCultures)); + } + locale.ifPresent(l -> { + tokens.add(String.format("locale=%s", l)); + }); + if (enableWixUIExtension) { + tokens.add("enableWixUIExtension=true"); + } + return String.join(", ", tokens); } @Parameters public static List data() { - return List.of(new Object[][]{ - {null, new String[] {"en-us"}, null, null, null, false}, - {null, new String[] {"en-us"}, null, "en", "US", false}, - {null, new String[] {"en-us"}, null, "en", "US", true}, - {null, new String[] {"de-de"}, null, "de", "DE", false}, - {null, new String[] {"de-de"}, null, "de", "DE", true}, - {null, new String[] {"ja-jp"}, null, "ja", "JP", false}, - {null, new String[] {"ja-jp"}, null, "ja", "JP", true}, - {null, new String[] {"zh-cn"}, null, "zh", "CN", false}, - {null, new String[] {"zh-cn"}, null, "zh", "CN", true}, - {new WixFileInitializer[] { + + List testCases = new ArrayList<>(); + + testCases.add(new WinL10nTest(List.of(), List.of("en-us"), Optional.empty(), false)); + for (var enableWixUIExtension : List.of(true, false)) { + testCases.add(new WinL10nTest(List.of("en-us"), Locale.of("en", "US"), enableWixUIExtension)); + testCases.add(new WinL10nTest(List.of("de-de"), Locale.of("de", "DE"), enableWixUIExtension)); + testCases.add(new WinL10nTest(List.of("ja-jp"), Locale.of("ja", "JP"), enableWixUIExtension)); + testCases.add(new WinL10nTest(List.of("zh-cn"), Locale.of("zh", "CN"), enableWixUIExtension)); + } + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "en-us") - }, new String[] {"en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "fr") - }, new String[] {"fr", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "fr"), WixFileInitializer.create("b.wxl", "fr") - }, new String[] {"fr", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "it"), WixFileInitializer.create("b.wxl", "fr") - }, new String[] {"it", "fr", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("it", "fr", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("c.wxl", "it"), WixFileInitializer.create("b.wxl", "fr") - }, new String[] {"fr", "it", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "it", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "fr"), WixFileInitializer.create("b.wxl", "it"), WixFileInitializer.create("c.wxl", "fr"), WixFileInitializer.create("d.wxl", "it") - }, new String[] {"fr", "it", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "it", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("c.wxl", "it"), WixFileInitializer.createMalformed("b.wxl") - }, null, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of())); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("MsiInstallerStrings_de.wxl", "de") - }, new String[] {"en-us"}, null, null, null, false} - }); + ), List.of("en-us"))); + + return testCases.stream().map(testCase -> { + return new Object[] { testCase }; + }).toList(); } private record OutputAnalizer(Executor.Result result, WixType wixType, Optional wixBuildCommandLine) { @@ -123,8 +165,8 @@ public class WinL10nTest { Objects.requireNonNull(wixBuildCommandLine); } - OutputAnalizer(Executor.Result result) { - this(result, getWixTypeFromVerboseJPackageOutput(result)); + OutputAnalizer(Locale locale, Executor.Result result) { + this(result, getWixTypeFromVerboseJPackageOutput(locale, result)); } OutputAnalizer(Executor.Result result, WixType wixType) { @@ -196,13 +238,7 @@ public class WinL10nTest { public void test() throws IOException { final Path tempRoot = TKit.createTempDirectory("tmp"); - final boolean allWxlFilesValid; - if (wxlFileInitializers != null) { - allWxlFilesValid = Stream.of(wxlFileInitializers).allMatch( - WixFileInitializer::isValid); - } else { - allWxlFilesValid = true; - } + final boolean allWxlFilesValid = wxlFileInitializers.stream().allMatch(WixFileInitializer::isValid); PackageTest test = new PackageTest() .forTypes(PackageType.WINDOWS) @@ -225,19 +261,18 @@ public class WinL10nTest { boolean withJavaOptions = false; // Set JVM default locale that is used to select primary l10n file. - if (userLanguage != null) { - withJavaOptions = true; - cmd.addArguments("-J-Duser.language=" + userLanguage); - } - if (userCountry != null) { - withJavaOptions = true; - cmd.addArguments("-J-Duser.country=" + userCountry); - } - - if (withJavaOptions) { + locale.ifPresent(l -> { + cmd.addArguments("-J-Duser.language=" + l.getLanguage()); + cmd.addArguments("-J-Duser.country=" + l.getCountry()); + // Force UTF8 encoding of the output of jpackage command. + // This is the default encoding of the output for the command executor. + // This is needed to properly handle JP and CN l10n-s. + cmd.addArguments("-J-Dstdout.encoding=UTF-8"); + cmd.addArguments("-J-Dstderr.encoding=UTF-8"); + cmd.addArguments("-J-Dfile.encoding=UTF-8"); // Use jpackage as a command to allow "-J" options come through cmd.useToolProvider(false); - } + }); // Cultures handling is affected by the WiX extensions used. // By default only WixUtilExtension is used, this flag @@ -252,14 +287,10 @@ public class WinL10nTest { }) .addBundleVerifier((cmd, result) -> { - var outputAnalizer = new OutputAnalizer(result); + var outputAnalizer = new OutputAnalizer(locale.orElseGet(Locale::getDefault), result); - if (expectedCultures != null) { - outputAnalizer.verifyCulturesInCmdline(expectedCultures); - } - - if (expectedErrorMessage != null) { - TKit.assertTextStream(expectedErrorMessage).apply(result.stderr()); + if (!expectedCultures.isEmpty()) { + outputAnalizer.verifyCulturesInCmdline(expectedCultures.toArray(String[]::new)); } if (wxlFileInitializers != null) { @@ -277,7 +308,7 @@ public class WinL10nTest { v.apply(List.of(outputAnalizer.getWixBuildCommandLine())); } } else { - Stream.of(wxlFileInitializers) + wxlFileInitializers.stream() .filter(Predicate.not(WixFileInitializer::isValid)) .forEach(v -> v.createCmdOutputVerifier( wixSrcDir).apply(result.getOutput())); @@ -287,9 +318,9 @@ public class WinL10nTest { } }); - if (wxlFileInitializers != null) { + if (!wxlFileInitializers.isEmpty()) { test.addInitializer(cmd -> { - resourceDir = TKit.createTempDirectory("resources"); + var resourceDir = TKit.createTempDirectory("resources"); cmd.addArguments("--resource-dir", resourceDir); @@ -299,21 +330,13 @@ public class WinL10nTest { }); } - if (expectedErrorMessage != null || !allWxlFilesValid) { + if (!allWxlFilesValid) { test.setExpectedExitCode(1); } test.run(); } - private final WixFileInitializer[] wxlFileInitializers; - private final String[] expectedCultures; - private final String expectedErrorMessage; - private final String userLanguage; - private final String userCountry; - private final boolean enableWixUIExtension; - private Path resourceDir; - private static class WixFileInitializer { static WixFileInitializer create(String name, String culture) { return new WixFileInitializer(name, culture);