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}