001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright (c) 2005-2018 The ConQAT Project                               |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016|                                                                          |
017+-------------------------------------------------------------------------*/
018package org.conqat.engine.index.shared.tests;
019
020import java.io.Serializable;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Objects;
027import java.util.Set;
028
029import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
030import org.conqat.engine.index.shared.CommitDescriptor;
031import org.conqat.lib.commons.assertion.CCSMAssert;
032import org.conqat.lib.commons.collections.CollectionUtils;
033import org.conqat.lib.commons.js_export.ExportToJavaScript;
034
035import com.fasterxml.jackson.annotation.JsonCreator;
036import com.fasterxml.jackson.annotation.JsonProperty;
037import com.google.common.annotations.VisibleForTesting;
038import com.google.common.base.Preconditions;
039
040/** Representation of a test execution in a partition. */
041@ExportToJavaScript
042public class TestExecutionWithPartition implements Serializable {
043
044        private static final long serialVersionUID = 1L;
045
046        /** The name of the JSON property name for {@link #testExecution}. */
047        private static final String TEST_EXECUTION_PROPERTY = "testExecution";
048
049        /** The name of the JSON property name for {@link #partition}. */
050        private static final String PARTITION_PROPERTY = "partition";
051
052        /** The name of the JSON property name for {@link #commit}. */
053        private static final String COMMIT_PROPERTY = "commit";
054
055        /** The name of the JSON property name for {@link #predecessorCommits}. */
056        private static final String PREDECESSOR_COMMITS_PROPERTY = "predecessorCommits";
057
058        /** The test execution. */
059        @JsonProperty(TEST_EXECUTION_PROPERTY)
060        private final TestExecution testExecution;
061
062        /** The partition. */
063        @JsonProperty(PARTITION_PROPERTY)
064        private final String partition;
065
066        /** The commit of the report upload containing the {@link #testExecution}. */
067        @JsonProperty(COMMIT_PROPERTY)
068        private final CommitDescriptor commit;
069
070        /**
071         * True if and only if this is a {@link TestExecutionWithPartition} created by
072         * {@link #merge(CommitDescriptor, Collection)}.
073         */
074        @JsonProperty("isArtificialMergeTestExecution")
075        private final boolean isArtificialMergeTestExecution;
076
077        /**
078         * The commit of the previous report upload. May be on another branch. For a
079         * merge the {@link TestExecutionWithPartition#getCommit()} of the first parent
080         * branch is used as the predecessor if one is present.
081         */
082        @JsonProperty(PREDECESSOR_COMMITS_PROPERTY)
083        private final List<CommitDescriptor> predecessorCommits = new ArrayList<>();
084
085        /** The timestamp as formatted string (used in JS). */
086        @JsonProperty("formattedCommit")
087        @SuppressWarnings("unused")
088        private final String formattedCommit = null;
089
090        @JsonCreator
091        public TestExecutionWithPartition(@JsonProperty(TEST_EXECUTION_PROPERTY) TestExecution testExecution,
092                        @JsonProperty(PARTITION_PROPERTY) String partition, @JsonProperty(COMMIT_PROPERTY) CommitDescriptor commit,
093                        @JsonProperty(PREDECESSOR_COMMITS_PROPERTY) Collection<CommitDescriptor> predecessorCommits) {
094                this(testExecution, partition, commit, predecessorCommits, false);
095        }
096
097        @VisibleForTesting
098        TestExecutionWithPartition(TestExecution testExecution, String partition, CommitDescriptor commit,
099                        Collection<CommitDescriptor> predecessorCommits, boolean isArtificialMergeTestExecution) {
100                Preconditions.checkState(
101                                predecessorCommits.stream().allMatch(predecessorCommit -> predecessorCommit.compareTo(commit) < 0),
102                                "Predecessor commits " + predecessorCommits + " can't come after commit " + commit);
103
104                this.testExecution = testExecution;
105                this.partition = partition;
106                this.commit = commit;
107                this.isArtificialMergeTestExecution = isArtificialMergeTestExecution;
108
109                // Ensures unique predecessor commits stable order for delta compression
110                predecessorCommits.stream().distinct().forEach(this.predecessorCommits::add);
111                Collections.sort(this.predecessorCommits);
112
113        }
114
115        /** @see #testExecution */
116        public TestExecution getTestExecution() {
117                return testExecution;
118        }
119
120        /** @see #partition */
121        public String getPartition() {
122                return partition;
123        }
124
125        /** @see #commit */
126        public CommitDescriptor getCommit() {
127                return commit;
128        }
129
130        /** @see #predecessorCommits */
131        public List<CommitDescriptor> getPredecessorCommits() {
132                return CollectionUtils.asUnmodifiable(predecessorCommits);
133        }
134
135        /**
136         * Returns true if this is a {@link TestExecutionWithPartition} which was
137         * created for a merge commit and doesn't represent a real test execution.
138         */
139        public boolean isArtificialMergeTestExecution() {
140                return isArtificialMergeTestExecution;
141        }
142
143        @Override
144        public boolean equals(Object o) {
145                if (this == o) {
146                        return true;
147                }
148                if (o == null || getClass() != o.getClass()) {
149                        return false;
150                }
151                TestExecutionWithPartition that = (TestExecutionWithPartition) o;
152                return isArtificialMergeTestExecution == that.isArtificialMergeTestExecution
153                                && Objects.equals(testExecution, that.testExecution) && Objects.equals(partition, that.partition)
154                                && Objects.equals(commit, that.commit) && Objects.equals(predecessorCommits, that.predecessorCommits)
155                                && Objects.equals(formattedCommit, that.formattedCommit);
156        }
157
158        @Override
159        public int hashCode() {
160                return Objects.hash(testExecution, partition, commit, isArtificialMergeTestExecution, predecessorCommits,
161                                formattedCommit);
162        }
163
164        @Override
165        public String toString() {
166                return ReflectionToStringBuilder.toString(this);
167        }
168
169        /**
170         * Merges a {@link Collection} of {@link TestExecutionWithPartition}s to a
171         * single one. Result is the the worst {@link ETestExecutionResult} with average
172         * duration in case there are is more than on {@link TestExecutionWithPartition}
173         * present.
174         */
175        public static TestExecutionWithPartition merge(CommitDescriptor mergeCommit,
176                        Collection<TestExecutionWithPartition> testExecutionsWithPartition) {
177                Preconditions.checkArgument(!testExecutionsWithPartition.isEmpty(),
178                                "Can't merge emtpy collection of test executions with partition.");
179
180                if (testExecutionsWithPartition.size() == 1) {
181                        return testExecutionsWithPartition.iterator().next();
182                }
183
184                String uniquePartition = testExecutionsWithPartition.iterator().next().getPartition();
185                List<TestExecution> testExecutions = new ArrayList<>();
186                Set<CommitDescriptor> predecessorCommits = new HashSet<>();
187
188                testExecutionsWithPartition.forEach(testExecutionWithPartition -> {
189                        String partition = testExecutionWithPartition.getPartition();
190
191                        predecessorCommits.add(testExecutionWithPartition.getCommit());
192                        testExecutions.add(testExecutionWithPartition.getTestExecution());
193
194                        CCSMAssert.isTrue(uniquePartition.equals(partition),
195                                        () -> "Can't merge test executions from separate partitions: " + uniquePartition + "," + partition);
196                });
197
198                TestExecution mergedTestExecution = TestExecution.merge(testExecutions);
199
200                return new TestExecutionWithPartition(mergedTestExecution, uniquePartition, mergeCommit, predecessorCommits,
201                                true);
202        }
203}