All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.engine.index.shared.tests.TestExecution Maven / Gradle / Ivy

/*-------------------------------------------------------------------------+
|                                                                          |
| Copyright (c) 2005-2018 The ConQAT Project                               |
|                                                                          |
| Licensed under the Apache License, Version 2.0 (the "License");          |
| you may not use this file except in compliance with the License.         |
| You may obtain a copy of the License at                                  |
|                                                                          |
|    http://www.apache.org/licenses/LICENSE-2.0                            |
|                                                                          |
| Unless required by applicable law or agreed to in writing, software      |
| distributed under the License is distributed on an "AS IS" BASIS,        |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and      |
| limitations under the License.                                           |
|                                                                          |
+-------------------------------------------------------------------------*/
package org.conqat.engine.index.shared.tests;

import java.io.Serializable;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.conqat.engine.sourcecode.coverage.TestUniformPathUtils;
import org.conqat.lib.commons.assessment.Assessment;
import org.conqat.lib.commons.assessment.ETrafficLightColor;
import org.conqat.lib.commons.js_export.ExportToTypeScript;
import org.conqat.lib.commons.string.StringUtils;
import org.conqat.lib.commons.uniformpath.RelativeUniformPath;
import org.conqat.lib.commons.uniformpath.UniformPath;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.teamscale.commons.utils.StringPool;

/**
 * Representation of a single test (method) execution.
 */
@ExportToTypeScript
public class TestExecution implements Serializable {

	private static final long serialVersionUID = 1L;

	/** The name of the JSON property name for {@link #uniformPath}. */
	private static final String UNIFORM_PATH_PROPERTY = "uniformPath";

	/** The name of the JSON property name for {@link #durationSeconds}. */
	private static final String DURATION_SECONDS_PROPERTY = "durationSeconds";

	/** The name of the JSON property name for {@link #result}. */
	private static final String RESULT_PROPERTY = "result";

	/** The name of the JSON property name for {@link #message}. */
	private static final String MESSAGE_PROPERTY = "message";

	/**
	 * The uniform path of the test (method) that was executed. This is an absolute
	 * (i.e. hierarchical) reference which identifies the test uniquely in the scope
	 * of the Teamscale project. It may (but is not required to) correspond to the
	 * path of some automated test case source code known to Teamscale. If the test
	 * was parameterized, this path is expected to reflect the parameter in some
	 * manner.
	 */
	@JsonProperty(UNIFORM_PATH_PROPERTY)
	private final String uniformPath;

	/**
	 * Duration of the execution in seconds.
	 */
	@JsonProperty(DURATION_SECONDS_PROPERTY)
	private final double durationSeconds;

	/**
	 * The actual execution result state.
	 */
	@JsonProperty(RESULT_PROPERTY)
	private final ETestExecutionResult result;

	/**
	 * Optional message given for test failures (normally contains a stack trace).
	 * For skipped and ignored tests the message will hold the message of skipped
	 * and ignored tests. e.g. "Flickers see TS-12345". May be {@code null}.
	 */
	@JsonProperty(MESSAGE_PROPERTY)
	@Nullable
	private final String message;

	private TestExecution(RelativeUniformPath uniformPath, double durationSeconds, ETestExecutionResult result,
			String message) {
		this(uniformPath.toString(), durationSeconds, result, message);
	}

	@JsonCreator
	protected TestExecution(@JsonProperty(UNIFORM_PATH_PROPERTY) String uniformPath,
			@JsonProperty(DURATION_SECONDS_PROPERTY) double durationSeconds,
			@JsonProperty(RESULT_PROPERTY) ETestExecutionResult result,
			@JsonProperty(MESSAGE_PROPERTY) String message) {
		Preconditions.checkArgument(uniformPath != null, "Uniform path can't be null for test execution");
		Preconditions.checkArgument(result != null, "Result can't be null for test execution");
		Preconditions.checkArgument(durationSeconds >= 0, "Test duration can't be negative");
		this.uniformPath = StringPool.intern(uniformPath);
		this.durationSeconds = durationSeconds;
		this.result = result;
		this.message = message;
	}

	/**
	 * @see #uniformPath
	 */
	public String getUniformPath() {
		return uniformPath;
	}

	/**
	 * @see #durationSeconds
	 */
	public double getDurationMillis() {
		return durationSeconds * 1000.0;
	}

	/**
	 * @see #durationSeconds
	 */
	public double getDurationSeconds() {
		return durationSeconds;
	}

	/**
	 * @see #result
	 */
	public ETestExecutionResult getResult() {
		return result;
	}

	/**
	 * @see #message
	 */
	public Optional getMessage() {
		return Optional.ofNullable(message);
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}
		TestExecution that = (TestExecution) o;
		return Double.compare(that.durationSeconds, durationSeconds) == 0
				&& Objects.equals(uniformPath, that.uniformPath) && result == that.result
				&& Objects.equals(message, that.message);
	}

	@Override
	public int hashCode() {
		return Objects.hash(uniformPath, durationSeconds, result, message);
	}

	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this);
	}

	/**
	 * Returns whether something went wrong during execution of this test (either
	 * direct test failure or error during execution).
	 */
	public boolean isFailure() {
		return result == ETestExecutionResult.ERROR || result == ETestExecutionResult.FAILURE;
	}

	/**
	 * Create an assessment for this test result.
	 */
	public Assessment createAssessment() {
		switch (result) {
		case PASSED:
			return new Assessment(ETrafficLightColor.GREEN);
		case IGNORED:
			return new Assessment(ETrafficLightColor.YELLOW);
		case SKIPPED:
			return new Assessment(ETrafficLightColor.YELLOW);
		case ERROR:
			return new Assessment(ETrafficLightColor.RED);
		case FAILURE:
			return new Assessment(ETrafficLightColor.RED);
		default:
			return new Assessment(ETrafficLightColor.UNKNOWN);
		}
	}

	/**
	 * Compute the test execution path.
	 */
	public UniformPath toUniformPath() {
		return TestUniformPathUtils.convertToUniformPath(uniformPath);
	}

	/**
	 * Merges the {@link TestExecution}s. The result is the worst
	 * {@link ETestExecutionResult} with the maximum encountered
	 * {@link TestExecution#durationSeconds}.
	 */
	public static TestExecution merge(Collection testExecutions) {
		Preconditions.checkArgument(!testExecutions.isEmpty(), "Can't merge emtpy collection of test executions.");

		if (testExecutions.size() == 1) {
			return Iterables.getOnlyElement(testExecutions);
		}

		Iterator it = testExecutions.iterator();
		TestExecution worstExecution = it.next();
		while (it.hasNext()) {
			TestExecution nextExecution = it.next();
			Preconditions.checkArgument(nextExecution.uniformPath.equals(worstExecution.uniformPath),
					"Can't merge test executions from separate uniform paths.");
			if (ETestExecutionResult.worst(nextExecution.result, worstExecution.result) != worstExecution.result) {
				worstExecution = nextExecution;
			}
		}

		double maxDurationSeconds = testExecutions.stream().mapToDouble(TestExecution::getDurationSeconds).max()
				.getAsDouble();

		return new TestExecution(worstExecution.uniformPath, maxDurationSeconds, worstExecution.result,
				worstExecution.message);
	}

	/**
	 * Builder for creating {@link TestExecution} instances.
	 */
	public static class Builder {

		private static final Pattern UNESCAPED_SLASH = Pattern.compile("(? pathSegments, String testName) {
			return fromPath(RelativeUniformPath.of(pathSegments).addSuffix(UniformPath.escapeSegment(testName)));
		}

		/**
		 * Sets the failure message. Trims leading whitespace on each line.
		 */
		public Builder setFailureMessage(String failureMessage) {
			if (failureMessage != null) {
				failureMessage = StringUtils.removeWhitespaceAtBeginningOfLine(failureMessage.trim());
				if (!failureMessage.isEmpty()) {
					this.failureMessage = failureMessage;
				}
			}
			return this;
		}

		/**
		 * Sets the test duration.
		 */
		public Builder setDuration(Duration duration) {
			this.durationInSeconds = duration.toMillis() / 1000d;
			return this;
		}

		/**
		 * Sets the duration.
		 */
		public Builder setDurationInSeconds(double durationInSeconds) {
			this.durationInSeconds = durationInSeconds;
			return this;
		}

		/**
		 * Sets the execution result.
		 */
		public Builder setResult(ETestExecutionResult result) {
			this.result = result;
			return this;
		}

		/**
		 * Creates a new {@link TestExecution} instance from this builder.
		 */
		public TestExecution build() {
			return new TestExecution(uniformPath, durationInSeconds, result, failureMessage);
		}

		/**
		 * Returns the absolute uniform path of this test execution including test
		 * namespace/path and test name.
		 */
		public RelativeUniformPath getAbsolutePath() {
			return uniformPath;
		}

	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy