'testFixtures' don't work with 'implementation' in Eclipse only

As has been reported by posts such as

declaring

dependencies {
    implementation project(':my-project')
    testImplementation testFixtures(project(':my-project'))
}

Causes compilation errors in Eclipse, while things work well from Gradle and in other IDEs. The above dependency configuration is not only valid, but also recommended in such cases. For example, here at 4:50:

I read about the changes in Gradle 7.5 and the 2 classpaths in Eclipse (The Eclipse Plugins). Note that 7.5+ - Eclipse cannot resolve types from projects with text fixtures · Issue #21853 · gradle/gradle · GitHub is still active, requesting a fix, and like the last comment there by effad, I also have to wonder about the choice that was made when both test=false and test=true are present. The above configuration means “I need the main source set of the dependency for my main source set and the testFixtures source set for my tests source set”.

This configuration should be made valid in Eclipse out of the box since it’s valid in Gradle and we shouldn’t need to choose one or the other. It seems that this is something Buildship should do (or is it the responsibility of the eclipse plugin?).

@donat Can you have a look?

Hi @omega09!

This problem has been solved with Gradle 8.9 (Gradle 8.9 Release Notes).

These related issues have been closed:

1 Like

Thanks for fixing this!

However, there seems to be either a related problem or the fix didn’t cover all cases. If the project that contains the test fixtures is a modular project (with module-info), then the consuming project can’t see the test fixtures. Running from Gradle doesn’t have issues, bit Eclipse won’t compile any test class that tries to import from a text fixture class.

Should I open a new bug or did I miss some configuration? If it’s a bug, is there a workaround?

The problem seems to be that the consumed project (fixtures in my example) is not recognized as modular. Open: Project > Properties: Java Build Path

This problem only affects project dependencies. For library dependencies the presence of the module descriptor is correctly recognized.

The missing attribute can be set manually in build.gradle of the consuming project:

plugins {
  // [...]
  id 'eclipse'
}

eclipse {
  classpath {
    file {
      whenMerged {
        it.entries.find { it.path == '/fixtures' }.entryAttributes['module'] = true
      }
    }
  }
}

Now required packages should be able to be imported in the module-info.java of the consuming project.

For me the project appears as modular already.

The fixtures project is modular and looks like

plugins {
	id("java-library")
	id("java-test-fixtures")
	id("eclipse")
}

and has a src/testFixtures/java folder with the test fixture classes.

The consuming project server is not modular and looks like this:

plugins {
	id("java")
	id("war")
	id("jvm-test-suite")
	id("eclipse")
}

testing {
	suites {
		configureEach {
			useJUnitJupiter()
			dependencies {
				implementation(project())
				implementation(testFixtures(project(":fixtures")))
			}
		}
		...
	}
}

This looks like the same setup you have.

Then, in the Libraries view as you show, I do have fixtures as modular. Yet, if I try to import a class from src/testFixtures/java in fixtures inside a class in src/test/java in server, I get a “The import cannot be resolved” error.

It is difficult to reproduce this problem from the description alone. Could you provide a minimal complete example project?

As a starting point you can use GitHub - oleosterhagen/gradle-test-fixtures. This Gradle build can be successfully imported into Eclipse.

Here is a project similar to yours. A parent project contains a lib modular project that defines text fixtures, and a consumer non-modular project that tries to import those test fixtures, but can’t.

testFixtures.zip (64.7 KB)

Thank you for providing an example project.

Eclipse separates the main classpath from the test classpath. The module-info.java of project lib is on the main classpath and does not export packages from the test classpath. So the test classes of the project lib are not visible in the project consumer.

A second module-info.java for the test classpath could be a solution. But Eclipse JDT does currently only support one module-info.java per project. This limitation is currently discussed

As a workaround you can set the module attribute to false in the project dependency in /consumer/build.gradle:

eclipse {
  classpath {
    file {
      whenMerged {
        it.entries.findAll { it.path == '/lib' }.each { it.entryAttributes['module'] = false }
      }
    }
  }
}

The lack of support in Eclipse JDT also affects the Maven integration (m2e): Build path contains duplicate entry: 'module-info.java' for project 'Test' · Issue #173 · eclipse-m2e/m2e-core · GitHub

1 Like

Thanks for the relevant links!

Won’t this workaround move lib to the classpath completely? I build an image of consumer with jlink, so I need lib to be a module dependency. It’s only the tests that need to be on the classpath.

When jlink will be invoked from the Gradle build script, everything should work as before. The workaround eclipse.classpath.file... only affects the classpath in Eclipse, e.g. when compiling sources with JDT or launching the tests within Eclipse.

When it’s invoked yes, but my consumer project has to be modular, and if I put lib on the classpath, its module-info throws errors because consumer defines a requires entry on packages in lib.

Can it be added to the test classpath and the compile modulepath maybe? Or some other workaround that fixes the test issue without causing problems in the main sources?

I have imported your example project and added the workaround in consumer. Everything compiles in Eclipse and no errors are shown.

[…] its module-info throws errors […]

What do I have to do now to get these errors? Are these errors shown in Eclipse or when invoking the Gradle build?

Sorry, I didn’t put a module-info into consumer. Here is the updated project:
testFixtures.zip (67.2 KB)

If I apply the workaround, the tests see the fixtures, but the module is not available. If I don’t, the module is available, but the fixtures aren’t.

Because consumer is now modular in the updated example project, the dependency to lib must also stay modular and the workaround cannot be applied anymore.

The test class LibraryTest is not exported in the module-info.java of lib. Without a second module-info.java, which isn’t currently supported by JDT, this class cannot be imported in consumer.

JDT has a concept for separating main and test sources and maintaining different classpaths for them, but a project dependency like lib can only be imported as a main or test dependency. So it is not possible to configure lib as modular for the main classpath and non modular for the test classpath the same time.

Splitting the lib project into two separate projects - one for the main sources and one for the test sources - could possibly work. As proposed in Support additional module-info.java for tests, if that ends up being de-facto standard · Issue #1465 · eclipse-jdt/eclipse.jdt.core · GitHub Maven (or Gradle) could do this under the hood. But I think, the different representations of projects in Gradle (or filesystem) and Eclipse are hard to understand and lead to a bad user experience.

1 Like

This is what I thought too.

Yes, this is the core of the problem. I had hoped there was a “trick” to put main on the modulepath and test on the classpath, but looks like there isn’t.

I might end up doing this split unfortunately. It decouples code that should be coupled, so there’s some danger there.

Tell me about it. Took me a long time to understand the surface level of Gradle-Eclipse integrations. I can see how problems in this area cause people to stop using either Gradle or Eclipse, although the alternatives have their own flaws. Some of these issues could be bridged with more investment into Buildship, but the seat in the peanut gallery is free.

Anyway, you fixed the non-modular problem in 8.9 :tada: and explained why there is no viable solution for the modular case. I consider the case closed :slight_smile: .