Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated unit tests #1709

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import picocli.CommandLine.Help.Ansi.IStyle;
import picocli.CommandLine.Help.Ansi.Style;
import picocli.CommandLine.Help.Ansi.Text;
Expand Down Expand Up @@ -11558,6 +11559,7 @@ public static class Messages {
private final String bundleBaseName;
private final ResourceBundle rb;
private final Set<String> keys;
private Messages parent;
public Messages(CommandSpec spec, String baseName) {
this(spec, baseName, createBundle(baseName));
}
Expand All @@ -11581,6 +11583,21 @@ private static String extractName(ResourceBundle rb) {
return (String) ResourceBundle.class.getDeclaredMethod("getBaseBundleName").invoke(rb);
} catch (Exception ignored) { return "?"; }
}
public Messages parent() {
CommandSpec parentSpec = this.spec.parent();
if ( parent==null || parent.spec!=parentSpec ) {
parent = null; // Refresh if parentSpec doesn't match
while ( parent==null && parentSpec!=null ) {
String parentResourceBundleBaseName = parentSpec.resourceBundleBaseName();
if ( parentResourceBundleBaseName!=null && !parentResourceBundleBaseName.equals(this.bundleBaseName)) {
parent = new Messages(parentSpec, parentResourceBundleBaseName);
} else {
parentSpec = parentSpec.parent();
}
}
}
return parent;
}
private static Set<String> keys(ResourceBundle rb) {
if (rb == null) { return Collections.emptySet(); }
Set<String> keys = new LinkedHashSet<String>();
Expand All @@ -11596,8 +11613,8 @@ private static Set<String> keys(ResourceBundle rb) {
public static Messages copy(CommandSpec spec, Messages original) {
return original == null ? null : new Messages(spec, original.bundleBaseName, original.rb);
}
/** Returns {@code true} if the specified {@code Messages} is {@code null} or has a {@code null ResourceBundle}. */
public static boolean empty(Messages messages) { return messages == null || messages.rb == null; }
/** Returns {@code true} if the specified {@code Messages} is {@code null}, has a {@code null ResourceBundle}, or has a {@code null parent Messages}. */
public static boolean empty(Messages messages) { return messages == null || messages.isEmpty(); }

/** Returns the String value found in the resource bundle for the specified key, or the specified default value if not found.
* @param key unqualified resource bundle key. This method will first try to find a value by qualifying the key with the command's fully qualified name,
Expand All @@ -11608,12 +11625,19 @@ public static Messages copy(CommandSpec spec, Messages original) {
public String getString(String key, String defaultValue) {
if (isEmpty()) { return defaultValue; }
String cmd = spec.qualifiedName(".");
if (keys.contains(cmd + "." + key)) { return rb.getString(cmd + "." + key); }
if (keys.contains(key)) { return rb.getString(key); }
return defaultValue;
String qualifiedKey = cmd + "." + key;
String result = getStringForExactKey(qualifiedKey);
if ( result==null ) { result = getStringForExactKey(key); }
return result!=null ? result : defaultValue;
}

private String getStringForExactKey(String key) {
if (keys.contains(key)) { return rb.getString(key); }
else if (parent()!=null) { return parent().getStringForExactKey(key); }
else { return null; }
}

boolean isEmpty() { return rb == null || keys.isEmpty(); }
boolean isEmpty() { return (rb == null || keys.isEmpty()) && (parent()==null || parent().isEmpty()); }

/** Returns the String array value found in the resource bundle for the specified key, or the specified default value if not found.
* Multi-line strings can be specified in the resource bundle with {@code key.0}, {@code key.1}, {@code key.2}, etc.
Expand All @@ -11625,10 +11649,15 @@ public String getString(String key, String defaultValue) {
public String[] getStringArray(String key, String[] defaultValues) {
if (isEmpty()) { return defaultValues; }
String cmd = spec.qualifiedName(".");
List<String> result = addAllWithPrefix(rb, cmd + "." + key, keys, new ArrayList<String>());
if (!result.isEmpty()) { return result.toArray(new String[0]); }
addAllWithPrefix(rb, key, keys, result);
return result.isEmpty() ? defaultValues : result.toArray(new String[0]);
String qualifiedKey = cmd + "." + key;
String[] result = getStringArrayForExactKey(qualifiedKey);
if ( result==null ) { result = getStringArrayForExactKey(key); }
return result!=null ? result : defaultValues;
}
private String[] getStringArrayForExactKey(String key) {
List<String> result = addAllWithPrefix(rb, key, keys, new ArrayList<String>());
if (!result.isEmpty()) { return result.toArray(new String[0]); }
return parent()==null ? null : parent().getStringArrayForExactKey(key);
}
private static List<String> addAllWithPrefix(ResourceBundle rb, String key, Set<String> keys, List<String> result) {
if (keys.contains(key)) { result.add(rb.getString(key)); }
Expand Down
6 changes: 5 additions & 1 deletion src/test/java/picocli/I18nCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Mixin;

@Command(name = "i18n-top",
resourceBundle = "picocli.SharedMessages",
subcommands = {CommandLine.HelpCommand.class, I18nSubcommand.class },
subcommands = {CommandLine.HelpCommand.class, I18nSubcommand.class, I18nSubcommand2.class },
mixinStandardHelpOptions = true,
description = {"top desc 1", "top desc 2", "top desc 3"},
descriptionHeading = "top desc heading%n",
Expand All @@ -33,6 +34,9 @@ public class I18nCommand {
@Parameters(index = "1", description = "top param1 description")
String param1;

@Mixin
private I18nMixinOption i18nMixinOption;

@Override
public String toString() {
return getClass().getName();
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/picocli/I18nMixinOption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package picocli;

import picocli.CommandLine.Option;

public class I18nMixinOption {
@Option(
names = {"--optionWithDescriptionFromParent"},
descriptionKey = "optionWithDescriptionFromParent"
)
public boolean parentOption1 = false;
}
27 changes: 27 additions & 0 deletions src/test/java/picocli/I18nSubcommand2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package picocli;

import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Mixin;

/**
* This command was created to test Issue #1706.
* We are testing here to ensure that the description for a shared Mixin option, where the description for that option
* is defined in a parent command's resource bundle, can still be displayed correctly by a subcommand that's using a
* resource bundle that doesn't have that Mixin's option description defined. Ideally, if the subcommand's resource
* bundle doesn't have the needed description key, picocli SHOULD check the resource bundle of the parent command to see
* if the description key exists.
*/
@Command(name = "sub2",
subcommands = CommandLine.HelpCommand.class,
mixinStandardHelpOptions = true,
resourceBundle = "picocli/ResourceBundlePropagationTest")
public class I18nSubcommand2 {
@Option(names = {"-a", "--aaa"}, descriptionKey = "additional.value")
String a;

// This is what we're really testing.
// The description key for this Mixin's options are defined in the parent command's resource bundle.
@Mixin
private I18nMixinOption i18nMixinOption;
}
68 changes: 65 additions & 3 deletions src/test/java/picocli/I18nTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import static org.junit.Assert.*;
import static picocli.TestUtil.stripAnsiTrace;
import static picocli.TestUtil.stripHashcodes;
import static picocli.TestUtil.stripFactoryInstanceHashcodes;

/**
* Tests internationalization (i18n) and localization (l12n)-related functionality.
Expand Down Expand Up @@ -184,7 +185,8 @@ public void testParentCommandWithSharedResourceBundle() {
"i18n-top header heading%n" +
"Shared header first line%n" +
"Shared header second line%n" +
"Usage: i18n-top [-hV] [-x=<x>] [-y=<y>] [-z=<z>] <param0> <param1> [COMMAND]%n" +
"Usage: i18n-top [-hV] [--optionWithDescriptionFromParent] [-x=<x>] [-y=<y>]%n" +
" [-z=<z>] <param0> <param1> [COMMAND]%n" +
"i18n-top description heading:%n" +
"Shared description 0%n" +
"Shared description 1%n" +
Expand All @@ -195,6 +197,9 @@ public void testParentCommandWithSharedResourceBundle() {
" shared param1 desc line 1%n" +
"top option list heading%n" +
" -h, --help Shared help option description.%n" +
" --optionWithDescriptionFromParent%n" +
" This description should be visible in both parent and sub%n" +
" commands%n" +
" -V, --version Print version information and exit.%n" +
" -x, --xxx=<x> X option description for i18n-top%n" +
" -y, --yyy=<y> top yyy description 1%n" +
Expand All @@ -203,6 +208,7 @@ public void testParentCommandWithSharedResourceBundle() {
"top command list heading%n" +
" help i18n-top HELP command header%n" +
" i18n-sub i18n-sub header (only one line)%n" +
" sub2 Shared header first line%n" +
"Shared Exit Codes Heading%n" +
"These exit codes are blah blah etc.%n" +
" 00 (From shared bundle) Normal termination%n" +
Expand All @@ -215,6 +221,7 @@ public void testParentCommandWithSharedResourceBundle() {
Locale original = Locale.getDefault();
Locale.setDefault(Locale.ENGLISH);
try {
String msg = new CommandLine(new I18nCommand()).getUsageMessage();
assertEquals(expected, new CommandLine(new I18nCommand()).getUsageMessage());
} finally {
Locale.setDefault(original);
Expand All @@ -227,7 +234,8 @@ public void testParentCommandWithSharedResourceBundle_ja() {
"i18n-top \u30d8\u30c3\u30c0\u30fc\u898b\u51fa\u3057%n" +
"\u5171\u901a\u30d8\u30c3\u30c0\u30fc\uff11\u884c\u76ee%n" +
"\u5171\u901a\u30d8\u30c3\u30c0\u30fc\uff12\u884c\u76ee%n" +
"Usage: i18n-top [-hV] [-x=<x>] [-y=<y>] [-z=<z>] <param0> <param1> [COMMAND]%n" +
"Usage: i18n-top [-hV] [--optionWithDescriptionFromParent] [-x=<x>] [-y=<y>]%n" +
" [-z=<z>] <param0> <param1> [COMMAND]%n" +
"i18n-top \u8aac\u660e\u898b\u51fa\u3057:%n" +
"\u5171\u901a\u8aac\u660e0%n" +
"\u5171\u901a\u8aac\u660e1%n" +
Expand All @@ -238,6 +246,9 @@ public void testParentCommandWithSharedResourceBundle_ja() {
" \u5171\u901a param1 \u8aac\u660e\uff12\u884c\u76ee%n" +
"top option list heading%n" +
" -h, --help \u5171\u901a help \u30aa\u30d7\u30b7\u30e7\u30f3\u8aac\u660e.%n" +
" --optionWithDescriptionFromParent%n" +
" \u3053\u306e\u8aac\u660e\u306f\u3001\u89aa\u30b3\u30de\u30f3\u30c9\u3068\u30b5\u30d6\u30b3\u30de\u30f3\u30c9\u306e\u4e21\u65b9\u306b\u8868\u793a\u3055\u308c\u308b\u5fc5\u8981\u304c\u3042%n" +
" \u308a\u307e\u3059%n" +
" -V, --version Print version information and exit.%n" +
" -x, --xxx=<x> i18n-top\u7528 X \u30aa\u30d7\u30b7\u30e7\u30f3\u8aac\u660e%n" +
" -y, --yyy=<y> top yyy description 1%n" +
Expand All @@ -246,6 +257,7 @@ public void testParentCommandWithSharedResourceBundle_ja() {
"top command list heading%n" +
" help i18n-top\u7528 HELP \u30b3\u30de\u30f3\u30c9\u30d8\u30c3\u30c0\u30fc%n" +
" i18n-sub i18n-sub \u30d8\u30c3\u30c0\u30fc\uff08\u4e00\u884c\u306e\u307f\uff09%n" +
" sub2 \u5171\u901a\u30d8\u30c3\u30c0\u30fc\uff11\u884c\u76ee%n" +
"\u7d42\u4e86\u30b9\u30c6\u30fc\u30bf\u30b9%n" +
"\u3053\u308c\u3089\u306e\u7d42\u4e86\u30b9\u30c6\u30fc\u30bf\u30b9\u306f\u7b49\u3005%n" +
" 00 (\u5171\u901a\u30d0\u30f3\u30c9\u30eb\u304b\u3089) \u6b63\u5e38\u7d42\u4e86%n" +
Expand Down Expand Up @@ -308,6 +320,45 @@ public void testSubcommandUsesParentBundle() {
}
}

@Test
public void testSubcommandOptionDescriptionUsesParentBundle() {
String expected = String.format("" +
"my name is sub2%n" +
"Shared header first line%n" +
"Shared header second line%n" +
"Usage: i18n-top sub2 [-hV] [--optionWithDescriptionFromParent] [-a=<a>]%n" +
" [COMMAND]%n" +
"my parent is i18n-top, Shared description 0%n" +
"Shared description 1%n" +
"Shared description 2%n" +
" -a, --aaa=<a> lorem ipsum%n" +
" -h, --help Shared help option description.%n" +
" --optionWithDescriptionFromParent%n" +
" This description should be visible in both parent and sub%n" +
" commands%n" +
" -V, --version Print version information and exit.%n" +
"Commands:%n" +
" help Shared header first line%n" +
"Shared Exit Codes Heading%n" +
"These exit codes are blah blah etc.%n" +
" 00 (From shared bundle) Normal termination%n" +
" 64 (From shared bundle)%n" +
" Multiline!%n" +
" Invalid input%n" +
" 70 (From shared bundle) Internal error%n" +
"Shared footer heading%n" +
"lorem ipsum%n");
Locale original = Locale.getDefault();
Locale.setDefault(Locale.ENGLISH);
try {
CommandLine top = new CommandLine(new I18nCommand());
CommandLine sub = top.getSubcommands().get("sub2");
assertEquals(expected, sub.getUsageMessage());
} finally {
Locale.setDefault(original);
}
}

@Test
public void testGetResourceBundle_nullIfNotSpecified() {
@Command class Noop {}
Expand Down Expand Up @@ -724,8 +775,19 @@ public void testTracingWithResourceBundle() {
"[picocli DEBUG] Adding subcommand 'i18n-sub' to 'i18n-top'%n" +
"[picocli DEBUG] Created Messages from resourceBundle[base=picocli.SharedMessages] for command 'i18n-sub' (command 'i18n-sub' (user object: class picocli.I18nSubcommand))%n" +
"[picocli DEBUG] Created Messages from resourceBundle[base=picocli.SharedMessages] for command 'help' (command 'help' (user object: class picocli.CommandLine$HelpCommand))%n" +
"[picocli DEBUG] Creating CommandSpec for class picocli.I18nSubcommand2 with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli DEBUG] Created Messages from resourceBundle[base=picocli/ResourceBundlePropagationTest] for command 'sub2' (command 'sub2' (user object: class picocli.I18nSubcommand2))%n" +
"[picocli DEBUG] Creating CommandSpec for class picocli.CommandLine$HelpCommand with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli DEBUG] Adding subcommand 'help' to 'sub2'%n" +
"[picocli DEBUG] Created Messages from resourceBundle[base=picocli/ResourceBundlePropagationTest] for command 'help' (command 'help' (user object: class picocli.CommandLine$HelpCommand))%n" +
"[picocli DEBUG] Getting a picocli.I18nSubcommand2 instance from factory picocli.CommandLine$DefaultFactory@%n" +
"[picocli DEBUG] Factory returned a picocli.I18nSubcommand2 instance (2bb7bd00)%n" +
"[picocli DEBUG] Creating CommandSpec for picocli.I18nMixinOption@ with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli DEBUG] Creating CommandSpec for picocli.CommandLine$AutoHelpMixin@ with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli DEBUG] Adding subcommand 'sub2' to 'i18n-top'%n" +
"[picocli DEBUG] Creating CommandSpec for picocli.I18nMixinOption@ with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli DEBUG] Creating CommandSpec for picocli.CommandLine$AutoHelpMixin@34c53688 with factory picocli.CommandLine$DefaultFactory%n" +
"");
assertEquals(stripAnsiTrace(stripHashcodes(expected)), stripAnsiTrace(stripHashcodes(err.toString())));
assertEquals(stripAnsiTrace(stripHashcodes(stripFactoryInstanceHashcodes(expected))), stripAnsiTrace(stripHashcodes(stripFactoryInstanceHashcodes(err.toString()))));
}
}
5 changes: 5 additions & 0 deletions src/test/java/picocli/TestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ public static String stripHashcodes(String original) {
return result.toString();
}

public static String stripFactoryInstanceHashcodes(String original) {
String replacePattern = "(Factory returned a \\w[_\\.a-zA-Z0-9]+ instance \\()[a-z0-9]{8}(\\))";
return original.replaceAll(replacePattern, "$1$2");
}

public static boolean equals(Object obj1, Object obj2) { return obj1 == null ? obj2 == null : obj1.equals(obj2); }
public static int hashCode(Object obj) {return obj == null ? 0 : obj.hashCode(); }
public static int hashCode(boolean bool) {return bool ? 1 : 0; }
Expand Down
2 changes: 2 additions & 0 deletions src/test/resources/picocli/SharedMessages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ i18n-top.help.helpCommand.command = Specialized description of COMMAND parameter

helpCommand.help = Shared description of --help option of built-in help subcommand
helpCommand.command = Shared description of COMMAND parameter of built-in help subcommand

optionWithDescriptionFromParent = This description should be visible in both parent and sub commands
1 change: 1 addition & 0 deletions src/test/resources/picocli/SharedMessages_ja.properties
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ i18n-top.help.helpCommand.command = i18-top \u30b3\u30de\u30f3\u30c9\u306eHELP\u
helpCommand.help=Picocli\u306e\u5185\u90e8HELP\u30b5\u30d6\u30b3\u30de\u30f3\u30c9\u306e--help\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u5171\u901a\u8aac\u660e
helpCommand.command=Picocli\u306e\u5185\u90e8HELP\u30b5\u30d6\u30b3\u30de\u30f3\u30c9\u306eCOMMAND\u30d1\u30e9\u30e1\u30fc\u30bf\u306e\u5171\u901a\u8aac\u660e

optionWithDescriptionFromParent = \u3053\u306e\u8aac\u660e\u306f\u3001\u89aa\u30b3\u30de\u30f3\u30c9\u3068\u30b5\u30d6\u30b3\u30de\u30f3\u30c9\u306e\u4e21\u65b9\u306b\u8868\u793a\u3055\u308c\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059