Skip to content

Commit

Permalink
issues/27: Prevent duplicate coverage builds if class files are dupli…
Browse files Browse the repository at this point in the history
…cated across SourceSets
  • Loading branch information
fmck3516 committed Dec 6, 2023
1 parent e0fb1d8 commit 056d122
Show file tree
Hide file tree
Showing 18 changed files with 470 additions and 418 deletions.
11 changes: 11 additions & 0 deletions skippy-gradle-sandbox/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ plugins {

apply plugin: io.skippy.gradle.SkippyPlugin;

skippy {
sourceSet {
name = 'test'
testTask = 'test'
}
sourceSet {
name = 'intTest'
testTask = 'integrationTest'
}
}

repositories {
mavenCentral()
maven { url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
Expand Down
2 changes: 1 addition & 1 deletion skippy-gradle-sandbox/gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
org.gradle.logging.level=info
org.gradle.logging.level=lifecycle
39 changes: 20 additions & 19 deletions skippy-gradle/src/main/java/io/skippy/gradle/AnalyzeTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package io.skippy.gradle;

import io.skippy.gradle.collector.ClassFileCollector;
import io.skippy.gradle.collector.SkippifiedTestCollector;
import io.skippy.gradle.model.SkippifiedTest;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.UncheckedIOException;
import org.gradle.tooling.BuildLauncher;
import org.gradle.tooling.GradleConnector;
Expand All @@ -26,7 +28,6 @@
import javax.inject.Inject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import static io.skippy.gradle.SkippyConstants.SKIPPY_ANALYSIS_FILES_TXT;
import static io.skippy.gradle.SkippyConstants.SKIPPY_DIRECTORY;
Expand All @@ -46,30 +47,26 @@ class AnalyzeTask extends DefaultTask {
* Comment to make the JavaDoc task happy.
*/
@Inject
public AnalyzeTask() {
public AnalyzeTask(ClassFileCollector classCollector, SkippifiedTestCollector skippifiedTestCollector) {
setGroup("skippy");
var dependencies = new ArrayList<String>();
dependencies.add("classes");
dependencies.add("testClasses");
dependencies.add("skippyClean");
setDependsOn(dependencies);
dependsOn("skippyClean");
doLast((task) -> {
createCoverageReportsForSkippifiedTests(getProject());
createAnalyzedFilesTxt(getProject());
createCoverageReportsForSkippifiedTests(skippifiedTestCollector);
createAnalyzedFilesTxt(classCollector);
});
}

private void createCoverageReportsForSkippifiedTests(Project project) {
private void createCoverageReportsForSkippifiedTests(SkippifiedTestCollector skippifiedTestCollector) {
GradleConnector connector = GradleConnector.newConnector();
connector.forProjectDirectory(getProject().getProjectDir());
try (ProjectConnection connection = connector.connect()) {
for (var skippifiedTest : DecoratedClass.fromAllSkippifiedTestsIn(project)) {
for (var skippifiedTest : skippifiedTestCollector.collectAllIn(getProject())) {
runCoverageBuild(connection, skippifiedTest);
}
}
}

private void runCoverageBuild(ProjectConnection connection, DecoratedClass skippifiedTest) {
private void runCoverageBuild(ProjectConnection connection, SkippifiedTest skippifiedTest) {
BuildLauncher buildLauncher = connection.newBuild();
var errorOutputStream = new ByteArrayOutputStream();
buildLauncher.setStandardError(errorOutputStream);
Expand All @@ -96,9 +93,13 @@ private void runCoverageBuild(ProjectConnection connection, DecoratedClass skipp
}
}

private void configureCoverageBuild(BuildLauncher build, DecoratedClass skippifiedTest) {
var tasks = asList(skippifiedTest.getTestTask().getName(), "jacocoTestReport");
var arguments = asList("-PskippyCoverageBuild=true", "-PskippyClassFile=" + skippifiedTest.getAbsolutePath());
private void configureCoverageBuild(BuildLauncher build, SkippifiedTest skippifiedTest) {
var tasks = asList(skippifiedTest.getTestTask(), "jacocoTestReport");
var arguments = asList(
"-PskippyCoverageBuild=true",
"-PskippyClassFile=" + skippifiedTest.getAbsolutePath(),
"-PskippyTestTask=" + skippifiedTest.getTestTask()
);
build.forTasks(tasks.toArray(new String[0]));
build.addArguments(arguments.toArray(new String[0]));
if (getLogging().getLevel() != null) {
Expand All @@ -118,12 +119,12 @@ private void configureCoverageBuild(BuildLauncher build, DecoratedClass skippifi
getLogger().lifecycle("%s".formatted(skippifiedTest.getFullyQualifiedClassName()));
}

private void createAnalyzedFilesTxt(Project project) {
private void createAnalyzedFilesTxt(ClassFileCollector classCollector) {
try {
var skippyAnalysisFile = getProject().getProjectDir().toPath().resolve(SKIPPY_DIRECTORY).resolve(SKIPPY_ANALYSIS_FILES_TXT);
skippyAnalysisFile.toFile().createNewFile();
var classFiles = DecoratedClass.fromAllClassesIn(project);
getLogger().lifecycle("\nCreating the Skippy analysis file %s.".formatted(project.getProjectDir().toPath().relativize(skippyAnalysisFile)));
getLogger().lifecycle("\nCreating the Skippy analysis file %s.".formatted(getProject().getProjectDir().toPath().relativize(skippyAnalysisFile)));
var classFiles = classCollector.collectAllInProject(getProject());
writeString(skippyAnalysisFile, classFiles.stream()
.map(classFile -> "%s:%s".formatted(classFile.getRelativePath(), classFile.getHash()))
.collect(joining(lineSeparator())));
Expand Down
127 changes: 0 additions & 127 deletions skippy-gradle/src/main/java/io/skippy/gradle/DecoratedClass.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.skippy.gradle;

import io.skippy.gradle.model.ClassFile;

import java.nio.file.Path;

/**
Expand All @@ -31,7 +33,7 @@ public final class SkippyConstants {
public static final Path SKIPPY_DIRECTORY = Path.of("skippy");

/**
* The file that contains data for all {@link DecoratedClass}s.
* The file that contains data for all {@link ClassFile}s.
*/
public static final Path SKIPPY_ANALYSIS_FILES_TXT = Path.of("analyzedFiles.txt");

Expand Down
29 changes: 20 additions & 9 deletions skippy-gradle/src/main/java/io/skippy/gradle/SkippyPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package io.skippy.gradle;

import io.skippy.gradle.collector.ClassFileCollector;
import io.skippy.gradle.collector.SkippifiedTestCollector;
import io.skippy.gradle.model.ClassFile;
import io.skippy.gradle.model.SkippifiedTest;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.SourceSet;
Expand All @@ -40,17 +44,24 @@ public final class SkippyPlugin implements org.gradle.api.Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(JavaPlugin.class);
project.getExtensions().create("skippy", SkippyPluginExtension.class);

var isSkippyCoverageBuild = project.hasProperty("skippyCoverageBuild");

if (! isSkippyCoverageBuild) {

// add skippy tasks to the regular build
project.getTasks().register("skippyClean", CleanTask.class);
project.getTasks().register("skippyAnalyze", AnalyzeTask.class);

var classFileCollector = new ClassFileCollector();
var skippifiedTestCollector = new SkippifiedTestCollector(classFileCollector);

project.getTasks().register("skippyAnalyze", AnalyzeTask.class, classFileCollector, skippifiedTestCollector);
} else {

var testClass = new DecoratedClass(project, Path.of(String.valueOf(project.property("skippyClassFile"))));
var classFile = Path.of(String.valueOf(project.property("skippyClassFile")));
var testTaskName = String.valueOf(project.property("skippyTestTask"));
var testClass = new SkippifiedTest(new ClassFile(project, classFile), testTaskName);

// modify test and jacocoTestReport tasks in the skippyCoverage builds
project.getPlugins().apply(JacocoPlugin.class);
Expand All @@ -59,25 +70,25 @@ public void apply(Project project) {
}
}

private static void modifyTestTask(Project project, DecoratedClass testClass) {
private static void modifyTestTask(Project project, SkippifiedTest skippifiedTest) {
project.getTasks().withType(Test.class, test -> {
test.filter((filter) -> filter.includeTestsMatching(testClass.getFullyQualifiedClassName()));
test.filter((filter) -> filter.includeTestsMatching(skippifiedTest.getFullyQualifiedClassName()));
test.getExtensions().configure(JacocoTaskExtension.class, jacoco -> {
jacoco.setDestinationFile(project.file(project.getProjectDir() + "/skippy/" + testClass.getFullyQualifiedClassName() + ".exec"));
jacoco.setDestinationFile(project.file(project.getProjectDir() + "/skippy/" + skippifiedTest.getFullyQualifiedClassName() + ".exec"));
});
});
}

private static void modifyJacocoTestReportTask(Project project, DecoratedClass testClass) {
private static void modifyJacocoTestReportTask(Project project, SkippifiedTest skippifiedTest) {
project.afterEvaluate(action -> {
var testTask = testClass.getTestTask().getName();
var testTask = skippifiedTest.getTestTask();
project.getTasks().named("jacocoTestReport", JacocoReport.class, jacoco -> {
jacoco.setDependsOn(asList(testTask));
jacoco.reports(reports -> {
reports.getXml().getRequired().set(Boolean.FALSE);
reports.getCsv().getRequired().set(Boolean.TRUE);
reports.getHtml().getOutputLocation().set(project.file(project.getBuildDir() + "/jacoco/html/" + testClass.getFullyQualifiedClassName()));
var csvFile = project.getProjectDir().toPath().resolve(SKIPPY_DIRECTORY).resolve(testClass.getFullyQualifiedClassName() + ".csv");
reports.getHtml().getOutputLocation().set(project.file(project.getBuildDir() + "/jacoco/html/" + skippifiedTest.getFullyQualifiedClassName()));
var csvFile = project.getProjectDir().toPath().resolve(SKIPPY_DIRECTORY).resolve(skippifiedTest.getFullyQualifiedClassName() + ".csv");
reports.getCsv().getOutputLocation().set(project.file(csvFile));
});
// capture coverage for all source sets
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2023 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.gradle;

import io.skippy.gradle.model.SourceSetWithTestTask;
import org.gradle.api.Action;

import java.util.ArrayList;
import java.util.List;

import static org.codehaus.groovy.runtime.InvokerHelper.asList;

/**
* Extension that allows for customization of {@link AnalyzeTask}.
*
* @author Florian McKee
*/
public class SkippyPluginExtension {

private List<SourceSetWithTestTask> sourceSetsWithTestTask = new ArrayList<>();

public List<SourceSetWithTestTask> getSourceSetsWithTestTasks() {
if (sourceSetsWithTestTask.isEmpty()) {
return asList(new SourceSetWithTestTask("test", "test"));
}
return sourceSetsWithTestTask;
}

public void sourceSet(Action<SourceSetWithTestTask> action) {
var sourceSetAndTestTask = new SourceSetWithTestTask(null, null);
sourceSetsWithTestTask.add(sourceSetAndTestTask);
action.execute(sourceSetAndTestTask);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public final class SkippyJUnit5Detector {
* @return {@code true} if the {@code classFile} is annotated with the Skippy JUnit 5 Extension, {@code false}
* otherwise
*/
public static boolean usesSkippyExtension(Path classFile) {
public static boolean usesSkippyJunit5Extension(Path classFile) {
var usesSkippyJunit5Extension = new AtomicBoolean(false);
try (var inputStream = new FileInputStream(classFile.toFile())) {
new ClassReader(inputStream).accept(createClassVisitor(usesSkippyJunit5Extension), 0);
Expand Down
Loading

0 comments on commit 056d122

Please sign in to comment.