From f636e9ff084adab8e33f272bda6d24334fcaa3a6 Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 13 Dec 2024 04:57:41 +0100 Subject: [PATCH] Registry and reworked builder pattern --- .../test_impacted/engine/TestDataWriter.java | 45 ---- .../engine/TestEngineRegistry.java | 65 ----- .../engine/options/TestEngineOptionUtils.java | 91 ------- .../engine/InternalImpactedTestEngine.kt | 1 - .../test_impacted/engine/TestDataWriter.kt | 39 +++ .../engine/TestEngineRegistry.kt | 45 ++++ .../engine/executor/ImpactedTestsProvider.kt | 7 +- .../engine/executor/ImpactedTestsSorter.kt | 2 +- .../engine/options/TestEngineOptionUtils.kt | 67 +++++ .../engine/options/TestEngineOptions.kt | 235 +++++------------- .../ITestDescriptorResolver.kt | 4 +- .../engine/ImpactedTestEngineTestBase.kt | 2 +- 12 files changed, 227 insertions(+), 376 deletions(-) delete mode 100644 impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestDataWriter.java delete mode 100644 impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestEngineRegistry.java delete mode 100644 impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.java create mode 100644 impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestDataWriter.kt create mode 100644 impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestEngineRegistry.kt create mode 100644 impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.kt diff --git a/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestDataWriter.java b/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestDataWriter.java deleted file mode 100644 index ca2b076f1..000000000 --- a/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestDataWriter.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.teamscale.test_impacted.engine; - -import com.teamscale.client.TestDetails; -import com.teamscale.report.ReportUtils; -import com.teamscale.report.testwise.model.TestExecution; -import com.teamscale.test_impacted.commons.LoggerUtils; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** Class for writing test data to a report directory. */ -public class TestDataWriter { - - private static final Logger LOGGER = LoggerUtils.getLogger(TestDataWriter.class); - - private final File reportDirectory; - - public TestDataWriter(File reportDirectory) { - this.reportDirectory = reportDirectory; - } - - /** Writes the given test executions to a report file. */ - void dumpTestExecutions(List testExecutions) { - File file = new File(reportDirectory, "test-execution.json"); - try { - ReportUtils.writeTestExecutionReport(file, testExecutions); - } catch (IOException e) { - LOGGER.log(Level.SEVERE, e, () -> "Error while writing report to file: " + file); - } - } - - /** Writes the given test details to a report file. */ - void dumpTestDetails(List testDetails) { - File file = new File(reportDirectory, "test-list.json"); - try { - ReportUtils.writeTestListReport(file, new ArrayList<>(testDetails)); - } catch (IOException e) { - LOGGER.log(Level.SEVERE, e, () -> "Error while writing report to file: " + file); - } - } -} diff --git a/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestEngineRegistry.java b/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestEngineRegistry.java deleted file mode 100644 index 3f4a25ff2..000000000 --- a/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/TestEngineRegistry.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.teamscale.test_impacted.engine; - -import org.junit.platform.commons.util.ClassLoaderUtils; -import org.junit.platform.engine.TestEngine; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.Set; -import java.util.stream.Collectors; - -import static java.util.Collections.unmodifiableMap; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; - -/** The test engine registry containing all */ -public class TestEngineRegistry implements Iterable { - - private final Map testEnginesById; - - public TestEngineRegistry(Set includedTestEngineIds, Set excludedTestEngineIds) { - List otherTestEngines = loadOtherTestEngines(excludedTestEngineIds); - - // If there are no test engines set we don't need to filter but simply use all other test engines. - if (!includedTestEngineIds.isEmpty()) { - otherTestEngines = otherTestEngines.stream() - .filter(testEngine -> includedTestEngineIds.contains(testEngine.getId())).collect( - Collectors.toList()); - } - - testEnginesById = unmodifiableMap(otherTestEngines.stream().collect(toMap(TestEngine::getId, identity()))); - } - - /** - * Uses the {@link ServiceLoader} to discover all {@link TestEngine}s but the {@link ImpactedTestEngine} and the - * excluded test engines. - */ - private List loadOtherTestEngines(Set excludedTestEngineIds) { - List testEngines = new ArrayList<>(); - - for (TestEngine testEngine : ServiceLoader.load(TestEngine.class, ClassLoaderUtils.getDefaultClassLoader())) { - if (!ImpactedTestEngine.ENGINE_ID.equals(testEngine.getId()) && !excludedTestEngineIds.contains( - testEngine.getId())) { - testEngines.add(testEngine); - } - } - - return testEngines; - } - - /** Returns the {@link TestEngine} for the engine id or null if none is present. */ - public TestEngine getTestEngine(String engineId) { - return testEnginesById.get(engineId); - } - - @Override - public Iterator iterator() { - List testEngines = new ArrayList<>(testEnginesById.values()); - testEngines.sort(Comparator.comparing(TestEngine::getId)); - return testEngines.iterator(); - } -} diff --git a/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.java b/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.java deleted file mode 100644 index 7411b3516..000000000 --- a/impacted-test-engine/src/main/java/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.teamscale.test_impacted.engine.options; - -import com.teamscale.client.CommitDescriptor; -import com.teamscale.client.StringUtils; -import org.junit.platform.engine.ConfigurationParameters; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Properties; -import java.util.function.Function; - -/** Utility class for {@link TestEngineOptions}. */ -public class TestEngineOptionUtils { - - /** Returns the {@link TestEngineOptions} configured in the {@link Properties}. */ - public static TestEngineOptions getEngineOptions(ConfigurationParameters configurationParameters) { - PrefixingPropertyReader propertyReader = new PrefixingPropertyReader("teamscale.test.impacted.", - configurationParameters); - ServerOptions serverOptions = null; - Boolean runImpacted = propertyReader.getBoolean("runImpacted", true); - if (runImpacted) { - serverOptions = ServerOptions.builder() - .url(propertyReader.getString("server.url")) - .project(propertyReader.getString("server.project")) - .userName(propertyReader.getString("server.userName")) - .userAccessToken(propertyReader.getString("server.userAccessToken")) - .build(); - } - - return TestEngineOptions.builder() - .serverOptions(serverOptions) - .partition(propertyReader.getString("partition")) - .runImpacted(runImpacted) - .runAllTests(propertyReader.getBoolean("runAllTests", false)) - .includeAddedTests(propertyReader.getBoolean("includeAddedTests", true)) - .includeFailedAndSkipped(propertyReader.getBoolean("includeFailedAndSkipped", true)) - .endCommit(propertyReader.getCommitDescriptor("endCommit")) - .endRevision(propertyReader.getString("endRevision")) - .baseline(propertyReader.getString("baseline")) - .baselineRevision(propertyReader.getString("baselineRevision")) - .repository(propertyReader.getString("repository")) - .agentUrls(propertyReader.getStringList("agentsUrls")) - .includedTestEngineIds(propertyReader.getStringList("includedEngines")) - .excludedTestEngineIds(propertyReader.getStringList("excludedEngines")) - .reportDirectory(propertyReader.getString("reportDirectory")) - .build(); - } - - private static class PrefixingPropertyReader { - - private final ConfigurationParameters configurationParameters; - - private final String prefix; - - private PrefixingPropertyReader(String prefix, ConfigurationParameters configurationParameters) { - this.prefix = prefix; - this.configurationParameters = configurationParameters; - } - - private T getOrNull(String propertyName, Function mapper) { - return get(propertyName, mapper, null); - } - - private T get(String propertyName, Function mapper, T defaultValue) { - return configurationParameters.get(prefix + propertyName).map(mapper).orElse(defaultValue); - } - - private String getString(String propertyName) { - return getOrNull(propertyName, Function.identity()); - } - - private Boolean getBoolean(String propertyName, boolean defaultValue) { - return get(propertyName, Boolean::valueOf, defaultValue); - } - - private CommitDescriptor getCommitDescriptor(String propertyName) { - return getOrNull(propertyName, CommitDescriptor::parse); - } - - private List getStringList(String propertyName) { - return get(propertyName, listAsString -> { - if (StringUtils.isEmpty(listAsString)) { - return Collections.emptyList(); - } - - return Arrays.asList(listAsString.split(",")); - }, Collections.emptyList()); - } - } -} diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/InternalImpactedTestEngine.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/InternalImpactedTestEngine.kt index e06e16bc3..21c22b7a8 100644 --- a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/InternalImpactedTestEngine.kt +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/InternalImpactedTestEngine.kt @@ -1,7 +1,6 @@ package com.teamscale.test_impacted.engine import com.teamscale.test_impacted.commons.LoggerUtils.createLogger -import com.teamscale.test_impacted.commons.LoggerUtils.getLogger import com.teamscale.test_impacted.engine.executor.TestwiseCoverageCollectingExecutionListener import com.teamscale.test_impacted.test_descriptor.TestDescriptorResolverRegistry.getTestDescriptorResolver import com.teamscale.test_impacted.test_descriptor.TestDescriptorUtils.getAvailableTests diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestDataWriter.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestDataWriter.kt new file mode 100644 index 000000000..b716fbaeb --- /dev/null +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestDataWriter.kt @@ -0,0 +1,39 @@ +package com.teamscale.test_impacted.engine + +import com.teamscale.client.TestDetails +import com.teamscale.report.ReportUtils.writeTestExecutionReport +import com.teamscale.report.ReportUtils.writeTestListReport +import com.teamscale.report.testwise.model.TestExecution +import com.teamscale.test_impacted.commons.LoggerUtils.createLogger +import com.teamscale.test_impacted.commons.LoggerUtils.getLogger +import java.io.File +import java.io.IOException +import java.util.logging.Level +import java.util.logging.Logger + +/** Class for writing test data to a report directory. */ +open class TestDataWriter(private val reportDirectory: File) { + /** Writes the given test executions to a report file. */ + fun dumpTestExecutions(testExecutions: List) { + val file = File(reportDirectory, "test-execution.json") + try { + writeTestExecutionReport(file, testExecutions) + } catch (e: IOException) { + LOGGER.log(Level.SEVERE, e) { "Error while writing report to file: $file" } + } + } + + /** Writes the given test details to a report file. */ + fun dumpTestDetails(testDetails: List) { + val file = File(reportDirectory, "test-list.json") + try { + writeTestListReport(file, ArrayList(testDetails)) + } catch (e: IOException) { + LOGGER.log(Level.SEVERE, e) { "Error while writing report to file: $file" } + } + } + + companion object { + private val LOGGER = createLogger() + } +} diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestEngineRegistry.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestEngineRegistry.kt new file mode 100644 index 000000000..1c8e11d34 --- /dev/null +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/TestEngineRegistry.kt @@ -0,0 +1,45 @@ +package com.teamscale.test_impacted.engine + +import org.junit.platform.commons.util.ClassLoaderUtils +import org.junit.platform.engine.TestEngine +import java.util.* +import java.util.function.Function +import java.util.stream.Collectors + +/** The test engine registry containing all */ +open class TestEngineRegistry( + includedTestEngineIds: Set, + excludedTestEngineIds: Set +) : Iterable { + private val testEnginesById: Map + + init { + var otherTestEngines = loadOtherTestEngines(excludedTestEngineIds) + + // If there are no test engines set we don't need to filter but simply use all other test engines. + if (includedTestEngineIds.isNotEmpty()) { + otherTestEngines = otherTestEngines.filter { testEngine -> + includedTestEngineIds.contains(testEngine.id) + } + } + + testEnginesById = otherTestEngines.associateBy { it.id } + } + + /** + * Uses the [ServiceLoader] to discover all [TestEngine]s but the [ImpactedTestEngine] and the + * excluded test engines. + */ + private fun loadOtherTestEngines(excludedTestEngineIds: Set) = + ServiceLoader.load( + TestEngine::class.java, ClassLoaderUtils.getDefaultClassLoader() + ).filter { + ImpactedTestEngine.ENGINE_ID != it.id && !excludedTestEngineIds.contains(it.id) + } + + /** Returns the [TestEngine] for the engine id or null if none is present. */ + fun getTestEngine(engineId: String) = testEnginesById[engineId] + + override fun iterator() = + testEnginesById.values.sortedBy { it.id }.iterator() +} diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsProvider.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsProvider.kt index 087b8c151..b01d94d90 100644 --- a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsProvider.kt +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsProvider.kt @@ -20,7 +20,7 @@ open class ImpactedTestsProvider( private val endCommit: CommitDescriptor, private val endRevision: String, private val repository: String, - @JvmField val partition: String, + val partition: String, private val includeNonImpacted: Boolean, private val includeAddedTests: Boolean, private val includeFailedAndSkipped: Boolean @@ -36,8 +36,7 @@ open class ImpactedTestsProvider( val response = client .getImpactedTests( availableTestDetails, baseline, baselineRevision, endCommit, endRevision, repository, - listOf(partition), - includeNonImpacted, includeAddedTests, includeFailedAndSkipped + listOf(partition), includeNonImpacted, includeAddedTests, includeFailedAndSkipped ) if (response.isSuccessful) { @@ -75,7 +74,7 @@ open class ImpactedTestsProvider( availableTestDetails: List ): Boolean { val returnedTests = testClusters.stream().mapToLong { - it.tests!!.size.toLong() + it.tests?.size?.toLong() ?: 0 }.sum() if (!includeNonImpacted) { logger.info { "Received $returnedTests impacted tests of ${availableTestDetails.size} available tests." } diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsSorter.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsSorter.kt index 17787859f..b1fffdeaf 100644 --- a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsSorter.kt +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/executor/ImpactedTestsSorter.kt @@ -16,7 +16,7 @@ class ImpactedTestsSorter(private val impactedTestsProvider: ImpactedTestsProvid val testClusters = impactedTestsProvider.getImpactedTestsFromTeamscale(availableTests.testList) - if (testClusters == null) { + if (testClusters.isEmpty()) { ImpactedTestEngine.LOGGER.fine { "Falling back to execute all!" } return } diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.kt new file mode 100644 index 000000000..c6957a0df --- /dev/null +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptionUtils.kt @@ -0,0 +1,67 @@ +package com.teamscale.test_impacted.engine.options + +import com.teamscale.client.CommitDescriptor +import com.teamscale.client.StringUtils.isEmpty +import org.junit.platform.engine.ConfigurationParameters +import java.util.* +import java.util.function.Function + +object TestEngineOptionUtils { + + private const val PREFIX = "teamscale.test.impacted." + + /** Returns the [TestEngineOptions] configured in the [Properties]. */ + fun getEngineOptions(configurationParameters: ConfigurationParameters): TestEngineOptions { + val propertyReader = PrefixingPropertyReader(PREFIX, configurationParameters) + val shouldRunImpactedTests = propertyReader.getBoolean("runImpacted", true) + + val serverOptions = if (shouldRunImpactedTests) { + createServerOptions(propertyReader) + } else null + + return TestEngineOptions.builder() + .serverOptions(serverOptions) + .partition(propertyReader.getString("partition")) + .runImpacted(shouldRunImpactedTests) + .runAllTests(propertyReader.getBoolean("runAllTests", false)) + .includeAddedTests(propertyReader.getBoolean("includeAddedTests", true)) + .includeFailedAndSkipped(propertyReader.getBoolean("includeFailedAndSkipped", true)) + .endCommit(propertyReader.getCommitDescriptor("endCommit")) + .endRevision(propertyReader.getString("endRevision")) + .baseline(propertyReader.getString("baseline")) + .baselineRevision(propertyReader.getString("baselineRevision")) + .repository(propertyReader.getString("repository")) + .testCoverageAgentUrls(propertyReader.getStringList("agentsUrls")) + .includedTestEngineIds(propertyReader.getStringList("includedEngines")) + .excludedTestEngineIds(propertyReader.getStringList("excludedEngines")) + .reportDirectory(propertyReader.getString("reportDirectory")) + .build() + } + + private fun createServerOptions(propertyReader: PrefixingPropertyReader) = + ServerOptions.builder() + .url(propertyReader.getString("server.url")) + .project(propertyReader.getString("server.project")) + .userName(propertyReader.getString("server.userName")) + .userAccessToken(propertyReader.getString("server.userAccessToken")) + .build() + + private class PrefixingPropertyReader( + private val prefix: String, + private val configurationParameters: ConfigurationParameters + ) { + fun getString(propertyName: String): String = + configurationParameters[prefix + propertyName].orElse("") + + fun getBoolean(propertyName: String, defaultValue: Boolean): Boolean = + configurationParameters[prefix + propertyName].map { it.toBoolean() }.orElse(defaultValue) + + fun getCommitDescriptor(propertyName: String): CommitDescriptor? = + configurationParameters[prefix + propertyName].map { CommitDescriptor.parse(it) }.orElse(null) + + fun getStringList(propertyName: String): List = + configurationParameters[prefix + propertyName] + .map { it.split(",").filterNot(String::isEmpty) } + .orElse(emptyList()) + } +} diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptions.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptions.kt index ceffec536..b75c39b71 100644 --- a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptions.kt +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/engine/options/TestEngineOptions.kt @@ -9,92 +9,52 @@ import com.teamscale.test_impacted.engine.executor.* import com.teamscale.tia.client.ITestwiseCoverageAgentApi import okhttp3.HttpUrl import java.io.File -import java.io.IOException -import java.nio.file.Files +import kotlin.io.path.createDirectories /** Represents options for the [com.teamscale.test_impacted.engine.ImpactedTestEngine]. */ class TestEngineOptions { - /** The server options. May not be null. */ - private var serverOptions: ServerOptions? = null - - /** The partition to upload test details to and get impacted tests from. If null, all partitions are used. - * @see [partition] - */ - var partition: String? = null - private set - /** Executes all tests, not only impacted ones if set. Defaults to false. - * @see [runAllTests] - */ - private var runAllTests = false + companion object { + private const val DEFAULT_RUN_IMPACTED = true + private const val DEFAULT_INCLUDE_ADDED_TESTS = true + private const val DEFAULT_INCLUDE_FAILED_AND_SKIPPED = true - /** Executes only impacted tests, not all ones if set. Defaults to true. */ - private var runImpacted = true + /** Returns the builder for [TestEngineOptions]. */ + @JvmStatic + fun builder() = Builder() + } - /** Includes added tests in the list of tests to execute. Defaults to true - * @see [includeAddedTests] - */ - private var includeAddedTests = true + private var serverOptions: ServerOptions? = null + var partition: String? = null + private var repository: String? = null - /** Includes failed and skipped tests in the list of tests to execute. Defaults to true - * @see [includeFailedAndSkipped] - */ - private var includeFailedAndSkipped = true + private var runAllTests = false + private var runImpacted = DEFAULT_RUN_IMPACTED + private var includeAddedTests = DEFAULT_INCLUDE_ADDED_TESTS + private var includeFailedAndSkipped = DEFAULT_INCLUDE_FAILED_AND_SKIPPED - /** - * The baseline. Only code changes after the baseline are considered for determining impacted tests. May be null to - * indicate no baseline. - */ private var baseline: String? = null - - /** - * Can be used instead of [baseline] by using a revision (e.g. git SHA1) instead of a branch and timestamp. - */ private var baselineRevision: String? = null - - /** The end commit used for TIA and for uploading the coverage. May not be null. */ private var endCommit: CommitDescriptor? = null - - /** - * Can be used instead of [endCommit] by using a revision (e.g. git SHA1) instead of a branch and timestamp. - */ private var endRevision: String? = null - /** - * The repository id in your Teamscale project which Teamscale should use to look up the revision, if given. - * Null or empty will lead to a lookup in all repositories in the Teamscale project. - */ - private var repository: String? = null - - /** The URLs (including port) at which the agents listen to. Maybe empty but not null. */ private var testwiseCoverageAgentApis = emptyList() - - /** The test engine ids of all [org.junit.platform.engine.TestEngine]s to use. - * If empty all available [org.junit.platform.engine.TestEngine]s are used. */ private var includedTestEngineIds = emptySet() - - /** The test engine ids of all [org.junit.platform.engine.TestEngine]s to exclude. */ private var excludedTestEngineIds = emptySet() - /** The directory used to store test-wise coverage reports. Must be a writeable directory. */ private var reportDirectory: File? = null + /** Creates the test engine configuration */ fun createTestEngineConfiguration(): ImpactedTestEngineConfiguration { val testSorter = createTestSorter() val teamscaleAgentNotifier = createTeamscaleAgentNotifier() val testEngineRegistry = TestEngineRegistry(includedTestEngineIds, excludedTestEngineIds) - val testDataWriter = TestDataWriter(reportDirectory) - + val testDataWriter = TestDataWriter(reportDirectory!!) return ImpactedTestEngineConfiguration(testDataWriter, testEngineRegistry, testSorter, teamscaleAgentNotifier) } private fun createTestSorter(): ITestSorter { - if (!runImpacted) { - return NOPTestSorter() - } - - val testsProvider = createImpactedTestsProvider() - return ImpactedTestsSorter(testsProvider) + return if (!runImpacted) NOPTestSorter() else ImpactedTestsSorter(createImpactedTestsProvider()) } private fun createImpactedTestsProvider(): ImpactedTestsProvider { @@ -114,120 +74,63 @@ class TestEngineOptions { private fun createTeamscaleAgentNotifier() = TeamscaleAgentNotifier(testwiseCoverageAgentApis, runImpacted && !runAllTests) - /** The builder for [TestEngineOptions]. */ + /** Builder for [TestEngineOptions]. */ class Builder { - private val testEngineOptions = TestEngineOptions() - - fun serverOptions(serverOptions: ServerOptions?): Builder { - testEngineOptions.serverOptions = serverOptions - return this - } - - fun partition(partition: String?): Builder { - testEngineOptions.partition = partition - return this - } - - fun runImpacted(runImpacted: Boolean): Builder { - testEngineOptions.runImpacted = runImpacted - return this - } - - fun runAllTests(runAllTests: Boolean): Builder { - testEngineOptions.runAllTests = runAllTests - return this - } - - fun includeAddedTests(includeAddedTests: Boolean): Builder { - testEngineOptions.includeAddedTests = includeAddedTests - return this - } - - fun includeFailedAndSkipped(includeFailedAndSkipped: Boolean): Builder { - testEngineOptions.includeFailedAndSkipped = includeFailedAndSkipped - return this - } - - fun endCommit(endCommit: CommitDescriptor?): Builder { - testEngineOptions.endCommit = endCommit - return this - } - - fun endRevision(endRevision: String?): Builder { - testEngineOptions.endRevision = endRevision - return this - } - - fun repository(repository: String?): Builder { - testEngineOptions.repository = repository - return this - } - - fun baseline(baseline: String?): Builder { - testEngineOptions.baseline = baseline - return this - } - - fun baselineRevision(baselineRevision: String?): Builder { - testEngineOptions.baselineRevision = baselineRevision - return this - } - - fun agentUrls(agentUrls: List): Builder { - testEngineOptions.testwiseCoverageAgentApis = agentUrls - .map { HttpUrl.parse(it) } - .mapNotNull { - if (it != null) { - ITestwiseCoverageAgentApi.createService(it) - } else null - } - return this - } - - fun includedTestEngineIds(testEngineIds: List): Builder { - testEngineOptions.includedTestEngineIds = HashSet(testEngineIds) - return this - } - - fun excludedTestEngineIds(testEngineIds: List): Builder { - testEngineOptions.excludedTestEngineIds = HashSet(testEngineIds) - return this - } - - fun reportDirectory(reportDirectory: String?): Builder { - reportDirectory ?: return this - testEngineOptions.reportDirectory = File(reportDirectory) - return this + private val options = TestEngineOptions() + + fun serverOptions(config: ServerOptions?): Builder = + apply { options.serverOptions = config } + fun partition(partition: String?): Builder = + apply { options.partition = partition } + fun repository(repository: String?): Builder = + apply { options.repository = repository } + fun runImpacted(flag: Boolean): Builder = + apply { options.runImpacted = flag } + fun runAllTests(flag: Boolean): Builder = + apply { options.runAllTests = flag } + fun includeAddedTests(flag: Boolean): Builder = + apply { options.includeAddedTests = flag } + fun includeFailedAndSkipped(flag: Boolean): Builder = + apply { options.includeFailedAndSkipped = flag } + fun baseline(baseline: String?): Builder = + apply { options.baseline = baseline } + fun baselineRevision(revision: String?): Builder = + apply { options.baselineRevision = revision } + fun endCommit(commit: CommitDescriptor?): Builder = + apply { options.endCommit = commit } + fun endRevision(revision: String?): Builder = + apply { options.endRevision = revision } + fun includedTestEngineIds(ids: List): Builder = + apply { options.includedTestEngineIds = ids.toSet() } + fun excludedTestEngineIds(ids: List): Builder = + apply { options.excludedTestEngineIds = ids.toSet() } + fun reportDirectory(path: String?): Builder = + apply { path?.let { options.reportDirectory = File(it) } } + + fun testCoverageAgentUrls(urls: List): Builder = apply { + options.testwiseCoverageAgentApis = urls.mapNotNull { + HttpUrl.parse(it)?.let(ITestwiseCoverageAgentApi::createService) + } } - /** Checks field conditions and returns the built [TestEngineOptions]. */ + /** Validates and builds the [TestEngineOptions]. */ fun build(): TestEngineOptions { - if (testEngineOptions.endCommit == null && testEngineOptions.endRevision == null) { - throw AssertionError("End commit must be set via endCommit or endRevision.") - } - if (testEngineOptions.runImpacted) { - checkNotNull(testEngineOptions.serverOptions) { "Server options must be set." } - } - checkNotNull(testEngineOptions.testwiseCoverageAgentApis) { "Agent urls may be empty but not null." } - checkNotNull(testEngineOptions.reportDirectory) { "Report directory must be set." } - if (!testEngineOptions.reportDirectory!!.isDirectory || !testEngineOptions.reportDirectory!!.canWrite()) { - try { - Files.createDirectories(testEngineOptions.reportDirectory!!.toPath()) - } catch (e: IOException) { - throw AssertionError( - "Report directory could not be created: ${testEngineOptions.reportDirectory}", e - ) - } - } - return testEngineOptions + options.validateBuildPreconditions() + return options } } - companion object { - /** Returns the builder for [TestEngineOptions]. */ - @JvmStatic - fun builder(): Builder { - return Builder() + /** Helper for build validation */ + private fun validateBuildPreconditions() { + require(endCommit != null || endRevision != null) { "End commit must be set via endCommit or endRevision." } + if (runImpacted) { + requireNotNull(serverOptions) { "Server options must be set." } + } + requireNotNull(reportDirectory) { "Report directory must be set." } + reportDirectory?.let { + if (!it.isDirectory || !it.canWrite()) { + it.toPath().createDirectories() + } } } } diff --git a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/test_descriptor/ITestDescriptorResolver.kt b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/test_descriptor/ITestDescriptorResolver.kt index 67e1b772b..9df347710 100644 --- a/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/test_descriptor/ITestDescriptorResolver.kt +++ b/impacted-test-engine/src/main/kotlin/com/teamscale/test_impacted/test_descriptor/ITestDescriptorResolver.kt @@ -12,8 +12,8 @@ interface ITestDescriptorResolver { fun getClusterId(descriptor: TestDescriptor): Optional /** - * Returns the [org.junit.platform.engine.TestEngine.getId] of the [org.junit.platform.engine.TestEngine] to use this [ITestDescriptorResolver] - * for. + * Returns the [org.junit.platform.engine.TestEngine.getId] of the [org.junit.platform.engine.TestEngine] + * to use this [ITestDescriptorResolver] for. */ val engineId: String diff --git a/impacted-test-engine/src/test/kotlin/com/teamscale/test_impacted/engine/ImpactedTestEngineTestBase.kt b/impacted-test-engine/src/test/kotlin/com/teamscale/test_impacted/engine/ImpactedTestEngineTestBase.kt index b46babe4b..cf2373d1c 100644 --- a/impacted-test-engine/src/test/kotlin/com/teamscale/test_impacted/engine/ImpactedTestEngineTestBase.kt +++ b/impacted-test-engine/src/test/kotlin/com/teamscale/test_impacted/engine/ImpactedTestEngineTestBase.kt @@ -62,7 +62,7 @@ abstract class ImpactedTestEngineTestBase { whenever(testEngineRegistry.getTestEngine(eq(engine.id))) .thenReturn(engine) } - whenever>(testEngineRegistry.iterator()).thenReturn(engines.iterator()) + whenever(testEngineRegistry.iterator()).thenReturn(engines.iterator()) return InternalImpactedTestEngine( ImpactedTestEngineConfiguration(