Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
@file:Suppress("UnstableApiUsage", "HasPlatformType", "PropertyName")

import org.jetbrains.kotlin.cli.common.toBooleanLenient

plugins {
id("java-gradle-plugin")
id("com.gradle.plugin-publish")
Expand Down Expand Up @@ -182,7 +180,7 @@ fun maxParallelForks() =

val isCi = providers.environmentVariable("CI")
.getOrElse("false")
.toBooleanLenient()!!
.toBoolean()

// This will slow down tests on CI, but maybe it won't run out of metaspace.
fun forkEvery(): Long = if (isCi) 40 else 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import spock.lang.Specification

abstract class AbstractFunctionalSpec extends Specification {

@SuppressWarnings('unused')
protected static final String FLAG_LOG_BYTECODE = "-D${Flags.FLAG_BYTECODE_LOGGING}=true"

protected static final GRADLE_7_5 = GradleVersion.version('7.5.1')
protected static final GRADLE_7_6 = GradleVersion.version('7.6.2')
protected static final GRADLE_8_0 = GradleVersion.version('8.0.2')
protected static final GRADLE_8_4 = GradleVersion.version('8.4')
protected static final GRADLE_8_9 = GradleVersion.version('8.9')
protected static final GRADLE_8_10 = GradleVersion.version('8.10.2')
protected static final GRADLE_8_11 = GradleVersion.version('8.11-rc-1')
protected static final GRADLE_8_11 = GradleVersion.version('8.11')

protected static final GRADLE_LATEST = GRADLE_8_10

Expand All @@ -26,11 +29,26 @@ abstract class AbstractFunctionalSpec extends Specification {
protected static final SUPPORTED_GRADLE_VERSIONS = [
GradleVersions.minGradleVersion,
GRADLE_LATEST,
// GRADLE_8_11,
//GRADLE_8_11,
]

protected GradleProject gradleProject = null

/**
* <a href="https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables">Default environment variables on Github Actions</a>
*/
private static boolean isCi = System.getenv("CI") == "true"

def cleanup() {
// Delete fixtures on CI to prevent disk space growing out of bounds
if (gradleProject != null && isCi) {
try {
gradleProject.rootDir.deleteDir()
} catch (Throwable t) {
}
}
}

protected static Boolean quick() {
return System.getProperty('com.autonomousapps.quick').toBoolean()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.autonomousapps.jvm

import com.autonomousapps.jvm.projects.BinaryIncompatibilityProject
import com.autonomousapps.jvm.projects.DuplicateClasspathProject
import org.gradle.testkit.runner.TaskOutcome
import com.autonomousapps.utils.Colors

import static com.autonomousapps.advice.truth.BuildHealthSubject.buildHealth
import static com.autonomousapps.utils.Runner.build
Expand All @@ -17,13 +18,19 @@ final class DuplicateClasspathSpec extends AbstractJvmSpec {
gradleProject = project.gradleProject

when:
// This first invocation "fixes" the dependency declarations
// This first invocation fixes the dependency declarations
build(gradleVersion, gradleProject.rootDir, ':consumer:fixDependencies')
// This second invocation fails because the fix (+ sort) causes the JVM to load the wrong class during compilation.
def result = buildAndFail(gradleVersion, gradleProject.rootDir, 'buildHealth')
// This used to fail because of classpath duplication and the wrong dep getting loaded first. Recent improvements
// have improved this case, however.
build(gradleVersion, gradleProject.rootDir, 'buildHealth')

then:
assertThat(result.task(':consumer:compileJava').outcome).isEqualTo(TaskOutcome.FAILED)
assertThat(gradleProject.rootDir.toPath().resolve('consumer/build.gradle').text).contains(
'''\
dependencies {
implementation project(':producer-2')
}'''.stripIndent()
)

where:
gradleVersion << [GRADLE_LATEST]
Expand All @@ -35,7 +42,7 @@ final class DuplicateClasspathSpec extends AbstractJvmSpec {
gradleProject = project.gradleProject

when:
def result = build(gradleVersion, gradleProject.rootDir, 'buildHealth')
def result = buildAndFail(gradleVersion, gradleProject.rootDir, 'buildHealth')

then:
assertThat(result.output).contains(
Expand All @@ -45,11 +52,11 @@ final class DuplicateClasspathSpec extends AbstractJvmSpec {

Source set: main
\\--- compile classpath
+--- com/example/producer/Producer$Inner.class is provided by multiple dependencies: [:producer-1, :producer-2]
\\--- com/example/producer/Producer.class is provided by multiple dependencies: [:producer-1, :producer-2]
+--- com/example/producer/Producer is provided by multiple dependencies: [:producer-1, :producer-2]
\\--- com/example/producer/Producer$Inner is provided by multiple dependencies: [:producer-1, :producer-2]
\\--- runtime classpath
+--- com/example/producer/Producer$Inner.class is provided by multiple dependencies: [:producer-1, :producer-2]
\\--- com/example/producer/Producer.class is provided by multiple dependencies: [:producer-1, :producer-2]'''
+--- com/example/producer/Producer is provided by multiple dependencies: [:producer-1, :producer-2]
\\--- com/example/producer/Producer$Inner is provided by multiple dependencies: [:producer-1, :producer-2]'''
.stripIndent()
)

Expand All @@ -61,4 +68,108 @@ final class DuplicateClasspathSpec extends AbstractJvmSpec {
where:
gradleVersion << [GRADLE_LATEST]
}

def "can report on which of the duplicates is needed for binary compatibility (#gradleVersion)"() {
given:
def project = new BinaryIncompatibilityProject()
gradleProject = project.gradleProject

when:
def result = build(
gradleVersion, gradleProject.rootDir,
':consumer:reason', '--id', ':producer-1',
//FLAG_LOG_BYTECODE,
)

then:
assertThat(Colors.decolorize(result.output)).contains(
'''\
------------------------------------------------------------
You asked about the dependency ':producer-1'.
There is no advice regarding this dependency.
------------------------------------------------------------

Shortest path from :consumer to :producer-1 for compileClasspath:
:consumer
\\--- :unused
\\--- :producer-1

Shortest path from :consumer to :producer-1 for runtimeClasspath:
:consumer
\\--- :unused
\\--- :producer-1

Shortest path from :consumer to :producer-1 for testCompileClasspath:
:consumer
\\--- :unused
\\--- :producer-1

Shortest path from :consumer to :producer-1 for testRuntimeClasspath:
:consumer
\\--- :unused
\\--- :producer-1

Source: main
------------
* Is binary-incompatible, and should be removed from the classpath:
Expected METHOD com/example/producer/Person.<init>(Ljava/lang/String;Ljava/lang/String;)V, but was com/example/producer/Person.<init>(Ljava/lang/String;)V

Source: test
------------
(no usages)'''.stripIndent()
)

where:
gradleVersion << [GRADLE_LATEST]
}

def "suggests removing a binary-incompatible duplicate (#gradleVersion)"() {
given:
def project = new BinaryIncompatibilityProject(true)
gradleProject = project.gradleProject

when:
def result = build(
gradleVersion, gradleProject.rootDir,
':consumer:reason', '--id', ':producer-1',
//FLAG_LOG_BYTECODE,
)

then:
assertThat(Colors.decolorize(result.output)).contains(
'''\
------------------------------------------------------------
You asked about the dependency ':producer-1'.
You have been advised to remove this dependency from 'implementation'.
------------------------------------------------------------

Shortest path from :consumer to :producer-1 for compileClasspath:
:consumer
\\--- :producer-1

Shortest path from :consumer to :producer-1 for runtimeClasspath:
:consumer
\\--- :producer-1

Shortest path from :consumer to :producer-1 for testCompileClasspath:
:consumer
\\--- :producer-1

Shortest path from :consumer to :producer-1 for testRuntimeClasspath:
:consumer
\\--- :producer-1

Source: main
------------
* Is binary-incompatible, and should be removed from the classpath:
Expected METHOD com/example/producer/Person.<init>(Ljava/lang/String;Ljava/lang/String;)V, but was com/example/producer/Person.<init>(Ljava/lang/String;)V

Source: test
------------
(no usages)'''.stripIndent()
)

where:
gradleVersion << [GRADLE_LATEST]
}
}
Loading