diff --git a/settings.gradle b/settings.gradle index 90f6d15..278dd21 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,3 +3,5 @@ include 'skippy-gradle' include 'skippy-maven' include 'skippy-junit4' include 'skippy-junit5' +include ':skippy-extensions:skippy-repository-filesystem' +include ':skippy-extensions:skippy-repository-regression-suite' diff --git a/skippy-core/.skippy/config.json b/skippy-core/.skippy/config.json deleted file mode 100644 index a5a3bd8..0000000 --- a/skippy-core/.skippy/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "projectDirectory": ".", - "saveExecutionData": "true" -} diff --git a/skippy-core/src/main/java/io/skippy/core/AnalyzedTest.java b/skippy-core/src/main/java/io/skippy/core/AnalyzedTest.java index 4cba950..8c17013 100644 --- a/skippy-core/src/main/java/io/skippy/core/AnalyzedTest.java +++ b/skippy-core/src/main/java/io/skippy/core/AnalyzedTest.java @@ -31,7 +31,7 @@ * "class": 0, * "result": "PASSED", * "coveredClasses": [0, 1], - * "executionId": "C57F877F6F9BF164" + * "executionId": "C57F877F...." * } * * @@ -180,16 +180,18 @@ public int compareTo(AnalyzedTest other) { } @Override - public boolean equals(Object other) { - if (other instanceof AnalyzedTest a) { - return Objects.equals(testClassId, a.testClassId); - } - return false; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AnalyzedTest that = (AnalyzedTest) o; + return testClassId == that.testClassId && + result == that.result && + Objects.equals(coveredClassesIds, that.coveredClassesIds) && + Objects.equals(executionId, that.executionId); } @Override public int hashCode() { - return Objects.hash(testClassId); + return Objects.hash(testClassId, result, coveredClassesIds, executionId); } - } \ No newline at end of file diff --git a/skippy-core/src/main/java/io/skippy/core/ClassFile.java b/skippy-core/src/main/java/io/skippy/core/ClassFile.java index 0f38073..90040b6 100644 --- a/skippy-core/src/main/java/io/skippy/core/ClassFile.java +++ b/skippy-core/src/main/java/io/skippy/core/ClassFile.java @@ -157,24 +157,26 @@ public String toJson() { @Override public int compareTo(ClassFile other) { return comparing(ClassFile::getClassName) + .thenComparing(ClassFile::getPath) .thenComparing(ClassFile::getOutputFolder) .compare(this, other); } @Override - public boolean equals(Object other) { - if (other instanceof ClassFile c) { - return Objects.equals(getClassName() + getOutputFolder(), c.getClassName() + c.getOutputFolder()); - } - return false; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClassFile classFile = (ClassFile) o; + return Objects.equals(className, classFile.className) && + Objects.equals(path, classFile.path) && + Objects.equals(outputFolder, classFile.outputFolder); } @Override public int hashCode() { - return Objects.hash(getClassName(), getOutputFolder()); + return Objects.hash(className, path, outputFolder); } - boolean hasChanged() { return ! hash.equals(HashUtil.debugAgnosticHash(outputFolder.resolve(path))); } diff --git a/skippy-core/src/main/java/io/skippy/core/ClassFileContainer.java b/skippy-core/src/main/java/io/skippy/core/ClassFileContainer.java index 98c4ce7..b5976a9 100644 --- a/skippy-core/src/main/java/io/skippy/core/ClassFileContainer.java +++ b/skippy-core/src/main/java/io/skippy/core/ClassFileContainer.java @@ -183,4 +183,16 @@ ClassFileContainer merge(ClassFileContainer other) { return ClassFileContainer.from(new ArrayList<>(result)); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClassFileContainer that = (ClassFileContainer) o; + return Objects.equals(classFilesById, that.classFilesById); + } + + @Override + public int hashCode() { + return Objects.hash(classFilesById); + } } diff --git a/skippy-core/src/main/java/io/skippy/core/ClassNameAndPrediction.java b/skippy-core/src/main/java/io/skippy/core/ClassNameAndPrediction.java index ebe0c09..43d0bf6 100644 --- a/skippy-core/src/main/java/io/skippy/core/ClassNameAndPrediction.java +++ b/skippy-core/src/main/java/io/skippy/core/ClassNameAndPrediction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.skippy.core; record ClassNameAndPrediction(String className, Prediction prediction) {} diff --git a/skippy-core/src/main/java/io/skippy/core/DefaultRepositoryExtension.java b/skippy-core/src/main/java/io/skippy/core/DefaultRepositoryExtension.java new file mode 100644 index 0000000..81bb4be --- /dev/null +++ b/skippy-core/src/main/java/io/skippy/core/DefaultRepositoryExtension.java @@ -0,0 +1,177 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.skippy.core; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Optional; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import static java.nio.file.Files.*; + +/** + * Default {@link SkippyRepositoryExtension} implementation that + *
- * package com.example; - * - * public class S3SkippyRepository implements SkippyRepositoryExtension { - * - * {@literal @}Override - * public Optional<TestImpactAnalysis> readTestImpactAnalysis() { - * // read from S3 - * } - * - * {@literal @}Override - * public void saveTestImpactAnalysis(TestImpactAnalysis testImpactAnalysis) { - * // save in S3 - * } - * - * {@literal @}Override - * public Optional<byte[]> readJacocoExecutionData(String executionId) { - * // read from S3 - * } - * - * {@literal @}Override - * public String saveJacocoExecutionData(byte[] jacocoExecutionData) { - * // save in S3 - * } - * - * } - *- * The custom implementation has to be registered with Skippy's build plugins. - *
- * skippy { - * ... - * repository = 'com.example.S3SkippyRepository' - * } - ** * @author Florian McKee */ -public final class SkippyRepository implements SkippyRepositoryExtension { +public final class SkippyRepository { private final Path projectDir; private final Path buildDir; - private final SkippyConfiguration skippyConfiguration; - private final Optional
+ * skippy { + * ... + * repository = 'com.example.S3SkippyRepository' + * } + ** * @author Florian McKee */ public interface SkippyRepositoryExtension { /** - * Returns the {@link TestImpactAnalysis} instance for the current build or an empty {@link Optional} is none - * was found. + * Retrieves a {@link TestImpactAnalysis} by {@code id}. * - * @return the {@link TestImpactAnalysis} instance for the current build or an empty {@link Optional} is none - * was found + * @param id must not be null + * @return the {@link TestImpactAnalysis} with the given {@code id} or {@link Optional#empty()} if none found */ - Optional
@@ -33,11 +35,18 @@ public interface SkippyPluginExtension { /** - * Returns the property to enable / disable capture of per-test JaCoCo execution data. + * Returns the property to enable / disable coverage generation for skipped tests. + * + * @return the property to enable / disable coverage generation for skipped tests + */ + PropertygetCoverageForSkippedTests(); + + /** + * Returns the property to register a custom {@link io.skippy.core.SkippyRepositoryExtension}. * - * @return the property to enable / disable capture of per-test JaCoCo execution data + * @return the property to register a custom {@link io.skippy.core.SkippyRepositoryExtension} */ - Property getSaveExecutionData(); + Property getRepository(); /** * Converts the extension data into a {@link SkippyConfiguration} @@ -45,6 +54,6 @@ public interface SkippyPluginExtension { * @return a {@link SkippyConfiguration} derived from the extension data */ default SkippyConfiguration toSkippyConfiguration() { - return new SkippyConfiguration(getSaveExecutionData().getOrElse(false)); + return new SkippyConfiguration(getCoverageForSkippedTests().getOrElse(false), Optional.ofNullable(getRepository().getOrNull())); } } \ No newline at end of file diff --git a/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildFinishedMojo.java b/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildFinishedMojo.java index b40dd42..f3db5d1 100644 --- a/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildFinishedMojo.java +++ b/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildFinishedMojo.java @@ -16,7 +16,7 @@ package io.skippy.maven; -import io.skippy.core.SkippyApi; +import io.skippy.core.SkippyBuildApi; import io.skippy.core.SkippyConfiguration; import io.skippy.core.SkippyRepository; import org.apache.maven.execution.MavenSession; @@ -28,6 +28,7 @@ import org.apache.maven.project.MavenProject; import java.nio.file.Path; +import java.util.Optional; /** * Mojo that informs Skippy that the parts of the build that are relevant for Skippy (e.g., compilation and test @@ -39,8 +40,11 @@ public class SkippyBuildFinishedMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", required = true, readonly = true) MavenProject project; - @Parameter(defaultValue = "false", property = "saveExecutionData", required = false) - private boolean saveExecutionData; + @Parameter(defaultValue = "false", property = "coverageForSkippedTests", required = false) + private boolean coverageForSkippedTests; + + @Parameter(defaultValue = "false", property = "repository", required = false) + private String repository; @Component private MavenSession session; @@ -48,8 +52,8 @@ public class SkippyBuildFinishedMojo extends AbstractMojo { @Override public void execute() { var projectDir = project.getBasedir().toPath(); - var skippyConfiguration = new SkippyConfiguration(saveExecutionData); - var skippyApi = new SkippyApi( + var skippyConfiguration = new SkippyConfiguration(coverageForSkippedTests, Optional.ofNullable(repository)); + var skippyApi = new SkippyBuildApi( skippyConfiguration, new MavenClassFileCollector(project), SkippyRepository.getInstance(skippyConfiguration, projectDir, projectDir.resolve(Path.of(project.getBuild().getOutputDirectory()).getParent())) diff --git a/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildStartedMojo.java b/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildStartedMojo.java index b6aefa0..3788728 100644 --- a/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildStartedMojo.java +++ b/skippy-maven/src/main/java/io/skippy/maven/SkippyBuildStartedMojo.java @@ -16,7 +16,7 @@ package io.skippy.maven; -import io.skippy.core.SkippyApi; +import io.skippy.core.SkippyBuildApi; import io.skippy.core.SkippyConfiguration; import io.skippy.core.SkippyRepository; import org.apache.maven.execution.MavenSession; @@ -28,6 +28,7 @@ import org.apache.maven.project.MavenProject; import java.nio.file.Path; +import java.util.Optional; /** * Mojo that informs Skippy that a build has started. @@ -38,8 +39,11 @@ public class SkippyBuildStartedMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", required = true, readonly = true) MavenProject project; - @Parameter(defaultValue = "false", property = "saveExecutionData", required = false) - private boolean saveExecutionData; + @Parameter(defaultValue = "false", property = "coverageForSkippedTests", required = false) + private boolean coverageForSkippedTests; + + @Parameter(defaultValue = "false", property = "repository", required = false) + private String repository; @Component private MavenSession session; @@ -47,8 +51,8 @@ public class SkippyBuildStartedMojo extends AbstractMojo { @Override public void execute() { var projectDir = project.getBasedir().toPath(); - var skippyConfiguration = new SkippyConfiguration(saveExecutionData); - var skippyApi = new SkippyApi( + var skippyConfiguration = new SkippyConfiguration(coverageForSkippedTests, Optional.ofNullable(repository)); + var skippyApi = new SkippyBuildApi( skippyConfiguration, new MavenClassFileCollector(project), SkippyRepository.getInstance(skippyConfiguration, projectDir, projectDir.resolve(Path.of(project.getBuild().getOutputDirectory()).getParent())) diff --git a/skippy-maven/src/main/java/io/skippy/maven/SkippyCleanMojo.java b/skippy-maven/src/main/java/io/skippy/maven/SkippyCleanMojo.java index 9ba8445..7712d29 100644 --- a/skippy-maven/src/main/java/io/skippy/maven/SkippyCleanMojo.java +++ b/skippy-maven/src/main/java/io/skippy/maven/SkippyCleanMojo.java @@ -16,7 +16,7 @@ package io.skippy.maven; -import io.skippy.core.SkippyApi; +import io.skippy.core.SkippyBuildApi; import io.skippy.core.SkippyConfiguration; import io.skippy.core.SkippyRepository; import org.apache.maven.execution.MavenSession; @@ -28,6 +28,7 @@ import org.apache.maven.project.MavenProject; import java.nio.file.Path; +import java.util.Optional; /** * Clears the skippy folder. @@ -42,8 +43,11 @@ public class SkippyCleanMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", required = true, readonly = true) MavenProject project; - @Parameter(defaultValue = "false", property = "saveExecutionData", required = false) - private boolean saveExecutionData; + @Parameter(defaultValue = "false", property = "coverageForSkippedTests", required = false) + private boolean coverageForSkippedTests; + + @Parameter(defaultValue = "false", property = "repository", required = false) + private String repository; @Component private MavenSession session; @@ -51,8 +55,8 @@ public class SkippyCleanMojo extends AbstractMojo { @Override public void execute() { var projectDir = project.getBasedir().toPath(); - var skippyConfiguration = new SkippyConfiguration(saveExecutionData); - var skippyApi = new SkippyApi( + var skippyConfiguration = new SkippyConfiguration(coverageForSkippedTests, Optional.ofNullable(repository)); + var skippyApi = new SkippyBuildApi( skippyConfiguration, new MavenClassFileCollector(project), SkippyRepository.getInstance(skippyConfiguration, projectDir, projectDir.resolve(Path.of(project.getBuild().getOutputDirectory()).getParent())) diff --git a/skippy-maven/src/main/resources/META-INF/maven/plugin.xml b/skippy-maven/src/main/resources/META-INF/maven/plugin.xml index c013382..e38cda6 100644 --- a/skippy-maven/src/main/resources/META-INF/maven/plugin.xml +++ b/skippy-maven/src/main/resources/META-INF/maven/plugin.xml @@ -43,11 +43,18 @@ - +saveExecutionData +coverageForSkippedTests boolean false true -enables / disables capture of per-test JaCoCo execution data +enables / disables generation of test coverage for skipped tests ++ repository +string +false +true +fully-qualified class name of a custom io.skippy.core.SkippyRepositoryExtension @@ -85,11 +92,18 @@ - +saveExecutionData +coverageForSkippedTests boolean false true -enables / disables capture of per-test JaCoCo execution data +enables / disables generation of test coverage for skipped tests ++ repository +string +false +true +fully-qualified class name of a custom io.skippy.core.SkippyRepositoryExtension @@ -127,11 +141,18 @@ - +saveExecutionData +coverageForSkippedTests boolean false true -enables / disables capture of per-test JaCoCo execution data +enables / disables generation of test coverage for skipped tests ++ repository +string +false +true +fully-qualified class name of a custom io.skippy.core.SkippyRepositoryExtension