8386609: WinL10nTest.java does not respect locale for WixType

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2026-06-17 14:12:21 +00:00
parent b18c1452e9
commit d0cb70b930
4 changed files with 188 additions and 108 deletions

View File

@ -1555,7 +1555,9 @@ public final class CommandOutputControl {
}
Optional<String> 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);

View File

@ -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<Locale, WeakReference<ResourceBundle>> items = new HashMap<>();
static final JPackageResourceBundleCache INSTANCE = new JPackageResourceBundleCache();
}
static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
"bin\\server\\jvm.dll"));

View File

@ -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<CommandOutputControlMutator>();
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<CommandOutputControlMutator>();
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).<CommandAction>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<String, byte[]> conv = str -> {
return (str + System.lineSeparator()).getBytes(coc.charset());
}).<CommandAction>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<String> 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<String> lines() {
var str = buffer.toString(charset);
List<String> lines(CommandOutputControl coc) {
var str = buffer.toString((coc.isSaveOutput() || coc.isSaveFirstLineOfOutput()) ? coc.charset() : charset);
return new BufferedReader(new StringReader(str)).lines().toList();
}

View File

@ -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<WixFileInitializer> wxlFileInitializers,
Collection<String> expectedCultures,
Optional<Locale> 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<String> expectedCultures, Locale locale, boolean enableWixUIExtension) {
this(List.of(), expectedCultures, Optional.of(locale), enableWixUIExtension);
}
public WinL10nTest(Collection<WixFileInitializer> wxlFileInitializers, Collection<String> 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<String>();
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<Object[]> 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<WinL10nTest> 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<String> 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);