This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-launcher.git
The following commit(s) were added to refs/heads/master by this push:
new 3cefbd3 SLING-10186 - Enhance cli parsing on sling-feature-launcher
3cefbd3 is described below
commit 3cefbd39ed17a9c880a07e2b4d7d5a1ce1526e35
Author: Stefan Bischof <[email protected]>
AuthorDate: Wed Mar 10 09:37:21 2021 +0100
SLING-10186 - Enhance cli parsing on sling-feature-launcher
- extract opts as Constants
- add longOpt to Options
- fix plural in description
- use Builder-pattern to build Options
- set numberOfArgs , valueSeparator(',')
- accept (but ignore) empty options without ParsingExceptions
using .optionalArg(true) and Optionals
- option to set loglevel on verbose Option
- update common-cli version
- add cli parse tests
Signed-off-by: Stefan Bischof <[email protected]>
---
pom.xml | 2 +-
.../apache/sling/feature/launcher/impl/Main.java | 316 ++++++++++++++-------
.../sling/feature/launcher/impl/MainTest.java | 151 +++++++++-
3 files changed, 361 insertions(+), 108 deletions(-)
diff --git a/pom.xml b/pom.xml
index f8ed699..e16c2a0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -143,7 +143,7 @@
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
- <version>1.3.1</version>
+ <version>1.4</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
index 1d182a4..3d5fcc9 100644
--- a/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
@@ -18,13 +18,20 @@ package org.apache.sling.feature.launcher.impl;
import java.io.File;
import java.util.AbstractMap;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
-import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
@@ -34,30 +41,57 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * This is the launcher main class.
- * It parses command line parameters and prepares the launcher.
+ * This is the launcher main class. It parses command line parameters and
+ * prepares the launcher.
*/
public class Main {
+ public static final String OPT_OSGI_FRAMEWORK_ARTIFACT = "fa";
+
+ public static final String OPT_FELIX_FRAMEWORK_VERSION = "fv";
+
+ public static final String OPT_EXTENSION_CONFIGURATION = "ec";
+
+ public static final String OPT_HOME_DIR = "p";
+
+ public static final String OPT_CACHE_DIR = "c";
+
+ public static final String OPT_VERBOSE = "v";
+
+ public static final String OPT_VARIABLE_VALUES = "V";
+
+ public static final String OPT_FRAMEWORK_PROPERTIES = "D";
+
+ public static final String OPT_FEATURE_FILES = "f";
+
+ public static final String OPT_REPOSITORY_URLS = "u";
+
+ public static final String OPT_CONFIG_CLASH = "CC";
+
+ public static final String OPT_ARTICACT_CLASH = "C";
+
private static Logger LOGGER;
private static Logger LOG() {
- if ( LOGGER == null ) {
+
+ if (LOGGER == null) {
LOGGER = LoggerFactory.getLogger("launcher");
}
return LOGGER;
}
/** Split a string into key and value */
- static String[] split(final String val) {
- final int pos = val.indexOf('=');
- if ( pos == -1 ) {
- return new String[] {val, "true"};
+ static String[] splitKeyVal(final String keyVal) {
+
+ final int pos = keyVal.indexOf('=');
+ if (pos == -1) {
+ return new String[] { keyVal, "true" };
}
- return new String[] {val.substring(0, pos), val.substring(pos + 1)};
+ return new String[] { keyVal.substring(0, pos), keyVal.substring(pos +
1) };
}
static Map.Entry<String, Map<String, String>> splitMap(final String val) {
+
String[] split1 = val.split(":");
if (split1.length < 2) {
@@ -66,116 +100,199 @@ public class Main {
Map<String, String> m = new HashMap<>();
for (String kv : split1[1].split(",")) {
- String[] keyval = split(kv);
+ String[] keyval = splitKeyVal(kv);
m.put(keyval[0], keyval[1]);
}
return new AbstractMap.SimpleEntry<>(split1[0], m);
}
+ private static Optional<String> extractValueFromOption(CommandLine cl,
String opt) {
+
+ return extractValueFromOption(cl, opt, null);
+ }
+
+ private static Optional<String> extractValueFromOption(CommandLine cl,
String opt,
+ String defaultVaue) {
+
+ return Optional.ofNullable(cl.getOptionValue(opt, defaultVaue));
+ }
+
+ private static Optional<List<String>> extractValuesFromOption(CommandLine
cl, String opt) {
+
+ String[] values = cl.getOptionValues(opt);
+ if (Objects.isNull(values)) {
+ return Optional.empty();
+ }
+ return Optional.of(Stream.of(values).collect(Collectors.toList()));
+ }
+
/**
* Parse the command line parameters and update a configuration object.
+ *
* @param args Command line parameters
* @return Configuration object.
*/
- private static void parseArgs(final LauncherConfig config, final String[]
args) {
- final Options options = new Options();
-
- final Option artifactClashOverride = new Option("C", true, "Set
artifact clash override");
- final Option configClashOverride = new Option("CC", true, "Set config
clash override");
- final Option repoOption = new Option("u", true, "Set repository url");
- final Option featureOption = new Option("f", true, "Set feature
files");
- final Option fwkProperties = new Option("D", true, "Set framework
properties");
- final Option varValue = new Option("V", true, "Set variable value");
- final Option debugOption = new Option("v", false, "Verbose");
- debugOption.setArgs(0);
- final Option cacheOption = new Option("c", true, "Set cache dir");
- final Option homeOption = new Option("p", true, "Set home dir");
-
- final Option extensionConfiguration = new Option("ec", true, "Provide
extension configuration, format: extensionName:key1=val1,key2=val2");
- final Option frameworkVersionOption = new Option("fv", true, "Set
Apache Felix framework version (default
".concat(Bootstrap.FELIX_FRAMEWORK_VERSION) + ")");
- final Option frameworkArtifactOption = new Option("fa", true, "Set
OSGi framework artifact (overrides Apache Felix framework version)");
-
- options.addOption(artifactClashOverride);
- options.addOption(configClashOverride);
- options.addOption(repoOption);
- options.addOption(featureOption);
- options.addOption(fwkProperties);
- options.addOption(varValue);
- options.addOption(debugOption);
- options.addOption(cacheOption);
- options.addOption(homeOption);
- options.addOption(extensionConfiguration);
- options.addOption(frameworkVersionOption);
- options.addOption(frameworkArtifactOption);
-
- final CommandLineParser clp = new BasicParser();
+ protected static void parseArgs(final LauncherConfig config, final
String[] args) {
+
+ final Option artifactClashOverride = Option.builder(OPT_ARTICACT_CLASH)
+ .longOpt("artifact-clash")
+ .desc("Set artifact clash override")
+ .optionalArg(true)
+ .numberOfArgs(Option.UNLIMITED_VALUES)
+ .build();
+
+ final Option configClashOverride = Option.builder(OPT_CONFIG_CLASH)
+ .longOpt("config-clash")
+ .desc("Set config clash override")
+ .optionalArg(true)
+ .numberOfArgs(Option.UNLIMITED_VALUES)
+ .build();
+
+ final Option repoOption = Option.builder(OPT_REPOSITORY_URLS)
+ .longOpt("repository-urls")
+ .desc("Set repository urls")
+ .optionalArg(true)
+ .valueSeparator(',')
+ .numberOfArgs(Option.UNLIMITED_VALUES)
+ .build();
+
+ final Option featureOption = Option.builder(OPT_FEATURE_FILES)
+ .longOpt("feature-files")
+ .desc("Set feature files")
+ .optionalArg(true)
+ .valueSeparator(',')
+ .numberOfArgs(Option.UNLIMITED_VALUES)
+ .build();
+
+ final Option fwkProperties = Option.builder(OPT_FRAMEWORK_PROPERTIES)
+ .longOpt("framework-properties")
+ .desc("Set framework properties")
+ .optionalArg(true)
+ .numberOfArgs(Option.UNLIMITED_VALUES)
+ .build();
+
+ final Option varValue = Option.builder(OPT_VARIABLE_VALUES)
+ .longOpt("variable-values")
+ .desc("Set variable values")
+ .optionalArg(true)
+ .numberOfArgs(Option.UNLIMITED_VALUES)
+ .build();
+
+ final Option debugOption = Option.builder(OPT_VERBOSE)
+ .longOpt("verbose")
+ .desc("Verbose")
+ .optionalArg(true)
+ .numberOfArgs(1)
+ .build();
+
+ final Option cacheOption = Option.builder(OPT_CACHE_DIR)
+ .longOpt("cache_dir")
+ .desc("Set cache dir")
+ .optionalArg(true)
+ .numberOfArgs(1)
+ .build();
+
+ final Option homeOption = Option.builder(OPT_HOME_DIR)
+ .longOpt("home_dir")
+ .desc("Set home dir")
+ .optionalArg(true)
+ .numberOfArgs(1)
+ .build();
+
+ final Option extensionConfiguration =
Option.builder(OPT_EXTENSION_CONFIGURATION)
+ .longOpt("extension_configuration")
+ .desc("Provide extension configuration, format:
extensionName:key1=val1,key2=val2")
+ .optionalArg(true)
+ .numberOfArgs(Option.UNLIMITED_VALUES)
+ .build();
+
+ final Option frameworkVersionOption =
Option.builder(OPT_FELIX_FRAMEWORK_VERSION)
+ .longOpt("felix-framework-version")
+ .desc("Set Apache Felix framework version (default "
+ .concat(Bootstrap.FELIX_FRAMEWORK_VERSION) + ")")
+ .optionalArg(true)
+ .numberOfArgs(1)
+ .build();
+
+ final Option frameworkArtifactOption =
Option.builder(OPT_OSGI_FRAMEWORK_ARTIFACT)
+ .longOpt("osgi-framework-artifact")
+ .desc("Set OSGi framework artifact (overrides Apache Felix
framework version)")
+ .optionalArg(true)
+ .numberOfArgs(1)
+ .build();
+
+ final Options options = new Options().addOption(artifactClashOverride)
+ .addOption(configClashOverride)
+ .addOption(repoOption)
+ .addOption(featureOption)
+ .addOption(fwkProperties)
+ .addOption(varValue)
+ .addOption(debugOption)
+ .addOption(cacheOption)
+ .addOption(homeOption)
+ .addOption(extensionConfiguration)
+ .addOption(frameworkVersionOption)
+ .addOption(frameworkArtifactOption);
+
+ final CommandLineParser clp = new DefaultParser();
try {
final CommandLine cl = clp.parse(options, args);
- if ( cl.hasOption(repoOption.getOpt()) ) {
- final String value = cl.getOptionValue(repoOption.getOpt());
- config.setRepositoryUrls(value.split(","));
- }
- if ( cl.hasOption(artifactClashOverride.getOpt()) ) {
- for(final String override :
cl.getOptionValues(artifactClashOverride.getOpt())) {
-
config.getArtifactClashOverrides().add(ArtifactId.parse(override));
- }
- }
- if ( cl.hasOption(configClashOverride.getOpt()) ) {
- for(final String override :
cl.getOptionValues(configClashOverride.getOpt())) {
- final String[] keyVal = split(override);
- config.getConfigClashOverrides().put(keyVal[0], keyVal[1]);
- }
- }
- if ( cl.hasOption(fwkProperties.getOpt()) ) {
- for(final String value :
cl.getOptionValues(fwkProperties.getOpt())) {
- final String[] keyVal = split(value);
+ extractValuesFromOption(cl, OPT_REPOSITORY_URLS).ifPresent(
+ values ->
config.setRepositoryUrls(values.stream().toArray(String[]::new)));
-
config.getInstallation().getFrameworkProperties().put(keyVal[0], keyVal[1]);
- }
- }
- if ( cl.hasOption(varValue.getOpt()) ) {
- for(final String optVal :
cl.getOptionValues(varValue.getOpt())) {
- final String[] keyVal = split(optVal);
+ extractValuesFromOption(cl, OPT_ARTICACT_CLASH).ifPresent(values
-> values
+ .forEach(v ->
config.getArtifactClashOverrides().add(ArtifactId.parse(v))));
- config.getVariables().put(keyVal[0], keyVal[1]);
- }
- }
- if ( cl.hasOption(debugOption.getOpt()) ) {
- System.setProperty("org.slf4j.simpleLogger.defaultLogLevel",
"debug");
+ Properties cfgCProps = cl.getOptionProperties(OPT_CONFIG_CLASH);
+ for (final String name : cfgCProps.stringPropertyNames()) {
+ config.getConfigClashOverrides().put(name,
cfgCProps.getProperty(name));
}
- if ( cl.hasOption(featureOption.getOpt()) ) {
- for(final String optVal :
cl.getOptionValues(featureOption.getOpt())) {
- config.addFeatureFiles(optVal.split(","));
- }
+ Properties fwProps =
cl.getOptionProperties(OPT_FRAMEWORK_PROPERTIES);
+ for (final String name : fwProps.stringPropertyNames()) {
+ config.getInstallation()
+ .getFrameworkProperties()
+ .put(name, fwProps.getProperty(name));
}
- if (cl.hasOption(cacheOption.getOpt())) {
- config.setCacheDirectory(new
File(cl.getOptionValue(cacheOption.getOpt())));
- }
- if (cl.hasOption(homeOption.getOpt())) {
- config.setHomeDirectory(new
File(cl.getOptionValue(homeOption.getOpt())));
- }
- if (cl.hasOption(extensionConfiguration.getOpt())) {
- for(final String optVal :
cl.getOptionValues(extensionConfiguration.getOpt())) {
- Map.Entry<String, Map<String, String>> xc =
splitMap(optVal);
- Map<String, Map<String, String>> ec =
config.getExtensionConfiguration();
- Map<String, String> c = ec.get(xc.getKey());
- if (c == null) {
- c = new HashMap<>();
- ec.put(xc.getKey(), c);
- }
- c.putAll(xc.getValue());
- }
- }
- if (cl.hasOption(frameworkVersionOption.getOpt())) {
-
config.setFrameworkVersion(cl.getOptionValue(frameworkVersionOption.getOpt()));
- }
- if (cl.hasOption(frameworkArtifactOption.getOpt())) {
-
config.setFrameworkArtifact(cl.getOptionValue(frameworkArtifactOption.getOpt()));
+
+ Properties varProps = cl.getOptionProperties(OPT_VARIABLE_VALUES);
+ for (final String name : varProps.stringPropertyNames()) {
+ config.getVariables().put(name, varProps.getProperty(name));
}
- } catch ( final ParseException pe) {
+
+ extractValueFromOption(cl, OPT_VERBOSE, "debug").ifPresent(
+ value ->
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", value));
+
+ extractValuesFromOption(cl,
OPT_FEATURE_FILES).orElseGet(ArrayList::new)
+ .forEach(config::addFeatureFiles);
+
+ extractValueFromOption(cl, OPT_CACHE_DIR).map(File::new)
+ .ifPresent(config::setCacheDirectory);
+
+ extractValueFromOption(cl, OPT_HOME_DIR).map(File::new)
+ .ifPresent(config::setHomeDirectory);
+
+ extractValuesFromOption(cl, OPT_EXTENSION_CONFIGURATION)
+ .ifPresent(values -> values.forEach(v -> {
+ Map.Entry<String, Map<String, String>> xc =
splitMap(v);
+ Map<String, Map<String, String>> ec =
config.getExtensionConfiguration();
+ Map<String, String> c = ec.get(xc.getKey());
+ if (c == null) {
+ c = new HashMap<>();
+ ec.put(xc.getKey(), c);
+ }
+ c.putAll(xc.getValue());
+ }));
+
+ extractValueFromOption(cl, OPT_FELIX_FRAMEWORK_VERSION)
+ .ifPresent(config::setFrameworkVersion);
+
+ extractValueFromOption(cl, OPT_OSGI_FRAMEWORK_ARTIFACT)
+ .ifPresent(config::setFrameworkArtifact);
+
+ } catch (final ParseException pe) {
Main.LOG().error("Unable to parse command line: {}",
pe.getMessage(), pe);
HelpFormatter formatter = new HelpFormatter();
@@ -186,6 +303,7 @@ public class Main {
}
public static void main(final String[] args) {
+
// setup logging
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
System.setProperty("org.slf4j.simpleLogger.showThreadName", "false");
diff --git a/src/test/java/org/apache/sling/feature/launcher/impl/MainTest.java
b/src/test/java/org/apache/sling/feature/launcher/impl/MainTest.java
index ddcfe12..fad9f3f 100644
--- a/src/test/java/org/apache/sling/feature/launcher/impl/MainTest.java
+++ b/src/test/java/org/apache/sling/feature/launcher/impl/MainTest.java
@@ -16,36 +16,171 @@
*/
package org.apache.sling.feature.launcher.impl;
-import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import org.apache.sling.feature.ArtifactId;
+import org.junit.Test;
public class MainTest {
+
@Test
public void testSplitCommandlineArgs() {
- assertArrayEquals(new String[] {"hi", "ho"}, Main.split("hi=ho"));
- assertArrayEquals(new String[] {"hi.de.hi", "true"},
Main.split("hi.de.hi"));
+
+ assertArrayEquals(new String[] { "hi", "ho" },
Main.splitKeyVal("hi=ho"));
+ assertArrayEquals(new String[] { "hi.de.hi", "true" },
Main.splitKeyVal("hi.de.hi"));
}
@Test
public void testSplitMapCommandlineArgs() {
+
assertEquals(new AbstractMap.SimpleEntry<>("foo",
Collections.singletonMap("bar", "tar")),
Main.splitMap("foo:bar=tar"));
assertEquals(new AbstractMap.SimpleEntry<>("hello",
Collections.emptyMap()),
Main.splitMap("hello"));
- Map<String,String> em = new HashMap<>();
+ Map<String, String> em = new HashMap<>();
em.put("a.b.c", "d.e.f");
em.put("h.i.j", "k.l.m");
Map.Entry<String, Map<String, String>> e = new
AbstractMap.SimpleEntry<>("ding.dong", em);
- assertEquals(e,
- Main.splitMap("ding.dong:a.b.c=d.e.f,h.i.j=k.l.m"));
+ assertEquals(e, Main.splitMap("ding.dong:a.b.c=d.e.f,h.i.j=k.l.m"));
+ }
+
+ LauncherConfig noActionAllowesConfig = mock(LauncherConfig.class,
invocationOnMock -> {
+ throw new RuntimeException(invocationOnMock.getMethod().getName());
+ });
+
+ @Test
+ public void testParse() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] {});
+ }
+
+ @Test
+ public void testParseVerbose() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-v", "debug" });
+ assertEquals("debug",
System.getProperty("org.slf4j.simpleLogger.defaultLogLevel"));
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-v" });
+ assertEquals("debug",
System.getProperty("org.slf4j.simpleLogger.defaultLogLevel"));
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-v", "warn" });
+ assertEquals("warn",
System.getProperty("org.slf4j.simpleLogger.defaultLogLevel"));
+
+ }
+
+ @Test
+ public void testParseHome() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-p" });
+
+ LauncherConfig config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-p", "foo" });
+ assertEquals("foo", config.getHomeDirectory().toString());
+
+ }
+
+ @Test
+ public void testParseCacheDir() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-c" });
+
+ LauncherConfig config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-c", "foo" });
+ assertEquals("foo", config.getCacheDirectory().toString());
+
+ }
+
+ @Test
+ public void testParseFelixFwVersion() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-fv" });
+
+ LauncherConfig config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-fv", "foo" });
+ assertEquals("foo", config.getFrameworkVersion().toString());
+
+ }
+
+ @Test
+ public void testParseOSGiFwArtifact() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-fa" });
+
+ LauncherConfig config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-fa", "foo" });
+ assertEquals("foo", config.getFrameworkArtifact());
+
+ }
+
+ @Test
+ public void testParse_Artifact_Clash() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-C" });
+
+ LauncherConfig config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-C", "foo:bar:1" });
+
assertTrue(config.getArtifactClashOverrides().contains(ArtifactId.parse("foo:bar:1")));
+
+ config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-C", "foo:bar:1", "foo:bar:2"
});
+
assertTrue(config.getArtifactClashOverrides().contains(ArtifactId.parse("foo:bar:1")));
+
assertTrue(config.getArtifactClashOverrides().contains(ArtifactId.parse("foo:bar:2")));
+
+ config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-C", "foo:bar:1", "-C",
"foo:bar:2" });
+
assertTrue(config.getArtifactClashOverrides().contains(ArtifactId.parse("foo:bar:1")));
+
assertTrue(config.getArtifactClashOverrides().contains(ArtifactId.parse("foo:bar:2")));
+
}
+
+ @Test
+ public void testParse_RepoUrls() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-u" });
+
+ LauncherConfig config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-u", "foo" });
+ assertArrayEquals(config.getRepositoryUrls(), new Object[] { "foo" });
+
+ config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-u", "foo", "bar" });
+ assertArrayEquals(config.getRepositoryUrls(), new Object[] { "foo",
"bar" });
+
+ config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-u", "foo", "-u", "bar" });
+ assertArrayEquals(config.getRepositoryUrls(), new Object[] { "foo",
"bar" });
+
+ }
+
+ @Test
+ public void testParse_FeatureFiles() {
+
+ Main.parseArgs(noActionAllowesConfig, new String[] { "-f" });
+
+ LauncherConfig config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-f", "foo" });
+ assertTrue(config.getFeatureFiles().contains("foo"));
+
+ config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-f", "foo", "bar" });
+ assertTrue(config.getFeatureFiles().contains("foo"));
+ assertTrue(config.getFeatureFiles().contains("bar"));
+
+ config = new LauncherConfig();
+ Main.parseArgs(config, new String[] { "-f", "foo", "-f", "bar" });
+ assertTrue(config.getFeatureFiles().contains("foo"));
+ assertTrue(config.getFeatureFiles().contains("bar"));
+
+ }
+
}