Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.openl.rules.maven.TestMojo Maven / Gradle / Ivy
package org.openl.rules.maven;
import static org.openl.rules.testmethod.TestStatus.TR_NEQ;
import static org.openl.rules.testmethod.TestStatus.TR_OK;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.openl.CompiledOpenClass;
import org.openl.OpenClassUtil;
import org.openl.dependency.CompiledDependency;
import org.openl.dependency.ResolvedDependency;
import org.openl.exception.OpenLCompilationException;
import org.openl.message.OpenLMessage;
import org.openl.message.OpenLMessagesUtils;
import org.openl.message.Severity;
import org.openl.rules.project.instantiation.AbstractDependencyManager;
import org.openl.rules.project.instantiation.RulesInstantiationException;
import org.openl.rules.project.instantiation.SimpleProjectEngineFactory;
import org.openl.rules.project.model.Module;
import org.openl.rules.project.model.ProjectDescriptor;
import org.openl.rules.project.resolving.ProjectResolver;
import org.openl.rules.project.resolving.ProjectResolvingException;
import org.openl.rules.testmethod.BaseTestUnit;
import org.openl.rules.testmethod.ITestUnit;
import org.openl.rules.testmethod.ProjectHelper;
import org.openl.rules.testmethod.TestRunner;
import org.openl.rules.testmethod.TestStatus;
import org.openl.rules.testmethod.TestSuite;
import org.openl.rules.testmethod.TestSuiteExecutor;
import org.openl.rules.testmethod.TestSuiteMethod;
import org.openl.rules.testmethod.TestUnit;
import org.openl.rules.testmethod.TestUnitsResults;
import org.openl.rules.testmethod.result.ComparedResult;
import org.openl.types.IOpenClass;
import org.openl.types.NullOpenClass;
import org.openl.types.impl.ThisField;
/**
* Runs OpenL Tablets tests.
*
* @author Yury Molchan
*/
@Mojo(name = "test", defaultPhase = LifecyclePhase.TEST, requiresDependencyResolution = ResolutionScope.TEST)
public final class TestMojo extends BaseOpenLMojo {
private static final String FAILURE = "<<< FAILURE";
private static final String ERROR = "<<< ERROR";
private static final int MAX_MODULES_IN_QUEUE = 10;
/**
* Parameter to skip running OpenL Tablets tests if it set to 'true'.
*/
@Parameter(property = "skipTests")
private boolean skipTests;
/**
* Directory containing OpenL Tablets sources to be used in testing OpenL Tablets rules.
*/
@Parameter(defaultValue = "${project.build.testSourceDirectory}/../openl")
private File testSourceDirectory;
/**
* Base directory where all reports are saved. Reports are surefire format compatible.
*/
@Parameter(defaultValue = "${project.build.directory}/openl-test-reports")
private File reportsDirectory;
/**
* File format of the test reports. Supported values: junit4 or xlsx.
*/
@Parameter(defaultValue = "junit4")
private ReportFormat[] reportsFormat;
/**
* Thread count to run test cases. The values are as follows:
*
* 4 - Runs tests with four threads.
* 1.5C - Runs tests with 1.5 thread per CPU core.
* none - Runs tests sequentially. No extra threads for running tests are created.
* auto - Automatically configures thread count.
*
*/
@Parameter(defaultValue = "auto")
private String threadCount;
/**
* Parameter for compiling the project in the single module mode, where each module is compiled in sequence and
* tests from that module are run. This parameter is beneficial for big projects. If this parameter is set to false,
* all modules are compiled at once and all tests from all modules are run.
*/
@Parameter(defaultValue = "false")
private boolean singleModuleMode;
/**
* Parameter for compiling the project in the smart mode to save the memory, where each module is compiled in
* sequence and tests from that module are run. This parameter is beneficial for big projects.
*/
@Parameter(defaultValue = "0")
private int maxModulesInMemory;
/**
* Additional options for testing defined externally.
*/
@Parameter
private Map externalParameters;
@Parameter(defaultValue = "${project.testClasspathElements}", readonly = true, required = true)
private List classpath;
private TestRunner testRunner;
@Override
public void execute(String sourcePath, boolean hasDependencies) throws Exception {
Summary summary = runAllTests(sourcePath, hasDependencies);
info("");
info("Results:");
if (summary.getFailedTests() > 0) {
info("");
info("Failed Tests:");
for (String failure : summary.getSummaryFailures()) {
info(" ", failure);
}
}
if (summary.getErrors() > 0) {
info("");
info("Tests in error:");
for (String error : summary.getSummaryErrors()) {
info(" ", error);
}
}
info("");
info("Total tests run: ",
summary.getRunTests(),
", Failures: ",
summary.getFailedTests(),
", Errors: ",
summary.getErrors());
info("");
if (summary.getFailedTests() > 0 || summary.getErrors() > 0) {
throw new MojoFailureException("There are errors in the OpenL tests.");
} else if (summary.isHasCompilationErrors()) {
throw new MojoFailureException("There are compilation errors in the OpenL tests.");
}
}
private Summary runAllTests(String sourcePath,
boolean hasDependencies) throws IOException, RulesInstantiationException, ProjectResolvingException {
String testSourcePath = sourcePath;
String mainSourcePath = null;
File testDir = testSourceDirectory.getCanonicalFile();
if (testDir.isDirectory() && ProjectResolver.getInstance().isRulesProject(testDir) != null) {
mainSourcePath = sourcePath;
try {
testSourcePath = testDir.getCanonicalPath();
} catch (Exception e) {
warn("The path to OpenL test directory cannot be converted to canonical form.");
testSourcePath = testDir.getPath();
}
}
return singleModuleMode || maxModulesInMemory > 0 ? executeModuleByModule(testSourcePath,
mainSourcePath,
hasDependencies) : executeAllAtOnce(testSourcePath, mainSourcePath, hasDependencies);
}
private Summary executeAllAtOnce(String testSourcePath,
String mainSourcePath,
boolean hasDependencies) throws MalformedURLException,
RulesInstantiationException,
ProjectResolvingException {
URL[] urls = toURLs(classpath);
ClassLoader classLoader = null;
try {
classLoader = new URLClassLoader(urls, SimpleProjectEngineFactory.class.getClassLoader());
SimpleProjectEngineFactory.SimpleProjectEngineFactoryBuilder> builder = new SimpleProjectEngineFactory.SimpleProjectEngineFactoryBuilder<>();
if (hasDependencies) {
builder.setWorkspace(workspaceFolder.getPath());
}
if (mainSourcePath != null) {
builder.setProjectDependencies(mainSourcePath);
}
SimpleProjectEngineFactory> factory = builder.setProject(testSourcePath)
.setClassLoader(classLoader)
.setExecutionMode(false)
.setExternalParameters(externalParameters)
.build();
CompiledOpenClass openLRules = factory.getCompiledOpenClass();
return executeTests(openLRules);
} finally {
OpenClassUtil.releaseClassLoader(classLoader);
}
}
private Summary executeModuleByModule(String testSourcePath,
String mainSourcePath,
boolean hasDependencies) throws MalformedURLException, ProjectResolvingException {
ProjectDescriptor pd = ProjectResolver.getInstance().resolve(new File(testSourcePath));
if (pd == null) {
throw new ProjectResolvingException("Failed to resolve project. Defined location is not an OpenL project.");
}
int runTests = 0;
int failedTests = 0;
int errors = 0;
List summaryFailures = new ArrayList<>();
List summaryErrors = new ArrayList<>();
boolean hasCompilationErrors = false;
List modules = new ArrayList<>(pd.getModules());
modules.sort(Comparator.comparing(Module::getName));
URL[] urls = toURLs(classpath);
ClassLoader classLoader = new URLClassLoader(urls, SimpleProjectEngineFactory.class.getClassLoader());
SimpleProjectEngineFactory.SimpleProjectEngineFactoryBuilder> builder = new SimpleProjectEngineFactory.SimpleProjectEngineFactoryBuilder<>();
if (mainSourcePath != null) {
builder.setProjectDependencies(mainSourcePath);
}
if (hasDependencies) {
builder.setWorkspace(workspaceFolder.getPath());
}
SimpleProjectEngineFactory> factory = builder.setProject(testSourcePath)
.setClassLoader(classLoader)
.setExecutionMode(false)
.setExternalParameters(externalParameters)
.build();
Deque queueToReset = new ArrayDeque<>();
for (Module module : modules) {
ResolvedDependency dependency = AbstractDependencyManager.buildResolvedDependency(module);
try {
info("");
info("Searching tests in module '", module.getName(), "'...");
CompiledOpenClass compiledOpenClass;
try {
CompiledDependency compiledDependency = factory.getDependencyManager().loadDependency(dependency);
compiledOpenClass = compiledDependency.getCompiledOpenClass();
} catch (OpenLCompilationException e) {
Collection messages = new LinkedHashSet<>();
for (OpenLMessage openLMessage : OpenLMessagesUtils.newErrorMessages(e)) {
String message = String
.format("Failed to load module '%s': %s", module.getName(), openLMessage.getSummary());
messages.add(new OpenLMessage(message, Severity.ERROR));
}
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
try {
compiledOpenClass = new CompiledOpenClass(NullOpenClass.the, messages);
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
}
Summary summary = executeTests(compiledOpenClass);
runTests += summary.getRunTests();
failedTests += summary.getFailedTests();
errors += summary.getErrors();
summaryFailures.addAll(summary.getSummaryFailures());
summaryErrors.addAll(summary.getSummaryErrors());
hasCompilationErrors |= summary.isHasCompilationErrors();
} finally {
queueToReset.add(dependency);
while (queueToReset.size() > maxModulesInMemory) {
queueToReset.poll();
}
factory.getDependencyManager().resetOthers(queueToReset.toArray(new ResolvedDependency[0]));
}
}
OpenClassUtil.releaseClassLoader(classLoader);
return new Summary(runTests, failedTests, errors, summaryFailures, summaryErrors, hasCompilationErrors);
}
private Summary executeTests(CompiledOpenClass openLRules) {
TestRunner testRunner = getTestRunner();
IOpenClass openClass = openLRules.getOpenClassWithErrors();
if (openLRules.hasErrors()) {
error("");
error("There are compilation errors. It can affect test execution.");
Collection errorMessages = OpenLMessagesUtils
.filterMessagesBySeverity(openLRules.getAllMessages(), Severity.ERROR);
int i = 0;
for (OpenLMessage message : errorMessages) {
String location = message.getSourceLocation() == null ? "" : (" at " + message.getSourceLocation());
error(i + 1 + ". '", message.getSummary(), "'", location);
i++;
}
error("");
}
int runTests = 0;
int failedTests = 0;
int errors = 0;
List summaryFailures = new ArrayList<>();
List summaryErrors = new ArrayList<>();
TestSuiteExecutor testSuiteExecutor = createTestSuiteExecutor();
try {
TestSuiteMethod[] tests = ProjectHelper.allTesters(openClass);
for (TestSuiteMethod test : tests) {
String moduleName = test.getModuleName();
try {
String moduleInfo = moduleName == null ? "" : String.format(" from module '%s'", moduleName);
info("Running ", String.format("'%s'", test.getName()), moduleInfo, "...");
TestUnitsResults result;
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(openLRules.getClassLoader());
if (testSuiteExecutor == null) {
result = new TestSuite(test, testRunner).invokeSequentially(openClass, 1);
} else {
result = new TestSuite(test, testRunner).invokeParallel(testSuiteExecutor, openClass, 1);
}
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
writeReport(result);
int suitTests = result.getNumberOfTestUnits();
int suitFailures = result.getNumberOfAssertionFailures();
int suitErrors = result.getNumberOfErrors();
info("Tests run: ",
suitTests,
", Failures: ",
suitFailures,
", Errors: ",
suitErrors,
". Time elapsed: ",
formatTime(result.getExecutionTime()),
" sec.",
result.getNumberOfFailures() > 0 ? " " + FAILURE : "");
if (result.getNumberOfFailures() > 0) {
showFailures(test, result, summaryFailures, summaryErrors);
}
runTests += suitTests;
failedTests += suitFailures;
errors += suitErrors;
} catch (Exception e) {
error(e);
errors++;
String modulePrefix = moduleName == null ? "" : moduleName + ".";
Throwable cause = ExceptionUtils.getRootCause(e);
if (cause == null) {
cause = e;
}
summaryErrors.add(modulePrefix + test.getName() + " " + cause.getClass().getName());
}
}
return new Summary(runTests, failedTests, errors, summaryFailures, summaryErrors, openLRules.hasErrors());
} finally {
if (testSuiteExecutor != null) {
testSuiteExecutor.destroy();
}
}
}
private TestRunner getTestRunner() {
if (testRunner == null) {
TestRunner runner = new TestRunner(BaseTestUnit.Builder.getInstance());
for (ReportFormat reportFormat : reportsFormat) {
// For now xlsx exporter needs all info.
if (reportFormat == ReportFormat.xlsx) {
runner = new TestRunner(TestUnit.Builder.getInstance());
break;
}
}
testRunner = runner;
}
return testRunner;
}
private void writeReport(TestUnitsResults result) throws Exception {
for (ReportFormat reporter : reportsFormat) {
reporter.write(reportsDirectory, result);
}
}
private void showFailures(TestSuiteMethod test,
TestUnitsResults result,
List summaryFailures,
List summaryErrors) {
int num = 1;
String moduleName = test.getModuleName();
String modulePrefix = moduleName == null ? "" : moduleName + ".";
for (ITestUnit testUnit : result.getTestUnits()) {
TestStatus status = testUnit.getResultStatus();
if (status != TR_OK) {
String failureType = status == TR_NEQ ? FAILURE : ERROR;
String description = testUnit.getDescription();
info(" Test case: #",
num,
ITestUnit.DEFAULT_DESCRIPTION.equals(description) ? "" : (" (" + description + ")"),
". Time elapsed: ",
formatTime(testUnit.getExecutionTime()),
" sec. ",
failureType);
if (status == TR_NEQ) {
StringBuilder summaryBuilder = new StringBuilder(modulePrefix + test.getName() + "#" + num);
List comparisonResults = testUnit.getComparisonResults();
int rowNum = 0;
for (ComparedResult comparisonResult : comparisonResults) {
if (comparisonResult.getStatus() != TR_OK) {
var fieldName = comparisonResult.getFieldName();
var expectedValue = toString(comparisonResult.getExpectedValue());
var actualValue = toString(comparisonResult.getActualValue());
if (fieldName == null || ThisField.THIS.equals(fieldName)) {
info(" Expected: <" + expectedValue + "> but was: <" + actualValue + ">");
summaryBuilder.append(" expected: <")
.append(expectedValue)
.append("> but was <")
.append(actualValue)
.append(">");
} else {
if (rowNum > 0) {
summaryBuilder.append(",");
}
info(" Field " + fieldName + " expected: <" + expectedValue + "> but was: <" + actualValue + ">");
summaryBuilder.append(" field ")
.append(fieldName)
.append(" expected: <")
.append(expectedValue)
.append("> but was <")
.append(actualValue)
.append(">");
}
rowNum++;
}
}
summaryFailures.add(summaryBuilder.toString());
} else {
Throwable error = (Throwable) testUnit.getActualResult();
info(" Error: ", error, "\n", ExceptionUtils.getStackTrace(error));
Throwable cause = ExceptionUtils.getRootCause(error);
if (cause == null) {
cause = error;
}
summaryErrors.add(modulePrefix + test.getName() + "#" + num + " " + cause.getClass().getName());
}
}
num++;
}
info("");
}
@Override
String getHeader() {
return "OPENL TESTS";
}
@Override
boolean isDisabled() {
return skipTests;
}
private String formatTime(long nanoseconds) {
double time = (double) nanoseconds / 1000_000_000;
DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
df.applyPattern("#.###");
return time < 0.001 ? "< 0.001" : df.format(time);
}
private String toString(Object value) {
if (value != null && value.getClass().isArray()) {
if (value instanceof Object[]) {
return Arrays.deepToString((Object[]) value);
} else {
var string = Arrays.deepToString(new Object[]{value});
return string.substring(1, string.length() - 1); // Remove the first and the last brackets in [[1, 2, 3]]
}
}
return Objects.toString(value);
}
private TestSuiteExecutor createTestSuiteExecutor() {
int threads;
switch (threadCount) {
case "none":
return null;
case "auto":
// Can be changed in the future
threads = Runtime.getRuntime().availableProcessors() + 2;
break;
default:
if (threadCount.matches("\\d+")) {
threads = Integer.parseInt(threadCount);
} else if (threadCount.matches("\\d[\\d.]*[cC]")) {
float multiplier = Float.parseFloat(threadCount.substring(0, threadCount.length() - 1));
threads = (int) (multiplier * Runtime.getRuntime().availableProcessors());
} else {
throw new IllegalArgumentException(String.format("Incorrect thread count '%s'", threadCount));
}
break;
}
info("Run tests using ", threads, " threads.");
return new TestSuiteExecutor(threads);
}
private static class Summary {
private final int runTests;
private final int failedTests;
private final int errors;
private final List summaryFailures;
private final List summaryErrors;
private final boolean hasCompilationErrors;
public Summary(int runTests,
int failedTests,
int errors,
List summaryFailures,
List summaryErrors,
boolean hasCompilationErrors) {
this.runTests = runTests;
this.failedTests = failedTests;
this.errors = errors;
this.summaryFailures = Collections.unmodifiableList(summaryFailures);
this.summaryErrors = Collections.unmodifiableList(summaryErrors);
this.hasCompilationErrors = hasCompilationErrors;
}
public int getRunTests() {
return runTests;
}
public int getFailedTests() {
return failedTests;
}
public int getErrors() {
return errors;
}
public List getSummaryFailures() {
return summaryFailures;
}
public List getSummaryErrors() {
return summaryErrors;
}
public boolean isHasCompilationErrors() {
return hasCompilationErrors;
}
}
}