-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTestSplitMain.java
More file actions
173 lines (162 loc) · 7.12 KB
/
TestSplitMain.java
File metadata and controls
173 lines (162 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package de.donnerbart.split;
import ch.qos.logback.classic.Level;
import com.beust.jcommander.JCommander;
import de.donnerbart.split.model.Splits;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.Objects;
import java.util.Properties;
import java.util.function.Consumer;
import static de.donnerbart.split.util.FormatUtil.formatTime;
public class TestSplitMain {
private static final @NotNull Logger LOG = LoggerFactory.getLogger(TestSplitMain.class);
public static void main(final @Nullable String @NotNull [] args) throws Exception {
run(System::exit, args);
}
@VisibleForTesting
static @NotNull Splits run(final @NotNull Consumer<Integer> exitConsumer, final @Nullable String @NotNull [] args)
throws Exception {
final var arguments = new Arguments();
final var jCommander = JCommander.newBuilder().addObject(arguments).build();
jCommander.parse(args);
if (arguments.help) {
jCommander.usage();
exitConsumer.accept(0);
}
if (arguments.debug) {
final var root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.DEBUG);
}
final var workingDirectory =
Objects.requireNonNullElse(arguments.workingDirectory, Paths.get(System.getProperty("user.dir")))
.toAbsolutePath()
.normalize();
if (!validateArguments(arguments, workingDirectory)) {
exitConsumer.accept(1);
}
final var properties = readProperties("split-tests-java.properties");
LOG.info("split-tests-java {} (commit: {} on branch: {}) built on {}",
properties.getProperty("version", "unknown"),
properties.getProperty("git.commit.id.abbrev", "unknown"),
properties.getProperty("git.branch", "unknown"),
getBuiltTime(properties.getProperty("git.commit.time", "unknown")));
LOG.info("Split index {} (total: {})", arguments.splitIndex, arguments.splitTotal);
LOG.info("Working directory: {}", workingDirectory);
LOG.info("Glob: {}", arguments.glob);
if (arguments.excludeGlob != null) {
LOG.info("Exclude glob: {}", arguments.excludeGlob);
}
if (arguments.junitGlob != null) {
LOG.info("JUnit glob: {}", arguments.junitGlob);
}
LOG.info("Output format: {}", arguments.formatOption);
if (arguments.calculateOptimalTotalSplit) {
calculateOptimalTotalSplit(arguments, workingDirectory);
}
final var testSplit = new TestSplit(arguments.splitTotal,
arguments.glob,
arguments.excludeGlob,
arguments.junitGlob,
arguments.formatOption,
arguments.newTestTimeOption,
workingDirectory,
true,
arguments.debug,
System::exit);
final var splits = testSplit.run();
final var split = splits.get(arguments.splitIndex);
LOG.info("This test split has {} tests ({})", split.tests().size(), formatTime(split.totalRecordedTime()));
System.out.print(String.join(" ", splits.get(arguments.splitIndex).sortedTests()));
return splits;
}
@VisibleForTesting
static boolean validateArguments(final @NotNull Arguments arguments, final @NotNull Path workingDirectory) {
if (arguments.splitTotal < 1) {
LOG.error("--split-total must be greater than 0");
return false;
}
if (arguments.splitIndex > arguments.splitTotal - 1) {
LOG.error("--split-index must lesser than --split-total");
return false;
}
if (!Files.exists(workingDirectory)) {
LOG.error("Working directory does not exist: {}", arguments.workingDirectory);
return false;
}
return true;
}
@VisibleForTesting
static int calculateOptimalTotalSplit(final @NotNull Arguments arguments, final @NotNull Path workingDirectory)
throws Exception {
if (arguments.junitGlob == null) {
LOG.warn("The option --calculate-optimal-total-split requires --junit-glob");
return 0;
}
if (arguments.splitIndex != 0) {
LOG.debug("Skipping calculation of optimal test split (only done on the first index)");
return 0;
}
LOG.info("Calculating optimal test split");
var optimalSplit = 1;
var lastSlowestSplit = Double.MAX_VALUE;
while (true) {
final var testSplit = new TestSplit(optimalSplit,
arguments.glob,
arguments.excludeGlob,
arguments.junitGlob,
arguments.formatOption,
arguments.newTestTimeOption,
workingDirectory,
false,
false,
System::exit);
final var splits = testSplit.run();
final var slowestSplit = splits.getSlowest().totalRecordedTime();
if (Double.compare(slowestSplit, lastSlowestSplit) == 0) {
optimalSplit--;
LOG.info("The optimal --total-split value for this test suite is {}", optimalSplit);
if (optimalSplit != arguments.splitTotal) {
LOG.warn("The --split-total value of {} does not match the optimal split of {}",
arguments.splitTotal,
optimalSplit);
}
return optimalSplit;
}
LOG.debug("The slowest split with {} splits takes {}", optimalSplit, formatTime(slowestSplit));
if (optimalSplit++ >= arguments.maxOptimalTotalSplitCalculations) {
LOG.warn(
"The option --max-optimal-total-split-calculations of {} is too low to calculate the optimal test split",
arguments.maxOptimalTotalSplitCalculations);
return 0;
}
lastSlowestSplit = slowestSplit;
}
}
@VisibleForTesting
static @NotNull Properties readProperties(final @NotNull String resourceFile) throws Exception {
final var properties = new Properties();
try (final var inputStream = TestSplitMain.class.getClassLoader().getResourceAsStream(resourceFile)) {
if (inputStream != null) {
properties.load(inputStream);
}
}
return properties;
}
@VisibleForTesting
static @NotNull Date getBuiltTime(final @NotNull String dateString) {
try {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateString);
} catch (final Exception e) {
return Date.from(Instant.EPOCH);
}
}
}