Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NoClassDefFoundError for Generated Test Sources with an Incremental Compilation (Maven) #4121

Open
mikewacker opened this issue Nov 1, 2023 · 1 comment

Comments

@mikewacker
Copy link

mikewacker commented Nov 1, 2023

Sample sources to reproduce this bug:

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>maven-dagger</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>

        <!-- main -->
        <dagger.version>2.48.1</dagger.version>

        <!-- test -->
        <assertj.version>3.24.2</assertj.version>
        <junit-jupiter.version>5.10.0</junit-jupiter.version>

        <!-- plugins -->
        <maven-compiler.version>3.11.0</maven-compiler.version>
        <maven-surefire.version>3.1.2</maven-surefire.version>
    </properties>

    <dependencies>
        <!-- main -->
        <dependency>
            <groupId>com.google.dagger</groupId>
            <artifactId>dagger-compiler</artifactId>
            <version>${dagger.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>

        <!-- test -->
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>${assertj.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit-jupiter.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                    <compilerArgs>
                        <arg>-Werror</arg>
                        <arg>-implicit:none</arg>
                    </compilerArgs>
                    <annotationProcessorPaths>
                        <dependency>
                            <groupId>com.google.dagger</groupId>
                            <artifactId>dagger-compiler</artifactId>
                            <version>${dagger.version}</version>
                        </dependency>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire.version}</version>
            </plugin>
        </plugins>
    </build>
</project>

Greeter.java (main):

package org.example;

import java.util.function.Supplier;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public final class Greeter {

    private final Supplier<String> recipientSupplier;

    @Inject
    public Greeter(Supplier<String> recipientSupplier) {
        this.recipientSupplier = recipientSupplier;
    }

    public String getGreeting() {
        return String.format("Hello, %s!", recipientSupplier.get());
    }
}

GreeterTest.java (test):

package org.example;

import static org.assertj.core.api.Assertions.assertThat;

import dagger.Binds;
import dagger.Component;
import dagger.Module;
import java.util.function.Supplier;
import javax.inject.Singleton;
import org.junit.jupiter.api.Test;

public final class GreeterTest {

    @Test
    public void getGreeting() {
        Greeter greeter = TestComponent.createGreeter();
        assertThat(greeter.getGreeting()).isEqualTo("Hello, world!");
    }

    /** Dagger module that binds dependencies needed to create a {@link Greeter}. */
    @Module
    interface TestModule {

        @Binds
        Supplier<String> bindRecipient(TestRecipientSupplier impl);
    }

    /** Dagger component that provides a {@link Greeter}. */
    @Component(modules = TestModule.class)
    @Singleton
    interface TestComponent {

        static Greeter createGreeter() {
            TestComponent component = DaggerGreeterTest_TestComponent.create();
            return component.greeter();
        }

        Greeter greeter();
    }
}

TestRecipientSupplier.java (test):

package org.example;

import java.util.function.Supplier;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public final class TestRecipientSupplier implements Supplier<String> {

    @Inject
    public TestRecipientSupplier() {}

    @Override
    public String get() {
        return "world";
    }
}

Steps:

  1. Set up a project with the sample sources, and verify that tests pass: mvn clean verify
  2. Change the code (e.g., change the recipient from "world" to "Dagger")
  3. Run the tests via an incremental compilation: mvn verify
    • Tests are expected to pass, but will fail.
  4. Run tests via a full compilation: mvn clean verify
    • Tests will pass again.

Here is the error from step 3:

[ERROR] org.example.GreeterTest.getGreeting -- Time elapsed: 0.017 s <<< ERROR!
java.lang.NoClassDefFoundError: org/example/TestRecipientSupplier_Factory
	at org.example.DaggerGreeterTest_TestComponent$TestComponentImpl.initialize(DaggerGreeterTest_TestComponent.java:55)
	at org.example.DaggerGreeterTest_TestComponent$TestComponentImpl.<init>(DaggerGreeterTest_TestComponent.java:49)
	at org.example.DaggerGreeterTest_TestComponent$Builder.build(DaggerGreeterTest_TestComponent.java:36)
	at org.example.DaggerGreeterTest_TestComponent.create(DaggerGreeterTest_TestComponent.java:28)
	at org.example.GreeterTest$TestComponent.createGreeter(GreeterTest.java:34)
	at org.example.GreeterTest.getGreeting(GreeterTest.java:16)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.ClassNotFoundException: org.example.TestRecipientSupplier_Factory
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
	... 9 more

Here's the output of mvn -v (on Ubuntu 22.04):

Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.8.1, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.2.0-36-generic", arch: "amd64", family: "unix"
@mikewacker
Copy link
Author

For some reason, this bug does not happen with Gradle, though (./gradlew build, using Gradle 8.4). Here are the relevant Gradle files (both in the root directory), not including the Gradle build scripts:

build.gradle.kts:

plugins {
    `java-library`
}

repositories {
    mavenCentral()
}

dependencies {
    annotationProcessor("com.google.dagger:dagger-compiler:2.48.1")

    implementation("com.google.dagger:dagger:2.48.1")

    testAnnotationProcessor("com.google.dagger:dagger-compiler:2.48.1")

    testImplementation("org.assertj:assertj-core:3.24.2")
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")

    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

tasks.withType<JavaCompile> {
    options.compilerArgs.add("-Werror")
}

tasks.test {
    useJUnitPlatform()
}

setting.gradle.kts:

rootProject.name = "gradle-dagger"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant