package picocli; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.ProvideSystemProperty; import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.contrib.java.lang.system.SystemErrRule; import org.junit.rules.TestRule; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; import picocli.CommandLine.ParseResult; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.ListResourceBundle; import java.util.Set; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.*; import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_AT_FILE_PARAMETER; import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST_HEADING; import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST_HEADING; import static picocli.TestUtil.setTraceLevel; public class AtFileTest { @Rule public final ProvideSystemProperty ansiOFF = new ProvideSystemProperty("picocli.ansi", "false"); // allows tests to set any kind of properties they like, without having to individually roll them back @Rule public final TestRule restoreSystemProperties = new RestoreSystemProperties(); @Rule public final SystemErrRule systemErrRule = new SystemErrRule().enableLog().muteForSuccessfulTests(); private File findFile(String resource) { URL url = this.getClass().getResource(resource); assertNotNull(resource, url); String str = url.toString(); return new File(str.substring(str.indexOf("file:") + 5)); } @Test public void testAtFileExpandedAbsolute() { class App { @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); App app = CommandLine.populateCommand(new App(), "@" + file.getAbsolutePath()); assertTrue(app.verbose); assertEquals(Arrays.asList("1111", "2222", ";3333"), app.files); } @Test public void testAtFileExpansionIgnoresSingleAtCharacter() { class App { @Parameters private List files; } App app = CommandLine.populateCommand(new App(), "@", "abc"); assertEquals(Arrays.asList("@", "abc"), app.files); } /** * * * @param source * @return Will have a newline at the end, no matter if the source file had one. * @throws IOException */ static String readFile(File source) throws IOException { String newLine = System.getProperty("line.separator", "\n"); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(source)); String line = null; StringBuilder sb = new StringBuilder(); while ((line = reader.readLine()) != null) { sb.append(line); sb.append(newLine); } sb.append(newLine); // enforce a newline at the end return sb.toString(); } finally { if (reader != null) { reader.close(); } } } static void writeFile(File target, String contents) throws IOException { BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(target)); writer.append(contents); } finally { if (writer != null) { writer.close(); } } } private static class AtFileTestingApp { @Option(names = "--simpleArg") private boolean simple; @Option(names = "--argWithSpaces") private String withSpaces; @Option(names = "--quotedArg") private String quoted; @Option(names = "--multiArg", arity = "1..*") private List strings; @Option(names = "--urlArg") private URL url; @Option(names = "--unescapedBackslashArg") private String unescaped; } @Test public void testUseSimplifiedAtFilesCanBeSetProgrammatically() { CommandLine.Model.ParserSpec parser = new CommandLine.Model.ParserSpec(); assertFalse(parser.useSimplifiedAtFiles()); parser.useSimplifiedAtFiles(true); assertTrue(parser.useSimplifiedAtFiles()); } @Test public void testUseSimplifiedAtFilesFromSystemProperty() { CommandLine.Model.ParserSpec parser = new CommandLine.Model.ParserSpec(); assertFalse(parser.useSimplifiedAtFiles()); System.setProperty("picocli.useSimplifiedAtFiles", "true"); assertTrue(parser.useSimplifiedAtFiles()); } @Test public void testUseSimplifiedAtFilesFromSystemPropertyCaseInsensitive() { CommandLine.Model.ParserSpec parser = new CommandLine.Model.ParserSpec(); assertFalse(parser.useSimplifiedAtFiles()); System.setProperty("picocli.useSimplifiedAtFiles", "TRUE"); assertTrue(parser.useSimplifiedAtFiles()); } @Test public void testUseSimplifiedAtFilesFromEmptySystemProperty() { CommandLine.Model.ParserSpec parser = new CommandLine.Model.ParserSpec(); assertFalse(parser.useSimplifiedAtFiles()); System.setProperty("picocli.useSimplifiedAtFiles", ""); assertTrue(parser.useSimplifiedAtFiles()); } @Test public void testUseSimplifiedAtFilesIsOverriddenBySystemProperty() { CommandLine.Model.ParserSpec parser = new CommandLine.Model.ParserSpec(); assertFalse(parser.useSimplifiedAtFiles()); parser.useSimplifiedAtFiles(true); System.setProperty("picocli.useSimplifiedAtFiles", "false"); assertFalse(parser.useSimplifiedAtFiles()); } @Test public void testAtFileSimplified() throws IOException { System.setProperty("picocli.useSimplifiedAtFiles", "true"); /* * first copy the old file and ensure it has a newline at the end. we do it this way to ensure that editors * can not mess up the file by removing the newline, therefore invalidating this test. */ File oldFile = findFile("/argfile-simplified.txt"); String contents = readFile(oldFile); // this is where we ensure the newline is there File newFile = File.createTempFile("picocli","atfile"); writeFile(newFile, contents); AtFileTestingApp app = CommandLine.populateCommand(new AtFileTestingApp(), "@" + newFile.getAbsolutePath()); assertTrue(app.simple); assertEquals("something with spaces", app.withSpaces); assertEquals("\"something else\"", app.quoted); assertEquals(Arrays.asList("something else", "yet something else"), app.strings); assertEquals("https://picocli.info/", app.url.toString()); assertEquals("C:\\Program Files\\picocli.txt", app.unescaped); } @Test public void testAtFileEndingWithoutNewline() throws IOException { System.setProperty("picocli.useSimplifiedAtFiles", "true"); /* * first copy the old file and ensure it has no newline at the end. we do it this way to ensure that editors * can not mess up the file by adding the newline, therefore invalidating this test. */ File oldFile = findFile("/argfile-simplified.txt"); String contents = readFile(oldFile).trim(); // this is where we remove the newline File newFile = File.createTempFile("picocli","atfile"); writeFile(newFile, contents); // then use the new file as the CLI at-file AtFileTestingApp app = CommandLine.populateCommand(new AtFileTestingApp(), "@" + newFile.getAbsolutePath()); assertTrue(app.simple); assertEquals("something with spaces", app.withSpaces); assertEquals("\"something else\"", app.quoted); assertEquals(Arrays.asList("something else", "yet something else"), app.strings); assertEquals("https://picocli.info/", app.url.toString()); assertEquals("C:\\Program Files\\picocli.txt", app.unescaped); } @Test public void testAtFileSimplifiedWithQuotesTrimmed() { System.setProperty("picocli.useSimplifiedAtFiles", ""); System.setProperty("picocli.trimQuotes", "true"); File file = findFile("/argfile-simplified-quoted.txt"); AtFileTestingApp app = CommandLine.populateCommand(new AtFileTestingApp(), "@" + file.getAbsolutePath()); assertEquals("something else", app.quoted); assertEquals("https://picocli.info/", app.url.toString()); assertEquals("C:\\Program Files\\picocli.txt", app.unescaped); } @Test public void testAtFileNotExpandedIfDisabled() { class App { @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); assertTrue(file.getAbsoluteFile().exists()); App app = new App(); new CommandLine(app).setExpandAtFiles(false).parseArgs("@" + file.getAbsolutePath()); assertFalse(app.verbose); assertEquals(Arrays.asList("@" + file.getAbsolutePath()), app.files); } @Test public void testAtFileExpansionEnabledByDefault() { @Command class App { } assertTrue(new CommandLine(new App()).isExpandAtFiles()); } @Test public void testAtFileExpandedRelative() { class App { @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); if (!file.getAbsolutePath().startsWith(System.getProperty("user.dir"))) { return; } String relative = file.getAbsolutePath().substring(System.getProperty("user.dir").length()); if (relative.startsWith(File.separator)) { relative = relative.substring(File.separator.length()); } App app = CommandLine.populateCommand(new App(), "@" + relative); assertTrue(app.verbose); assertEquals(Arrays.asList("1111", "2222", ";3333"), app.files); } @Test public void testAtFileExpandedMixedWithOtherParams() { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); App app = CommandLine.populateCommand(new App(), "-f", "fVal1", "@" + file.getAbsolutePath(), "-x", "-f", "fVal2"); assertTrue(app.verbose); assertEquals(Arrays.asList("1111", "2222", ";3333"), app.files); assertTrue(app.xxx); assertArrayEquals(new String[]{"fVal1", "fVal2"}, app.fff); } @Test public void testAtFileExpandedWithCommentsOff() { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); App app = new App(); CommandLine cmd = new CommandLine(app); cmd.setAtFileCommentChar(null); cmd.parseArgs("-f", "fVal1", "@" + file.getAbsolutePath(), "-x", "-f", "fVal2"); assertTrue(app.verbose); assertEquals(Arrays.asList("#", "first", "comment", "1111", "2222", "#another", "comment", ";3333"), app.files); assertTrue(app.xxx); assertArrayEquals(new String[]{"fVal1", "fVal2"}, app.fff); } @Test public void testAtFileExpandedWithNonDefaultCommentChar() { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); App app = new App(); CommandLine cmd = new CommandLine(app); cmd.setAtFileCommentChar(';'); cmd.parseArgs("-f", "fVal1", "@" + file.getAbsolutePath(), "-x", "-f", "fVal2"); assertTrue(app.verbose); assertEquals(Arrays.asList("#", "first", "comment", "1111", "2222", "#another", "comment"), app.files); assertTrue(app.xxx); assertArrayEquals(new String[]{"fVal1", "fVal2"}, app.fff); } @Test public void testAtFileWithMultipleValuesPerLine() { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile3-multipleValuesPerLine.txt"); App app = CommandLine.populateCommand(new App(), "-f", "fVal1", "@" + file.getAbsolutePath(), "-f", "fVal2"); assertTrue(app.verbose); assertEquals(Arrays.asList("1111", "2222", "3333"), app.files); assertTrue(app.xxx); assertArrayEquals(new String[]{"fVal1", "FFFF", "F2F2F2", "fVal2"}, app.fff); } @Test public void testAtFileWithQuotedValuesContainingWhitespace() { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } setTraceLevel("OFF"); File file = findFile("/argfile4-quotedValuesContainingWhitespace.txt"); App app = CommandLine.populateCommand(new App(), "-f", "fVal1", "@" + file.getAbsolutePath(), "-f", "fVal2"); assertTrue(app.verbose); assertEquals(Arrays.asList("11 11", "22\n22", "3333"), app.files); assertTrue(app.xxx); assertArrayEquals(new String[]{"fVal1", "F F F F", "F2 F2 F2", "fVal2"}, app.fff); } @Test public void testAtFileWithExcapedAtValues() { class App { @Parameters private List files; } setTraceLevel("INFO"); File file = findFile("/argfile5-escapedAtValues.txt"); App app = CommandLine.populateCommand(new App(), "aa", "@" + file.getAbsolutePath(), "bb"); assertEquals(Arrays.asList("aa", "@val1", "@argfile5-escapedAtValues.txt", "bb"), app.files); assertTrue(this.systemErrRule.getLog().contains("Not expanding @-escaped argument")); } @Test public void testEscapedAtFileIsUnescapedButNotExpanded() { class App { @Parameters private List files; } setTraceLevel("OFF"); File file = findFile("/argfile1.txt"); App app = CommandLine.populateCommand(new App(), "aa", "@@" + file.getAbsolutePath(), "bb"); assertEquals(Arrays.asList("aa", "@" + file.getAbsolutePath(), "bb"), app.files); } @Test public void testMultipleAtFilesExpandedMixedWithOtherParams() { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); File file2 = findFile("/argfile2.txt"); setTraceLevel("OFF"); App app = new App(); CommandLine commandLine = new CommandLine(app).setOverwrittenOptionsAllowed(true); commandLine.setToggleBooleanFlags(true); commandLine.parseArgs("-f", "fVal1", "@" + file.getAbsolutePath(), "-x", "@" + file2.getAbsolutePath(), "-f", "fVal2"); assertFalse("invoked twice", app.verbose); assertEquals(Arrays.asList("1111", "2222", ";3333", "1111", "2222", "3333"), app.files); assertFalse("invoked twice", app.xxx); assertArrayEquals(new String[]{"fVal1", "FFFF", "F2F2F2", "fVal2"}, app.fff); } @Test public void testNestedAtFile() throws IOException { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile-with-nested-at-file.txt"); File file2 = findFile("/argfile2.txt"); File nested = new File("argfile2.txt"); nested.delete(); assertFalse("does not exist yet", nested.exists()); copyFile(file2, nested); setTraceLevel("OFF"); App app = new App(); CommandLine commandLine = new CommandLine(app).setOverwrittenOptionsAllowed(true); commandLine.parseArgs("-f", "fVal1", "@" + file.getAbsolutePath(), "-f", "fVal2"); assertTrue("invoked in argFile2", app.verbose); assertEquals(Arrays.asList("abcdefg", "1111", "2222", "3333"), app.files); assertTrue("invoked in argFile2", app.xxx); assertArrayEquals(new String[]{"fVal1", "FFFF", "F2F2F2", "fVal2"}, app.fff); assertTrue("Deleted " + nested, nested.delete()); } @Test public void testRecursiveNestedAtFileIgnored() throws IOException { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile-with-recursive-at-file.txt"); File localCopy = new File("argfile-with-recursive-at-file.txt"); localCopy.delete(); assertFalse("does not exist yet", localCopy.exists()); copyFile(file, localCopy); setTraceLevel("INFO"); App app = new App(); CommandLine commandLine = new CommandLine(app).setOverwrittenOptionsAllowed(true); commandLine.parseArgs("-f", "fVal1", "@" + localCopy.getAbsolutePath(), "-f", "fVal2"); assertEquals(Arrays.asList("abc defg", "xyz"), app.files); assertArrayEquals(new String[]{"fVal1", "fVal2"}, app.fff); assertFalse("not invoked", app.verbose); assertFalse("not invoked", app.xxx); assertTrue("Deleted " + localCopy, localCopy.delete()); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] Parsing 5 command line args [-f, fVal1, @")); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] Expanding argument file @")); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] Expanding argument file @argfile-with-recursive-at-file.txt")); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] Already visited file ")); assertThat(systemErrRule.getLog(), containsString("; ignoring...")); } @Test public void testNestedAtFileNotFound() throws IOException { class App { @Option(names = "-x") private boolean xxx; @Option(names = "-f") private String[] fff; @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile-with-nested-at-file.txt"); File nested = new File("argfile2.txt"); nested.delete(); assertFalse(nested + " does not exist", nested.exists()); setTraceLevel("INFO"); App app = new App(); CommandLine commandLine = new CommandLine(app).setOverwrittenOptionsAllowed(true); commandLine.parseArgs("-f", "fVal1", "@" + file.getAbsolutePath(), "-f", "fVal2"); assertEquals(Arrays.asList("abcdefg", "@" + nested.getName()), app.files); assertArrayEquals(new String[]{"fVal1", "fVal2"}, app.fff); assertFalse("never invoked", app.verbose); assertFalse("never invoked", app.xxx); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] Parsing 5 command line args [-f, fVal1, @")); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] Expanding argument file @")); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] Expanding argument file @argfile2.txt")); assertThat(systemErrRule.getLog(), containsString("[picocli INFO] File argfile2.txt does not exist or cannot be read; treating argument literally")); } private void copyFile(File source, File destination) throws IOException { InputStream in = null; OutputStream out = null; try { in = new FileInputStream(source); out = new FileOutputStream(destination); byte[] buff = new byte[(int) source.length()]; int read = 0; while ((read = in.read(buff)) > 0) { out.write(buff, 0, read); } } finally { if (in != null) { try { in.close(); } catch (Exception ignored) {} } if (out != null) { try { out.close(); } catch (Exception ignored) {} } } } @Test public void testGetAtFileCommentChar_SharpByDefault() { @Command class A {} assertEquals((Character) '#', new CommandLine(new A()).getAtFileCommentChar()); } @Test public void testAtFileExpansionExceptionHandling() throws Exception { Class interpreterClass = Class.forName("picocli.CommandLine$Interpreter"); Method m = interpreterClass.getDeclaredMethod("expandValidArgumentFile", String.class, File.class, List.class, Set.class); m.setAccessible(true); class App { @Parameters private List files; } App app = new App(); CommandLine commandLine = new CommandLine(app); Field f = CommandLine.class.getDeclaredField("interpreter"); f.setAccessible(true); Object interpreter = f.get(commandLine); try { m.invoke(interpreter, "fileName", null, new ArrayList(), new HashSet()); fail("Expected exception"); } catch (InvocationTargetException ex) { CommandLine.InitializationException actual = (CommandLine.InitializationException) ex.getCause(); assertEquals("Could not read argument file @fileName", actual.getMessage()); assertTrue(String.valueOf(actual.getCause()), actual.getCause() instanceof NullPointerException); } } @Test public void testShowAtFileInUsageHelpBasic() { @Command(name = "myapp", mixinStandardHelpOptions = true, showAtFileInUsageHelp = true, description = "Example command.") class MyApp { @Parameters(description = "A file.") File file; } String actual = new CommandLine(new MyApp()).getUsageMessage(); String expected = String.format("" + "Usage: myapp [-hV] [@...] %n" + "Example command.%n" + " [@...] One or more argument files containing options.%n" + " A file.%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n"); assertEquals(expected, actual); } @Test public void testShowAtFileInUsageHelpSystemProperties() { @Command(name = "myapp", mixinStandardHelpOptions = true, showAtFileInUsageHelp = true, description = "Example command.") class MyApp { @Parameters(description = "A file.") File file; } System.setProperty("picocli.atfile.label", "my@@@@file"); System.setProperty("picocli.atfile.description", "@files rock!"); String actual = new CommandLine(new MyApp()).getUsageMessage(); String expected = String.format("" + "Usage: myapp [-hV] [my@@@@file...] %n" + "Example command.%n" + " [my@@@@file...] @files rock!%n" + " A file.%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n"); assertEquals(expected, actual); } public static class MyResourceBundle extends ListResourceBundle { protected Object[][] getContents() { return new Object[][] { {"picocli.atfile", "hi! I amd the @file description from a file"}, {"picocli.atfile.label", "BUNDLE@FILE"}, {"picocli.atfile.description", "BUNDLE @FILE DESCRIPTION"}, }; } } @Test public void testShowAtFileInUsageHelpResourceBundleWithSystemProps() { @Command(name = "A", mixinStandardHelpOptions = true, resourceBundle = "picocli.AtFileTest$MyResourceBundle", showAtFileInUsageHelp = true, description = "... description ...") class A { } System.setProperty("picocli.atfile.label", "my@@@@file"); System.setProperty("picocli.atfile.description", "@files rock!"); String actual = new CommandLine(new A()).setResourceBundle(new MyResourceBundle()).getUsageMessage(); String expected = String.format("" + "Usage: A [-hV] [my@@@@file...]%n" + "... description ...%n" + " [my@@@@file...] hi! I amd the @file description from a file%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n"); assertEquals(expected, actual); } @Test public void testShowAtFileInUsageHelpResourceBundleWithoutSystemProps() { @Command(name = "A", mixinStandardHelpOptions = true, resourceBundle = "picocli.AtFileTest$MyResourceBundle", showAtFileInUsageHelp = true, description = "... description ...") class A { } String actual = new CommandLine(new A()).getUsageMessage(); String expected = String.format("" + "Usage: A [-hV] [BUNDLE@FILE...]%n" + "... description ...%n" + " [BUNDLE@FILE...] hi! I amd the @file description from a file%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n"); assertEquals(expected, actual); } @Test public void testAtFileParameterListSectionLast() { @Command(name = "A", mixinStandardHelpOptions = true, showAtFileInUsageHelp = true, description = "... description ...") class A { } CommandLine commandLine = new CommandLine(new A()); List helpSectionKeys = commandLine.getHelpSectionKeys(); List copy = new ArrayList(helpSectionKeys); copy.remove(SECTION_KEY_AT_FILE_PARAMETER); copy.add(copy.indexOf(SECTION_KEY_COMMAND_LIST_HEADING), SECTION_KEY_AT_FILE_PARAMETER); commandLine.setHelpSectionKeys(copy); String actual = commandLine.getUsageMessage(); String expected = String.format("" + "Usage: A [-hV] [@...]%n" + "... description ...%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n" + " [@...] One or more argument files containing options.%n" + ""); assertEquals(expected, actual); } @Test public void testAtFileParameterListSectionBeforeOptions() { @Command(name = "A", mixinStandardHelpOptions = true, showAtFileInUsageHelp = true, description = "... description ...") class A { @Parameters(index = "0", arity = "1", description = "Some file.") File file; @Parameters(index = "1", description = "Some other file.") File anotherFile; } CommandLine commandLine = new CommandLine(new A()); List helpSectionKeys = commandLine.getHelpSectionKeys(); List copy = new ArrayList(helpSectionKeys); copy.remove(SECTION_KEY_AT_FILE_PARAMETER); copy.add(helpSectionKeys.indexOf(SECTION_KEY_OPTION_LIST_HEADING), SECTION_KEY_AT_FILE_PARAMETER); commandLine.setHelpSectionKeys(copy); String actual = commandLine.getUsageMessage(); String expected = String.format("" + "Usage: A [-hV] [@...] %n" + "... description ...%n" + " Some file.%n" + " Some other file.%n" + " [@...] One or more argument files containing options.%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n" + ""); assertEquals(expected, actual); } @Test public void testAtFileParameterListHeadingShownIfNoOtherPositionalParameters() { @Command(name = "A", mixinStandardHelpOptions = true, showAtFileInUsageHelp = true, parameterListHeading = "Parameters:%n", optionListHeading = "Options:%n", description = "... description ...") class A { } String actual = new CommandLine(new A()).getUsageMessage(); String expected = String.format("" + "Usage: A [-hV] [@...]%n" + "... description ...%n" + "Parameters:%n" + " [@...] One or more argument files containing options.%n" + "Options:%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n"); assertEquals(expected, actual); } @Test public void testAtFileExpandedArgsParsed() { class App { @Option(names = "-v") private boolean verbose; @Parameters private List files; } File file = findFile("/argfile1.txt"); if (!file.getAbsolutePath().startsWith(System.getProperty("user.dir"))) { return; } String relative = file.getAbsolutePath().substring(System.getProperty("user.dir").length()); if (relative.startsWith(File.separator)) { relative = relative.substring(File.separator.length()); } CommandLine commandLine = new CommandLine(new App()); ParseResult parseResult = commandLine.parseArgs(new String[] {"@" + relative}); assertEquals(Arrays.asList("1111", "-v", "2222", ";3333"), parseResult.expandedArgs()); } }