blob: d27c5353f5dd7e612e4294a5950325ed1a0d5cf8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Brock Janiczak (brockj@tpg.com.au)
* - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102236: [JUnit] display execution time next to each test
* Xavier Coulon <xcoulon@redhat.com> - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 - [JUnit] test method name cut off before (
*******************************************************************************/
package org.eclipse.jdt.internal.junit.model;
import java.util.Arrays;
import org.eclipse.jdt.junit.model.ITestElement;
import org.eclipse.jdt.junit.model.ITestElementContainer;
import org.eclipse.jdt.junit.model.ITestRunSession;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.Signature;
public abstract class TestElement implements ITestElement {
public final static class Status {
public static final Status RUNNING_ERROR= new Status("RUNNING_ERROR", 5); //$NON-NLS-1$
public static final Status RUNNING_FAILURE= new Status("RUNNING_FAILURE", 6); //$NON-NLS-1$
public static final Status RUNNING= new Status("RUNNING", 3); //$NON-NLS-1$
public static final Status ERROR= new Status("ERROR", /*1*/ITestRunListener2.STATUS_ERROR); //$NON-NLS-1$
public static final Status FAILURE= new Status("FAILURE", /*2*/ITestRunListener2.STATUS_FAILURE); //$NON-NLS-1$
public static final Status OK= new Status("OK", /*0*/ITestRunListener2.STATUS_OK); //$NON-NLS-1$
public static final Status NOT_RUN= new Status("NOT_RUN", 4); //$NON-NLS-1$
private static final Status[] OLD_CODE= { OK, ERROR, FAILURE};
private final String fName;
private final int fOldCode;
private Status(String name, int oldCode) {
fName= name;
fOldCode= oldCode;
}
public int getOldCode() {
return fOldCode;
}
@Override
public String toString() {
return fName;
}
/* error state predicates */
public boolean isOK() {
return this == OK || this == RUNNING || this == NOT_RUN;
}
public boolean isFailure() {
return this == FAILURE || this == RUNNING_FAILURE;
}
public boolean isError() {
return this == ERROR || this == RUNNING_ERROR;
}
public boolean isErrorOrFailure() {
return isError() || isFailure();
}
/* progress state predicates */
public boolean isNotRun() {
return this == NOT_RUN;
}
public boolean isRunning() {
return this == RUNNING || this == RUNNING_FAILURE || this == RUNNING_ERROR;
}
public boolean isDone() {
return this == OK || this == FAILURE || this == ERROR;
}
public static Status combineStatus(Status one, Status two) {
Status progress= combineProgress(one, two);
Status error= combineError(one, two);
return combineProgressAndErrorStatus(progress, error);
}
private static Status combineProgress(Status one, Status two) {
if (one.isNotRun() && two.isNotRun())
return NOT_RUN;
else if (one.isDone() && two.isDone())
return OK;
else if (!one.isRunning() && !two.isRunning())
return OK; // one done, one not-run -> a parent failed and its children are not run
else
return RUNNING;
}
private static Status combineError(Status one, Status two) {
if (one.isError() || two.isError())
return ERROR;
else if (one.isFailure() || two.isFailure())
return FAILURE;
else
return OK;
}
private static Status combineProgressAndErrorStatus(Status progress, Status error) {
if (progress.isDone()) {
if (error.isError())
return ERROR;
if (error.isFailure())
return FAILURE;
return OK;
}
if (progress.isNotRun()) {
// Assert.isTrue(!error.isErrorOrFailure());
return NOT_RUN;
}
// Assert.isTrue(progress.isRunning());
if (error.isError())
return RUNNING_ERROR;
if (error.isFailure())
return RUNNING_FAILURE;
// Assert.isTrue(error.isOK());
return RUNNING;
}
/**
* @param oldStatus one of {@link ITestRunListener2}'s STATUS_* constants
* @return the Status
*/
public static Status convert(int oldStatus) {
return OLD_CODE[oldStatus];
}
public Result convertToResult() {
if (isNotRun())
return Result.UNDEFINED;
if (isError())
return Result.ERROR;
if (isFailure())
return Result.FAILURE;
if (isRunning()) {
return Result.UNDEFINED;
}
return Result.OK;
}
public ProgressState convertToProgressState() {
if (isRunning()) {
return ProgressState.RUNNING;
}
if (isDone()) {
return ProgressState.COMPLETED;
}
return ProgressState.NOT_STARTED;
}
}
private final TestSuiteElement fParent;
private final String fId;
private String fTestName;
/**
* The display name of the test element, can be <code>null</code>. In that case, use
* {@link TestElement#fTestName fTestName}.
*/
private String fDisplayName;
/**
* The array of fully qualified type names of method parameters if applicable, otherwise
* <code>null</code>.
*/
private String[] fParameterTypes;
/**
* The unique ID of the test element which can be <code>null</code> as it is applicable to JUnit 5
* and above.
*/
private String fUniqueId;
private Status fStatus;
private String fTrace;
private String fExpected;
private String fActual;
private boolean fAssumptionFailed;
/**
* Running time in seconds. Contents depend on the current {@link #getProgressState()}:
* <ul>
* <li>{@link org.eclipse.jdt.junit.model.ITestElement.ProgressState#NOT_STARTED}: {@link Double#NaN}</li>
* <li>{@link org.eclipse.jdt.junit.model.ITestElement.ProgressState#RUNNING}: negated start time</li>
* <li>{@link org.eclipse.jdt.junit.model.ITestElement.ProgressState#STOPPED}: elapsed time</li>
* <li>{@link org.eclipse.jdt.junit.model.ITestElement.ProgressState#COMPLETED}: elapsed time</li>
* </ul>
*/
/* default */ double fTime= Double.NaN;
/**
* @param parent the parent, can be <code>null</code>
* @param id the test id
* @param testName the test name
* @param displayName the test display name, can be <code>null</code>
* @param parameterTypes the array of fully qualified type names of method parameters if
* applicable, otherwise <code>null</code>
* @param uniqueId the unique ID of the test element, can be <code>null</code> as it is applicable to JUnit 5 and above
*/
public TestElement(TestSuiteElement parent, String id, String testName, String displayName, String[] parameterTypes, String uniqueId) {
Assert.isNotNull(id);
Assert.isNotNull(testName);
fParent= parent;
fId= id;
fTestName= testName;
fDisplayName= displayName;
fParameterTypes= parameterTypes;
fUniqueId= uniqueId;
fStatus= Status.NOT_RUN;
if (parent != null)
parent.addChild(this);
}
@Override
public ProgressState getProgressState() {
return getStatus().convertToProgressState();
}
@Override
public Result getTestResult(boolean includeChildren) {
if (fAssumptionFailed) {
return Result.IGNORED;
}
return getStatus().convertToResult();
}
@Override
public ITestRunSession getTestRunSession() {
return getRoot().getTestRunSession();
}
@Override
public ITestElementContainer getParentContainer() {
if (fParent instanceof TestRoot) {
return getTestRunSession();
}
return fParent;
}
@Override
public FailureTrace getFailureTrace() {
Result testResult= getTestResult(false);
if (testResult == Result.ERROR || testResult == Result.FAILURE
|| (testResult == Result.IGNORED && fTrace != null)) {
return new FailureTrace(fTrace, fExpected, fActual);
}
return null;
}
/**
* @return the parent suite, or <code>null</code> for the root
*/
public TestSuiteElement getParent() {
return fParent;
}
public String getId() {
return fId;
}
public String getTestName() {
return fTestName;
}
public void setName(String name) {
fTestName= name;
}
public void setStatus(Status status) {
if (status == Status.RUNNING) {
fTime= - System.currentTimeMillis() / 1000d ;
} else if (status.convertToProgressState() == ProgressState.COMPLETED) {
if (fTime < 0) { // assert ! Double.isNaN(fTime)
double endTime= System.currentTimeMillis() / 1000.0d;
fTime= endTime + fTime;
}
}
fStatus= status;
TestSuiteElement parent= getParent();
if (parent != null)
parent.childChangedStatus(this, status);
}
public void setStatus(Status status, String trace, String expected, String actual) {
if (trace != null && fTrace != null) {
//don't overwrite first trace if same test run logs multiple errors
fTrace= fTrace + trace;
} else {
fTrace= trace;
fExpected= expected;
fActual= actual;
}
setStatus(status);
}
public Status getStatus() {
return fStatus;
}
public String getTrace() {
return fTrace;
}
public String getExpected() {
return fExpected;
}
public String getActual() {
return fActual;
}
public boolean isComparisonFailure() {
return fExpected != null && fActual != null;
}
/**
* @return return the class name
* @see org.eclipse.jdt.internal.junit.runner.ITestIdentifier#getName()
* @see org.eclipse.jdt.internal.junit.runner.MessageIds#TEST_IDENTIFIER_MESSAGE_FORMAT
*/
public String getClassName() {
return extractClassName(getTestName());
}
private static String extractClassName(String testNameString) {
testNameString= extractRawClassName(testNameString);
testNameString= testNameString.replace('$', '.'); // see bug 178503
return testNameString;
}
public static String extractRawClassName(String testNameString) {
if (testNameString.startsWith("[") && testNameString.endsWith("]")) { //$NON-NLS-1$ //$NON-NLS-2$
// a group of parameterized tests, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512
return testNameString;
}
int index= testNameString.lastIndexOf('(');
if (index < 0)
return testNameString;
int end= testNameString.lastIndexOf(')');
testNameString= testNameString.substring(index + 1, end > index ? end : testNameString.length());
return testNameString;
}
public TestRoot getRoot() {
return getParent().getRoot();
}
public void setElapsedTimeInSeconds(double time) {
fTime= time;
}
@Override
public double getElapsedTimeInSeconds() {
if (Double.isNaN(fTime) || fTime < 0.0d) {
return Double.NaN;
}
return fTime;
}
public void setAssumptionFailed(boolean assumptionFailed) {
fAssumptionFailed= assumptionFailed;
}
public boolean isAssumptionFailure() {
return fAssumptionFailed;
}
@Override
public String toString() {
return getProgressState() + " - " + getTestResult(true); //$NON-NLS-1$
}
/**
* Returns the display name of the test. Can be <code>null</code>. In that case, use
* {@link TestElement#getTestName() getTestName()}.
*
* @return the test display name, can be <code>null</code>
*/
public String getDisplayName() {
return fDisplayName;
}
/**
* @return the array of fully qualified type names of method parameters if applicable, otherwise
* <code>null</code>
*/
public String[] getParameterTypes() {
return fParameterTypes;
}
/**
* Returns the unique ID of the test element. Can be <code>null</code> as it is applicable to JUnit
* 5 and above.
*
* @return the unique ID of the test, can be <code>null</code>
*/
public String getUniqueId() {
return fUniqueId;
}
/**
* Creates encoded type signatures from fully qualified type names provided by
* {@link #getParameterTypes()}.
*
* @return the array of parameter type signatures obtained by converting the fully qualified
* type names from {@link #getParameterTypes()}, can be <code>null</code>
*/
public String[] getParameterTypeSignatures() {
// TODO - JUnit5: Create encoded type signatures so that IType.getMethod(String name, String[] parameterTypeSignatures) can find the method
// use new API from jdt.core - https://bugs.eclipse.org/bugs/show_bug.cgi?id=502563
// For two overloaded methods with same simple name of param type (e.g. pkg1.A, pkg2.A), the type sign created by using simple name will be same
// hence IType#getMethod will just return the first of the overloaded methods, which is wrong.
return fParameterTypes == null ? null : Arrays.stream(fParameterTypes).map(t -> Signature.createTypeSignature(Signature.getSimpleName(t.trim()), false)).toArray(String[]::new);
}
}