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

edu.hm.hafner.grading.TestScore Maven / Gradle / Ivy

The newest version!
package edu.hm.hafner.grading;

import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.errorprone.annotations.CanIgnoreReturnValue;

import edu.hm.hafner.coverage.ContainerNode;
import edu.hm.hafner.coverage.Metric;
import edu.hm.hafner.coverage.ModuleNode;
import edu.hm.hafner.coverage.Node;
import edu.hm.hafner.coverage.TestCase;
import edu.hm.hafner.coverage.TestCase.TestResult;
import edu.hm.hafner.util.Ensure;
import edu.hm.hafner.util.Generated;
import edu.umd.cs.findbugs.annotations.CheckForNull;

/**
 * Computes the {@link Score} impact of test results. These results are obtained by evaluating the
 * number of passed, failed or skipped tests.
 *
 * @author Eva-Maria Zeintl
 */
@SuppressWarnings("PMD.DataClass")
public final class TestScore extends Score {
    @Serial
    private static final long serialVersionUID = 3L;
    private static final int CAPACITY = 1024;

    private final int passedSize;
    private final int failedSize;
    private final int skippedSize;
    private transient Node report; // do not persist the tree of nodes

    private TestScore(final String id, final String name, final TestConfiguration configuration,
            final List scores) {
        super(id, name, configuration, scores.toArray(new TestScore[0]));

        this.failedSize = aggregate(scores, TestScore::getFailedSize);
        this.skippedSize = aggregate(scores, TestScore::getSkippedSize);
        this.passedSize = aggregate(scores, TestScore::getPassedSize);

        this.report = new ContainerNode(name);
        scores.stream().map(TestScore::getReport).forEach(report::addChild);
    }

    private TestScore(final String id, final String name, final TestConfiguration configuration, final Node report) {
        super(id, name, configuration);

        this.report = report;

        passedSize = sum(report, TestResult.PASSED);
        failedSize = sum(report, TestResult.FAILED);
        skippedSize = sum(report, TestResult.SKIPPED);
    }

    /**
     * Restore an empty report after de-serialization.
     *
     * @return this
     */
    @Serial @CanIgnoreReturnValue
    private Object readResolve() {
        report = new ModuleNode("empty");

        return this;
    }

    private int aggregate(final List scores, final Function property) {
        return scores.stream().reduce(0, (sum, score) -> sum + property.apply(score), Integer::sum);
    }

    private int sum(final Node testReport, final TestResult testResult) {
        return testReport.getTestCases().stream()
                .map(TestCase::getResult)
                .filter(status -> status == testResult)
                .mapToInt(i -> 1)
                .sum();
    }

    @JsonIgnore
    public Node getReport() {
        return report;
    }

    public int getReportFiles() {
        return report.getAll(Metric.MODULE).size();
    }

    @Override
    public int getImpact() {
        var configuration = getConfiguration();

        int change = 0;

        change = change + configuration.getPassedImpact() * getPassedSize();
        change = change + configuration.getFailureImpact() * getFailedSize();
        change = change + configuration.getSkippedImpact() * getSkippedSize();

        return change;
    }

    public int getPassedSize() {
        return passedSize;
    }

    /**
     * Returns whether this score has any passed tests.
     *
     * @return {@code true} if this score has passed tests, {@code false} otherwise
     */
    public boolean hasPassedTests() {
        return getPassedSize() > 0;
    }

    public int getTotalSize() {
        return passedSize + failedSize + skippedSize;
    }

    public int getFailedSize() {
        return failedSize;
    }

    /**
     * Returns whether this score has any test failures.
     *
     * @return {@code true} if this score has any test failures, {@code false} otherwise
     */
    public boolean hasFailures() {
        return getFailedSize() > 0;
    }

    public int getSkippedSize() {
        return skippedSize;
    }

    /**
     * Returns whether this score has any skipped tests.
     *
     * @return {@code true} if this score has any skipped tests, {@code false} otherwise
     */
    public boolean hasSkippedTests() {
        return getSkippedSize() > 0;
    }

    /**
     * Returns the list of failed test cases.
     *
     * @return the failed test cases
     */
    public List getFailures() {
        return filterTests(TestResult.FAILED);
    }

    /**
     * Returns the list of skipped test cases.
     *
     * @return the skipped test cases
     */
    public List getSkippedTests() {
        return filterTests(TestResult.SKIPPED);
    }

    private List filterTests(final TestResult result) {
        return getReport().getTestCases().stream()
                .filter(testCase -> testCase.getResult() == result).collect(Collectors.toList());
    }

    @Override
    protected String createSummary() {
        var summary = new StringBuilder(CAPACITY);
        if (hasFailures()) {
            summary.append(format("%d tests failed, %d passed", getFailedSize(), getPassedSize()));
        }
        else {
            summary.append(format("%d tests passed", getPassedSize()));
        }
        if (getSkippedSize() > 0) {
            summary.append(format(", %d skipped", getSkippedSize()));
        }
        return summary.toString();
    }

    @Override @Generated
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        var testScore = (TestScore) o;
        return passedSize == testScore.passedSize
                && failedSize == testScore.failedSize
                && skippedSize == testScore.skippedSize;
    }

    @Override @Generated
    public int hashCode() {
        return Objects.hash(super.hashCode(), passedSize, failedSize, skippedSize);
    }

    /**
     * A builder for {@link TestScore} instances.
     */
    @SuppressWarnings({"checkstyle:HiddenField", "ParameterHidesMemberVariable"})
    public static class TestScoreBuilder {
        @CheckForNull
        private String id;
        @CheckForNull
        private String name;
        @CheckForNull
        private TestConfiguration configuration;

        private final List scores = new ArrayList<>();
        @CheckForNull
        private Node report;

        /**
         * Sets the ID of the analysis score.
         *
         * @param id
         *         the ID
         *
         * @return this
         */
        @CanIgnoreReturnValue
        public TestScoreBuilder withId(final String id) {
            this.id = id;
            return this;
        }

        private String getId() {
            return StringUtils.defaultIfBlank(id, getConfiguration().getId());
        }

        /**
         * Sets the human-readable name of the analysis score.
         *
         * @param name
         *         the name to show
         *
         * @return this
         */
        @CanIgnoreReturnValue
        public TestScoreBuilder withName(final String name) {
            this.name = name;
            return this;
        }

        private String getName() {
            return StringUtils.defaultIfBlank(name, getConfiguration().getName());
        }

        /**
         * Sets the grading configuration.
         *
         * @param configuration
         *         the grading configuration
         *
         * @return this
         */
        @CanIgnoreReturnValue
        public TestScoreBuilder withConfiguration(final TestConfiguration configuration) {
            this.configuration = configuration;
            return this;
        }

        private TestConfiguration getConfiguration() {
            return Objects.requireNonNull(configuration);
        }

        /**
         * Sets the test report for this score.
         *
         * @param rootNode
         *         the root of the tree with the test cases
         *
         * @return this
         */
        @CanIgnoreReturnValue
        public TestScoreBuilder withReport(final Node rootNode) {
            this.report = rootNode;
            return this;
        }

        /**
         * Sets the scores that should be aggregated by this score.
         *
         * @param scores
         *         the scores to aggregate
         *
         * @return this
         */
        @CanIgnoreReturnValue
        public TestScoreBuilder withScores(final List scores) {
            Ensure.that(scores).isNotEmpty("You cannot add an empty list of scores.");
            this.scores.clear();
            this.scores.addAll(scores);
            return this;
        }

        /**
         * Builds the {@link TestScore} instance with the configured values.
         *
         * @return the new instance
         */
        public TestScore build() {
            Ensure.that(report != null ^ !scores.isEmpty()).isTrue(
                    "You must either specify test results or provide a list of sub-scores.");

            if (scores.isEmpty() && report != null) {
                return new TestScore(getId(), getName(), getConfiguration(), report);
            }
            else {
                return new TestScore(getId(), getName(), getConfiguration(), scores);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy