Skip to content

Commit 67a0f99

Browse files
committed
Add new ComputeTypeUsageTest.
1 parent d42dd31 commit 67a0f99

12 files changed

Lines changed: 715 additions & 1 deletion

File tree

api/api.txt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.autonomousapps {
44
public abstract class AbstractExtension {
55
ctor @javax.inject.Inject public AbstractExtension(org.gradle.api.model.ObjectFactory objects, org.gradle.api.invocation.Gradle gradle);
66
method public final org.gradle.api.file.RegularFileProperty adviceOutput();
7+
method public final org.gradle.api.file.RegularFileProperty typeUsageOutput();
78
method public final void app();
89
method public final void registerPostProcessingTask(org.gradle.api.tasks.TaskProvider<? extends com.autonomousapps.AbstractPostProcessingTask> task);
910
field public static final String NAME = "dependencyAnalysis";
@@ -28,6 +29,7 @@ package com.autonomousapps {
2829
method public final void issues(org.gradle.api.Action<com.autonomousapps.extension.IssueHandler> action);
2930
method public final void reporting(org.gradle.api.Action<com.autonomousapps.extension.ReportingHandler> action);
3031
method public final void structure(org.gradle.api.Action<com.autonomousapps.extension.DependenciesHandler> action);
32+
method public final void typeUsage(org.gradle.api.Action<com.autonomousapps.extension.TypeUsageHandler> action);
3133
method public final void usage(org.gradle.api.Action<com.autonomousapps.extension.UsageHandler> action);
3234
method @Deprecated public final void usages(org.gradle.api.Action<com.autonomousapps.extension.UsageHandler> action);
3335
method public final void useTypesafeProjectAccessors(boolean enable);
@@ -44,6 +46,7 @@ package com.autonomousapps {
4446
method public final void abi(org.gradle.api.Action<com.autonomousapps.extension.AbiHandler> action);
4547
method public final void issues(org.gradle.api.Action<com.autonomousapps.extension.ProjectIssueHandler> action);
4648
method public final void structure(org.gradle.api.Action<com.autonomousapps.extension.DependenciesHandler> action);
49+
method public final void typeUsage(org.gradle.api.Action<com.autonomousapps.extension.TypeUsageHandler> action);
4750
}
4851

4952
public final class Flags {
@@ -207,6 +210,13 @@ package com.autonomousapps.extension {
207210
property @org.gradle.api.tasks.Input public abstract org.gradle.api.provider.Property<java.lang.String> postscript;
208211
}
209212

213+
public abstract class TypeUsageHandler {
214+
ctor @javax.inject.Inject public TypeUsageHandler(org.gradle.api.model.ObjectFactory objects);
215+
method public final void excludePackages(java.lang.String... packages);
216+
method public final void excludeRegex(@org.intellij.lang.annotations.Language("RegExp") java.lang.String... patterns);
217+
method public final void excludeTypes(java.lang.String... types);
218+
}
219+
210220
public abstract class SourceSetsHandler implements org.gradle.api.Named {
211221
ctor @javax.inject.Inject public SourceSetsHandler(String sourceSetName, String projectPath, org.gradle.api.model.ObjectFactory objects);
212222
method public String getName();
@@ -505,6 +515,48 @@ package com.autonomousapps.model {
505515
property public String identifier;
506516
}
507517

518+
@com.squareup.moshi.JsonClass(generateAdapter=false) public final class ProjectTypeUsage implements java.lang.Comparable<com.autonomousapps.model.ProjectTypeUsage> {
519+
ctor public ProjectTypeUsage(String projectPath, com.autonomousapps.model.TypeUsageSummary summary, java.util.Map<java.lang.String,java.lang.Integer> internal, java.util.Map<java.lang.String,? extends java.util.Map<java.lang.String,java.lang.Integer>> projectDependencies, java.util.Map<java.lang.String,? extends java.util.Map<java.lang.String,java.lang.Integer>> libraryDependencies);
520+
method public int compareTo(com.autonomousapps.model.ProjectTypeUsage other);
521+
method public String component1();
522+
method public com.autonomousapps.model.TypeUsageSummary component2();
523+
method public java.util.Map<java.lang.String,java.lang.Integer> component3();
524+
method public java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Integer>> component4();
525+
method public java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Integer>> component5();
526+
method public com.autonomousapps.model.ProjectTypeUsage copy(String projectPath, com.autonomousapps.model.TypeUsageSummary summary, java.util.Map<java.lang.String,java.lang.Integer> internal, java.util.Map<java.lang.String,? extends java.util.Map<java.lang.String,java.lang.Integer>> projectDependencies, java.util.Map<java.lang.String,? extends java.util.Map<java.lang.String,java.lang.Integer>> libraryDependencies);
527+
method public java.util.Map<java.lang.String,java.lang.Integer> getInternal();
528+
method public java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Integer>> getLibraryDependencies();
529+
method public java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Integer>> getProjectDependencies();
530+
method public String getProjectPath();
531+
method public com.autonomousapps.model.TypeUsageSummary getSummary();
532+
method public boolean isEmpty();
533+
property public final java.util.Map<java.lang.String,java.lang.Integer> internal;
534+
property public final java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Integer>> libraryDependencies;
535+
property public final java.util.Map<java.lang.String,java.util.Map<java.lang.String,java.lang.Integer>> projectDependencies;
536+
property public final String projectPath;
537+
property public final com.autonomousapps.model.TypeUsageSummary summary;
538+
}
539+
540+
@com.squareup.moshi.JsonClass(generateAdapter=false) public final class TypeUsageSummary {
541+
ctor public TypeUsageSummary(int totalTypes, int totalFiles, int internalTypes, int projectDependencies, int libraryDependencies);
542+
method public int component1();
543+
method public int component2();
544+
method public int component3();
545+
method public int component4();
546+
method public int component5();
547+
method public com.autonomousapps.model.TypeUsageSummary copy(int totalTypes, int totalFiles, int internalTypes, int projectDependencies, int libraryDependencies);
548+
method public int getInternalTypes();
549+
method public int getLibraryDependencies();
550+
method public int getProjectDependencies();
551+
method public int getTotalFiles();
552+
method public int getTotalTypes();
553+
property public final int internalTypes;
554+
property public final int libraryDependencies;
555+
property public final int projectDependencies;
556+
property public final int totalFiles;
557+
property public final int totalTypes;
558+
}
559+
508560
@com.squareup.moshi.JsonClass(generateAdapter=false) public final class Warning implements java.lang.Comparable<com.autonomousapps.model.Warning> {
509561
ctor public Warning(java.util.Set<com.autonomousapps.model.DuplicateClass> duplicateClasses);
510562
method public int compareTo(com.autonomousapps.model.Warning other);
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.jvm
4+
5+
import com.autonomousapps.jvm.projects.TypeUsageProject
6+
import com.autonomousapps.jvm.projects.TypeUsageWithFiltersProject
7+
8+
import static com.autonomousapps.utils.Runner.build
9+
import static com.google.common.truth.Truth.assertThat
10+
11+
final class TypeUsageSpec extends AbstractJvmSpec {
12+
13+
def "generates type usage report (#gradleVersion)"() {
14+
given:
15+
def project = new TypeUsageProject()
16+
gradleProject = project.gradleProject
17+
18+
when:
19+
build(gradleVersion, gradleProject.rootDir, 'computeTypeUsageMain')
20+
21+
then: 'has correct summary'
22+
def usage = project.actualTypeUsage()
23+
assertThat(usage.projectPath).isEqualTo(':proj')
24+
assertThat(usage.summary.totalTypes).isGreaterThan(0)
25+
assertThat(usage.summary.totalFiles).isEqualTo(2)
26+
assertThat(usage.summary.internalTypes).isEqualTo(1)
27+
28+
and: 'tracks internal usage'
29+
assertThat(usage.internal).containsKey('com.example.Example')
30+
// Note: Internal class is not tracked because it's defined but never used
31+
32+
and: 'tracks library dependencies'
33+
assertThat(usage.libraryDependencies).isNotEmpty()
34+
35+
and: 'tracks commons-collections usage'
36+
assertThat(usage.libraryDependencies)
37+
.containsKey('org.apache.commons:commons-collections4')
38+
def commonsUsage = usage.libraryDependencies.get('org.apache.commons:commons-collections4')
39+
assertThat(commonsUsage).containsKey('org.apache.commons.collections4.bag.HashBag')
40+
41+
and: 'tracks kotlin stdlib usage'
42+
assert usage.libraryDependencies.keySet().any { it.startsWith('org.jetbrains.kotlin:kotlin-stdlib') }
43+
44+
where:
45+
gradleVersion << gradleVersions()
46+
}
47+
48+
def "excludes filtered types (#gradleVersion)"() {
49+
given:
50+
def project = new TypeUsageWithFiltersProject()
51+
gradleProject = project.gradleProject
52+
53+
when:
54+
build(gradleVersion, gradleProject.rootDir, 'computeTypeUsageMain')
55+
56+
then: 'excluded packages are not present'
57+
def usage = project.actualTypeUsage()
58+
def allTypes = usage.libraryDependencies.values()
59+
.collectMany { it.keySet() }
60+
61+
assertThat(allTypes).doesNotContain('org.apache.commons.collections4.bag.HashBag')
62+
63+
and: 'excluded types are not present'
64+
assertThat(allTypes).doesNotContain('kotlin.Unit')
65+
66+
and: 'non-excluded types are still present'
67+
assertThat(usage.internal).containsKey('com.example.Example')
68+
69+
where:
70+
gradleVersion << gradleVersions()
71+
}
72+
73+
def "categorizes dependencies correctly (#gradleVersion)"() {
74+
given:
75+
def project = new TypeUsageProject()
76+
gradleProject = project.gradleProject
77+
78+
when:
79+
build(gradleVersion, gradleProject.rootDir, 'computeTypeUsageMain')
80+
81+
then: 'internal types are in internal map'
82+
def usage = project.actualTypeUsage()
83+
assertThat(usage.internal).isNotEmpty()
84+
assertThat(usage.internal).containsKey('com.example.Example')
85+
86+
and: 'library types are in libraryDependencies map'
87+
assertThat(usage.libraryDependencies).isNotEmpty()
88+
89+
and: 'no project dependencies (single-project)'
90+
assertThat(usage.projectDependencies).isEmpty()
91+
92+
and: 'summary counts match'
93+
assertThat(usage.summary.internalTypes).isEqualTo(usage.internal.size())
94+
assertThat(usage.summary.libraryDependencies).isEqualTo(usage.libraryDependencies.size())
95+
assertThat(usage.summary.projectDependencies).isEqualTo(0)
96+
97+
where:
98+
gradleVersion << gradleVersions()
99+
}
100+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.jvm.projects
4+
5+
import com.autonomousapps.AbstractProject
6+
import com.autonomousapps.kit.GradleProject
7+
import com.autonomousapps.kit.Source
8+
import com.autonomousapps.kit.SourceType
9+
import com.autonomousapps.model.ProjectTypeUsage
10+
11+
import static com.autonomousapps.kit.gradle.dependencies.Dependencies.*
12+
13+
final class TypeUsageProject extends AbstractProject {
14+
15+
final GradleProject gradleProject
16+
17+
TypeUsageProject() {
18+
this.gradleProject = build()
19+
}
20+
21+
private GradleProject build() {
22+
return newGradleProjectBuilder()
23+
.withSubproject('proj') { s ->
24+
s.sources = sources
25+
s.withBuildScript { bs ->
26+
bs.plugins = kotlin
27+
bs.dependencies = [
28+
commonsCollections('implementation'),
29+
kotlinStdLib('implementation')
30+
]
31+
}
32+
}
33+
.write()
34+
}
35+
36+
private sources = [
37+
new Source(
38+
SourceType.KOTLIN, "Example", "com/example",
39+
"""\
40+
package com.example
41+
42+
import org.apache.commons.collections4.bag.HashBag
43+
44+
class Example {
45+
private val bag = HashBag<String>()
46+
47+
fun doSomething() {
48+
bag.add("test")
49+
}
50+
}
51+
""".stripIndent()
52+
),
53+
new Source(
54+
SourceType.KOTLIN, "Internal", "com/example",
55+
"""\
56+
package com.example
57+
58+
internal class Internal {
59+
fun helper() = Example()
60+
}
61+
""".stripIndent()
62+
)
63+
]
64+
65+
ProjectTypeUsage actualTypeUsage() {
66+
def typeUsage = gradleProject.singleArtifact(':proj',
67+
com.autonomousapps.internal.OutputPathsKt.getTypeUsagePath('main'))
68+
def adapter = com.autonomousapps.internal.utils.MoshiUtils.MOSHI
69+
.adapter(com.autonomousapps.model.ProjectTypeUsage)
70+
return adapter.fromJson(typeUsage.asPath.text)
71+
}
72+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) 2025. Tony Robalik.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.autonomousapps.jvm.projects
4+
5+
import com.autonomousapps.AbstractProject
6+
import com.autonomousapps.kit.GradleProject
7+
import com.autonomousapps.kit.Source
8+
import com.autonomousapps.kit.SourceType
9+
import com.autonomousapps.model.ProjectTypeUsage
10+
11+
import static com.autonomousapps.kit.gradle.dependencies.Dependencies.*
12+
13+
final class TypeUsageWithFiltersProject extends AbstractProject {
14+
15+
final GradleProject gradleProject
16+
17+
TypeUsageWithFiltersProject() {
18+
this.gradleProject = build()
19+
}
20+
21+
private GradleProject build() {
22+
return newGradleProjectBuilder()
23+
.withSubproject('proj') { s ->
24+
s.sources = sources
25+
s.withBuildScript { bs ->
26+
bs.plugins = kotlin
27+
bs.dependencies = [
28+
commonsCollections('implementation'),
29+
kotlinStdLib('implementation')
30+
]
31+
bs.additions = """\
32+
dependencyAnalysis {
33+
typeUsage {
34+
excludePackages('org.apache.commons')
35+
excludeTypes('kotlin.Unit')
36+
}
37+
}
38+
""".stripIndent()
39+
}
40+
}
41+
.write()
42+
}
43+
44+
private sources = [
45+
new Source(
46+
SourceType.KOTLIN, "Example", "com/example",
47+
"""\
48+
package com.example
49+
50+
import org.apache.commons.collections4.bag.HashBag
51+
52+
class Example {
53+
private val bag = HashBag<String>()
54+
55+
fun doSomething(): Unit {
56+
bag.add("test")
57+
}
58+
}
59+
""".stripIndent()
60+
),
61+
new Source(
62+
SourceType.KOTLIN, "Internal", "com/example",
63+
"""\
64+
package com.example
65+
66+
internal class Internal {
67+
fun helper() = Example()
68+
}
69+
""".stripIndent()
70+
)
71+
]
72+
73+
ProjectTypeUsage actualTypeUsage() {
74+
def typeUsage = gradleProject.singleArtifact(':proj',
75+
com.autonomousapps.internal.OutputPathsKt.getTypeUsagePath('main'))
76+
def adapter = com.autonomousapps.internal.utils.MoshiUtils.MOSHI
77+
.adapter(com.autonomousapps.model.ProjectTypeUsage)
78+
return adapter.fromJson(typeUsage.asPath.text)
79+
}
80+
}

src/main/kotlin/com/autonomousapps/AbstractExtension.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ public abstract class AbstractExtension @Inject constructor(
3434
internal val dependenciesHandler: DependenciesHandler = dslService.get().dependenciesHandler
3535
internal val reportingHandler: ReportingHandler = dslService.get().reportingHandler
3636
internal val usageHandler: UsageHandler = dslService.get().usageHandler
37+
internal val typeUsageHandler: TypeUsageHandler = objects.newInstance(TypeUsageHandler::class.java)
3738

3839
private val adviceOutput = objects.fileProperty()
40+
private val typeUsageOutput = objects.fileProperty()
3941
private var postProcessingTask: TaskProvider<out AbstractPostProcessingTask>? = null
4042

4143
internal var forceAppProject = false
@@ -47,6 +49,13 @@ public abstract class AbstractExtension @Inject constructor(
4749
adviceOutput.set(output)
4850
}
4951

52+
internal fun storeTypeUsageOutput(provider: Provider<RegularFile>) {
53+
val output = objects.fileProperty().also {
54+
it.set(provider)
55+
}
56+
typeUsageOutput.set(output)
57+
}
58+
5059
/**
5160
* Returns the output from the project-level advice.
5261
*
@@ -55,6 +64,14 @@ public abstract class AbstractExtension @Inject constructor(
5564
@Suppress("MemberVisibilityCanBePrivate") // explicit API
5665
public fun adviceOutput(): RegularFileProperty = adviceOutput
5766

67+
/**
68+
* Returns the output from the project-level type usage analysis.
69+
*
70+
* Never null, but may _contain_ a null value. Use with [RegularFileProperty.getOrNull].
71+
*/
72+
@Suppress("MemberVisibilityCanBePrivate") // explicit API
73+
public fun typeUsageOutput(): RegularFileProperty = typeUsageOutput
74+
5875
/**
5976
* Whether to force the project being treated as an app project even if only the `java` plugin is applied.
6077
*/

0 commit comments

Comments
 (0)