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

Extract CLI logic from TestNG core #2612

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Current
New: Provide an SPI for 3rd party CLI integration (Julien Herr)
Fixed: GITHUB-2613: Ignored Tests are not retrieved for a mixed test class (test with enabled, disabled and ignored test method) (Krishnan Mahadevan)
Fixed: GITHUB-849: Performance improvement by fixing hashCode (testn & Vladimir Sitnikov)
Fixed: GITHUB-2570: Use Guice injector for instantiate IRetryAnalyzer (Krishnan Mahadevan)
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ include(":testng-bom")
include(":testng-collections")
include(":testng-core")
include(":testng-core-api")
include(":testng-jcommander")
include(":testng-reflection-utils")
include(":testng-runner-api")
include(":testng-runner-junit4")
Expand Down
1 change: 1 addition & 0 deletions testng-ant/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dependencies {
api("org.apache.ant:ant:_")

implementation(projects.testngCore)
implementation(projects.testngJcommander)
testImplementation(projects.testngAsserts)
testImplementation("org.apache.ant:ant-testutil:_")
}
3 changes: 2 additions & 1 deletion testng-ant/src/main/java/org/testng/TestNGAntTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,8 @@ public void execute() throws BuildException {
List<String> argv = createArguments();

if (!forkJvm) {
TestNG tng = TestNG.privateMain(argv.toArray(new String[0]), null);
CliTestNgRunner cliRunner = new JCommanderCliTestNgRunner();
TestNG tng = CliTestNgRunner.Main.privateMain(cliRunner, argv.toArray(new String[0]), null);
actOnResult(tng.getStatus(), false);
return;
}
Expand Down
1 change: 1 addition & 0 deletions testng-bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
api(projects.testngCollections)
api(projects.testngCoreApi)
api(projects.testngCore)
api(projects.testngJcommander)
api(projects.testngReflectionUtils)
api(projects.testngRunnerApi)
api(projects.testngRunnerJunit4)
Expand Down
2 changes: 1 addition & 1 deletion testng-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ dependencies {
api(projects.testngCoreApi)
// Annotations have to be available on the compile classpath for the proper compilation
api("com.google.code.findbugs:jsr305:_")
api("com.beust:jcommander:_")

"guiceApi"(platform("com.google.inject:guice-bom:_"))
"guiceApi"("com.google.inject:guice")
Expand All @@ -39,6 +38,7 @@ dependencies {
implementation("org.webjars:jquery:_")

testImplementation(projects.testngAsserts)
testImplementation(projects.testngTestKit)
testImplementation("org.codehaus.groovy:groovy-all:_")
testImplementation("org.spockframework:spock-core:_")
testImplementation("org.apache-extras.beanshell:bsh:_")
Expand Down
119 changes: 119 additions & 0 deletions testng-core/src/main/java/org/testng/AbstractCommandLineArgs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.testng;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.testng.collections.Lists;
import org.testng.internal.ClassHelper;
import org.testng.internal.Utils;
import org.testng.log4testng.Logger;
import org.testng.xml.XmlMethodSelector;

public abstract class AbstractCommandLineArgs implements CommandLineArgs {

private static final Logger LOGGER = Logger.getLogger(TestNG.class);

protected abstract String[] getListenerValues();

public List<Class<? extends ITestNGListener>> getListener() {
String[] strs = getListenerValues();
if (strs == null) {
return null;
}
List<Class<? extends ITestNGListener>> classes = Lists.newArrayList();
for (String cls : strs) {
Class<?> clazz = ClassHelper.fileToClass(cls);
if (ITestNGListener.class.isAssignableFrom(clazz)) {
classes.add((Class<? extends ITestNGListener>) clazz);
}
}

return classes;
}

protected abstract String getMethodSelectorsValue();

@Override
public List<XmlMethodSelector> getMethodSelectors() {
String methodSelectors = getMethodSelectorsValue();
if (methodSelectors == null) {
return null;
}
String[] strs = Utils.split(methodSelectors, ",");
List<XmlMethodSelector> selectors = new ArrayList<>(strs.length);
for (String cls : strs) {
String[] sel = Utils.split(cls, ":");
try {
if (sel.length == 2) {
XmlMethodSelector selector = new XmlMethodSelector();
selector.setName(sel[0]);
selector.setPriority(Integer.parseInt(sel[1]));
selectors.add(selector);
} else {
LOGGER.error("Method selector value was not in the format org.example.Selector:4");
}
} catch (NumberFormatException nfe) {
LOGGER.error("Method selector value was not in the format org.example.Selector:4");
}
}
return selectors;
}

protected abstract String getObjectFactoryValue();

@Override
public Class<? extends ITestObjectFactory> getObjectFactory() {
String objectFactory = getObjectFactoryValue();
if (objectFactory == null) {
return null;
}
return (Class<? extends ITestObjectFactory>) ClassHelper.fileToClass(objectFactory);
}

protected abstract String getTestClassValue();

@Override
public List<Class<?>> getTestClass() {
String testClass = getTestClassValue();
if (testClass == null) {
return null;
}
String[] strClasses = testClass.split(",");
List<Class<?>> classes = Lists.newArrayList();
for (String c : strClasses) {
classes.add(ClassHelper.fileToClass(c));
}

return classes;
}

protected abstract String getTestNamesValue();

@Override
public List<String> getTestNames() {
String testNames = getTestNamesValue();
if (testNames == null) {
return null;
}
return Arrays.asList(testNames.split(","));
}

protected abstract String getTestRunnerFactoryValue();

@Override
public Class<? extends ITestRunnerFactory> getTestRunnerFactory() {
String testRunnerFactory = getObjectFactoryValue();
if (testRunnerFactory == null) {
return null;
}
return (Class<? extends ITestRunnerFactory>) ClassHelper.fileToClass(testRunnerFactory);
}

protected abstract String getSpiListenersToSkipValue();

@Override
public List<String> getSpiListenersToSkip() {
String spiListenersToSkip = getSpiListenersToSkipValue();
return Arrays.asList(spiListenersToSkip.split(","));
}
}
112 changes: 112 additions & 0 deletions testng-core/src/main/java/org/testng/CliTestNgRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.testng;

import java.util.List;
import java.util.ServiceLoader;
import org.testng.collections.Lists;
import org.testng.internal.ExitCode;
import org.testng.log4testng.Logger;

public interface CliTestNgRunner {

CommandLineArgs parse(String[] argv) throws CommandLineArgs.ParameterException;

void usage();

void clear();

default void exitWithError(String msg) {
System.err.println(msg);
usage();
System.exit(1);
}

class Main {

private static final Logger LOGGER = Logger.getLogger(TestNG.class);

private static final CliTestNgRunner RUNNER = findRunner();

private static CliTestNgRunner findRunner() {
List<CliTestNgRunner> runners = Lists.newArrayList();
ServiceLoader<CliTestNgRunner> runnerLoader = ServiceLoader.load(CliTestNgRunner.class);
for (CliTestNgRunner runner : runnerLoader) {
runners.add(runner);
}
if (runners.isEmpty()) {
throw new TestNGException("No runner found");
}
CliTestNgRunner runner = runners.get(0);
if (runners.size() > 1) {
LOGGER.warn("Too many runners found. Takes the first one: " + runner.getClass());
}
return runner;
}

public static CliTestNgRunner getRunner() {
return RUNNER;
}

/**
* The TestNG entry point for command line execution.
*
* @param argv the TestNG command line parameters.
*/
public static void main(String[] argv) {
TestNG testng = privateMain(RUNNER, argv, null);
System.exit(testng.getStatus());
}

/**
* <B>Note</B>: this method is not part of the public API and is meant for internal usage only.
*
* @param argv The param arguments
* @param listener The listener
* @return The TestNG instance
*/
public static TestNG privateMain(
CliTestNgRunner cliRunner, String[] argv, ITestListener listener) {
if (cliRunner == null) {
LOGGER.warn("No runner passed, use the default one: " + RUNNER.getClass());
cliRunner = RUNNER;
}
TestNG result = new TestNG();

if (null != listener) {
result.addListener(listener);
}

// Parse the arguments
try {
CommandLineArgs cla = cliRunner.parse(argv);
validateCommandLineParameters(cla);
cla.configure(result);
} catch (CommandLineArgs.ParameterException ex) {
cliRunner.exitWithError(ex.getMessage());
}

// Run
try {
result.run();
} catch (TestNGException ex) {
if (TestRunner.getVerbose() > 1) {
ex.printStackTrace(System.out);
} else {
LOGGER.error(ex.getMessage());
}
result.setExitCode(ExitCode.newExitCodeRepresentingFailure());
}

return result;
}

/**
* Double check that the command line parameters are valid.
*
* @param args The command line to check
*/
protected static void validateCommandLineParameters(CommandLineArgs args)
throws CommandLineArgs.ParameterException {
args.validate();
}
}
}
Loading