Skip to content

treat testFixtures and the associated main sources like different projects #288

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

Merged
merged 2 commits into from
Nov 18, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ data class ClasspathDependencies(
}
.toSet()

val directPairs = directDependencies.map { TransitiveProjectDependency(it, it) }
val directTransitive = directDependencies.map { TransitiveProjectDependency(it, it) }

return directPairs + inherited
val mainFromTestFixtures = directDependencies.filter { it.isTestFixture }
.map { TransitiveProjectDependency(it, it.copy(isTestFixture = false)) }

return directTransitive + inherited + mainFromTestFixtures
}

companion object Key : ProjectContext.Key<ClasspathDependencies> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ data class MustBeApi(
// exclude anything which is inherited but already included in local `api` deps
cpd in project.projectDependencies[ConfigurationName.api].orEmpty()
}
.filterBlocking { it.project.mustBeApiIn(importsFromDependencies) }
.filterBlocking { it.project.mustBeApiIn(importsFromDependencies, it.isTestFixture) }
.mapBlocking { cpd ->
val source = project
.projectDependencies
Expand Down Expand Up @@ -98,22 +98,28 @@ private suspend fun McProject.importsFromDependencies(): Set<String> {
}

suspend fun McProject.mustBeApiIn(
dependentProject: McProject
dependentProject: McProject,
isTestFixtures: Boolean
): Boolean {
val importsFromDependencies = dependentProject.importsFromDependencies()
return declarations()[SourceSetName.MAIN]
.orEmpty()
.map { it.fqName }
.any { declared -> declared in importsFromDependencies }
return mustBeApiIn(importsFromDependencies, isTestFixtures)
}

suspend fun McProject.mustBeApiIn(
importsFromDependencies: Set<String>
importsFromDependencies: Set<String>,
isTestFixtures: Boolean
): Boolean {
return declarations()[SourceSetName.MAIN]
.orEmpty()
.map { it.fqName }
.any { declared -> declared in importsFromDependencies }

val declarations = if (isTestFixtures) {
declarations()[SourceSetName.TEST_FIXTURES]
} else {
declarations()[SourceSetName.MAIN]
} ?: return false

return declarations
.any { declared ->
declared.fqName in importsFromDependencies
}
}

data class InheritedDependencyWithSource(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,35 @@ class InheritedDependencyRule : ModuleCheckRule<InheritedDependencyFinding> {
override suspend fun check(project: McProject): List<InheritedDependencyFinding> {

val mainDirectDependencies = project.projectDependencies.main()
.map { it.project }
.map { it.project to it.isTestFixture }
.toSet()

val used = project.classpathDependencies().all()
.filterNot { it.contributed.project in mainDirectDependencies }
.distinctBy { it.contributed.project.path }
.filterNot { mainDirectDependencies.contains(it.contributed.project to it.contributed.isTestFixture) }
.distinctBy { it.contributed.project.path to it.contributed.isTestFixture }
.filter { project.uses(it) }

val dependencyPathCache = mutableMapOf<SourceSetName, Set<String>>()
fun pathsForSourceSet(sourceSetName: SourceSetName): Set<String> {
val dependencyPathCache = mutableMapOf<SourceSetName, Set<Pair<String, Boolean>>>()
fun pathsForSourceSet(sourceSetName: SourceSetName): Set<Pair<String, Boolean>> {
return dependencyPathCache.getOrPut(sourceSetName) {
project.projectDependencies[sourceSetName].map { it.project.path }.toSet()
project.projectDependencies[sourceSetName]
.map { it.project.path to it.isTestFixture }
.toSet()
}
}

return used.asSequence()
.filterNot { it.contributed.project.path in pathsForSourceSet(it.source.configurationName.toSourceSetName()) }
.filterNot {
pathsForSourceSet(it.source.configurationName.toSourceSetName())
.contains((it.contributed.project.path to it.contributed.isTestFixture))
}
.distinct()
.mapBlocking { transitive ->

val source = transitive.source
val inherited = transitive.contributed

val mustBeApi = inherited.project.mustBeApiIn(project)
val mustBeApi = inherited.project.mustBeApiIn(project, inherited.isTestFixture)

val newConfig = if (mustBeApi) {
source.configurationName.apiVariant()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ class InheritedDependenciesTest : ProjectTest() {
}

val lib2 = project(":lib2") {
addDependency("testFixturesImplementation".asConfigurationName(), lib1)
addDependency("testFixturesApi".asConfigurationName(), lib1, asTestFixture = true)

buildFile.writeText(
Expand All @@ -797,6 +798,7 @@ class InheritedDependenciesTest : ProjectTest() {
}

dependencies {
testFixturesImplementation(project(path = ":lib1"))
testFixturesApi(testFixtures(project(path = ":lib1")))
}
""".trimIndent()
Expand Down Expand Up @@ -1064,19 +1066,188 @@ class InheritedDependenciesTest : ProjectTest() {
}
"""

logger.collectReport()
.joinToString()
.clean() shouldBe """
:lib2
dependency name source build file
✔ :lib1 overshot /lib2/build.gradle.kts:
✔ :lib1 unusedDependency /lib2/build.gradle.kts: (6, 3):

:lib3
dependency name source build file
✔ :lib1 inheritedDependency :lib2 /lib3/build.gradle.kts: (6, 3):

ModuleCheck found 3 issues
"""
}

@Test
fun `inherited main source testFixture in same module with auto-correct should be fixed as normal testImplementation`() {

val runner = ModuleCheckRunner(
autoCorrect = true,
settings = baseSettings,
findingFactory = findingFactory,
logger = logger
)

val lib1 = project(":lib1") {
addSource(
"com/modulecheck/lib1/Lib1Class.kt",
"""
package com.modulecheck.lib1

open class Lib1Class
""".trimIndent()
)
addSource(
"com/modulecheck/lib1/test/FakeLib1Class.kt",
"""
package com.modulecheck.lib1.test

import com.modulecheck.lib1.Lib1Class

open class FakeLib1Class : Lib1Class()
""".trimIndent(),
SourceSetName.TEST_FIXTURES
)
}

val lib2 = project(":lib2") {
addDependency(ConfigurationName.testImplementation, lib1, asTestFixture = true)

buildFile.writeText(
"""
plugins {
kotlin("jvm")
}

dependencies {
testImplementation(testFixtures(project(path = ":lib1")))
}
""".trimIndent()
)
addSource(
"com/modulecheck/lib2/Lib2Class.kt",
"""
package com.modulecheck.lib2

import com.modulecheck.lib1.Lib1Class
import com.modulecheck.lib1.test.FakeLib2Class

val clazz = Lib1Class()
private val clazz2 = FakeLib2Class()
""".trimIndent(),
SourceSetName.TEST
)
}

runner.run(allProjects()).isSuccess shouldBe true

lib2.buildFile.readText() shouldBe """
plugins {
kotlin("jvm")
}

dependencies {
testImplementation(project(path = ":lib1"))
testImplementation(testFixtures(project(path = ":lib1")))
}
"""

logger.collectReport()
.joinToString()
.clean() shouldBe """
:lib2
dependency name source build file
✔ :lib1 overshot /lib2/build.gradle.kts:
✔ :lib1 unusedDependency /lib2/build.gradle.kts: (6, 3):
dependency name source build file
✔ :lib1 inheritedDependency /lib2/build.gradle.kts: (6, 3):

:lib3
ModuleCheck found 1 issue
"""
}

@Test
fun `inherited main source testFixture in same module with auto-correct should be fixed as normal api`() {

val runner = ModuleCheckRunner(
autoCorrect = true,
settings = baseSettings,
findingFactory = findingFactory,
logger = logger
)

val lib1 = project(":lib1") {
addSource(
"com/modulecheck/lib1/Lib1Class.kt",
"""
package com.modulecheck.lib1

open class Lib1Class
""".trimIndent()
)
addSource(
"com/modulecheck/lib1/test/FakeLib1Class.kt",
"""
package com.modulecheck.lib1.test

import com.modulecheck.lib1.Lib1Class

open class FakeLib1Class : Lib1Class()
""".trimIndent(),
SourceSetName.TEST_FIXTURES
)
}

val lib2 = project(":lib2") {
addDependency(ConfigurationName.implementation, lib1, asTestFixture = true)

buildFile.writeText(
"""
plugins {
kotlin("jvm")
}

dependencies {
implementation(testFixtures(project(path = ":lib1")))
}
""".trimIndent()
)
addSource(
"com/modulecheck/lib2/Lib2Class.kt",
"""
package com.modulecheck.lib2

import com.modulecheck.lib1.Lib1Class
import com.modulecheck.lib1.test.FakeLib2Class

val clazz = Lib1Class()
private val clazz2 = FakeLib2Class()
""".trimIndent()
)
}

runner.run(allProjects()).isSuccess shouldBe true

lib2.buildFile.readText() shouldBe """
plugins {
kotlin("jvm")
}

dependencies {
api(project(path = ":lib1"))
implementation(testFixtures(project(path = ":lib1")))
}
"""

logger.collectReport()
.joinToString()
.clean() shouldBe """
:lib2
dependency name source build file
✔ :lib1 inheritedDependency :lib2 /lib3/build.gradle.kts: (6, 3):
✔ :lib1 inheritedDependency /lib2/build.gradle.kts: (6, 3):

ModuleCheck found 3 issues
ModuleCheck found 1 issue
"""
}

Expand Down