diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfiguration.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfiguration.kt index 355ddc5..7150166 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfiguration.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfiguration.kt @@ -2,8 +2,9 @@ package com.dropbox.affectedmoduledetector import com.dropbox.affectedmoduledetector.util.toOsSpecificPath import java.io.File +import java.io.Serializable -class AffectedModuleConfiguration { +class AffectedModuleConfiguration : Serializable { /** * Implementation of [AffectedModuleTaskType] for easy adding of custom gradle task to diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetector.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetector.kt index 84f855d..c00ca92 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetector.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetector.kt @@ -25,14 +25,20 @@ import com.dropbox.affectedmoduledetector.AffectedModuleDetector.Companion.CHANG import com.dropbox.affectedmoduledetector.AffectedModuleDetector.Companion.DEPENDENT_PROJECTS_ARG import com.dropbox.affectedmoduledetector.AffectedModuleDetector.Companion.ENABLE_ARG import com.dropbox.affectedmoduledetector.AffectedModuleDetector.Companion.MODULES_ARG -import com.dropbox.affectedmoduledetector.commitshaproviders.CommitShaProvider +import com.dropbox.affectedmoduledetector.commitshaproviders.CommitShaProviderConfiguration import com.dropbox.affectedmoduledetector.util.toPathSections +import org.gradle.api.Action import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.UnknownDomainObjectException +import org.gradle.api.file.DirectoryProperty import org.gradle.api.logging.Logger +import org.gradle.api.provider.Provider +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters +import org.gradle.api.services.BuildServiceSpec import java.io.File +import java.io.Serializable /** * The subsets we allow the projects to be partitioned into. @@ -60,7 +66,7 @@ enum class ProjectSubset { DEPENDENT_PROJECTS, CHANGED_PROJECTS, ALL_AFFECTED_PR * An identifier for a project, ensuring that projects are always identified by their path. */ @JvmInline -value class ProjectPath(val path: String) +value class ProjectPath(val path: String) : Serializable /** * A utility class that can discover which files are changed based on git history. @@ -82,13 +88,13 @@ value class ProjectPath(val path: String) * Since this needs to check project dependency graph to work, it cannot be accessed before * all projects are loaded. Doing so will throw an exception. */ -abstract class AffectedModuleDetector { +abstract class AffectedModuleDetector(protected val logger: Logger?) { /** * Returns whether this project was affected by current changes. * * Can only be called during the execution phase */ - abstract fun shouldInclude(project: Project): Boolean + abstract fun shouldInclude(project: ProjectPath): Boolean /** * Returns true if at least one project has been affected @@ -102,7 +108,7 @@ abstract class AffectedModuleDetector { * * Can be called during the configuration or execution phase */ - abstract fun isProjectProvided2(project: Project): Boolean + abstract fun isProjectProvided2(project: ProjectPath): Boolean /** * Returns the set that the project belongs to. The set is one of the ProjectSubset above. @@ -112,8 +118,20 @@ abstract class AffectedModuleDetector { */ abstract fun getSubset(project: Project): ProjectSubset + /** + * Returns a set of all projects that are affected by the current changes. This includes both + * projects that have changed files and projects that depend on changed projects. + */ + abstract fun getAllAffectedProjects(): Set + + /** + * Returns a set of all projects that have changed files + */ + abstract fun getAllChangedProjects(): Set + companion object { private const val ROOT_PROP_NAME = "AffectedModuleDetectorPlugin" + private const val SERVICE_NAME = ROOT_PROP_NAME + "BuildService" private const val MODULES_ARG = "affected_module_detector.modules" private const val DEPENDENT_PROJECTS_ARG = "affected_module_detector.dependentProjects" private const val CHANGED_PROJECTS_ARG = "affected_module_detector.changedProjects" @@ -126,12 +144,17 @@ abstract class AffectedModuleDetector { "Project provided must be root, project was ${rootProject.path}" } + val instance = AffectedModuleDetectorWrapper() + rootProject.extensions.add(ROOT_PROP_NAME, instance) + val enabled = isProjectEnabled(rootProject) if (!enabled) { - setInstance( - rootProject, - AcceptAll() - ) + val provider = + setupWithParams(rootProject) { spec -> + val params = spec.parameters + params.acceptAll = true + } + instance.wrapped = provider return } isConfigured = true @@ -156,54 +179,77 @@ abstract class AffectedModuleDetector { "extension added." } - val logger = - ToStringLogger.createWithLifecycle( - rootProject, - config.logFilename, - config.logFolder - ) + val distDir = if (config.logFolder != null) { + val distDir = File(config.logFolder!!) + if (!distDir.exists()) { + distDir.mkdirs() + } + distDir + } else { + rootProject.rootDir + } + + val outputFile = distDir.resolve(config.logFilename).also { + it.writeText("") + } + val logger = FileLogger(outputFile) val modules = getModulesProperty( rootProject ) - AffectedModuleDetectorImpl( - rootProject = rootProject, - logger = logger, - ignoreUnknownProjects = true, - projectSubset = subset, - modules = modules, - config = config - ).also { - logger.info("Using real detector with $subset") - setInstance( - rootProject, - it - ) + val gitClient = GitClientImpl( + rootProject.projectDir, + logger, + commitShaProviderConfiguration = CommitShaProviderConfiguration( + type = config.compareFrom, + specifiedBranch = config.specifiedBranch, + specifiedSha = config.specifiedRawCommitSha, + top = config.top, + includeUncommitted = config.includeUncommitted + ), + ignoredFiles = config.ignoredFiles + ) + + logger.lifecycle("projects evaluated") + val projectGraph = ProjectGraph(rootProject) + val dependencyTracker = DependencyTracker(rootProject, logger.toLogger()) + val provider = setupWithParams(rootProject) { spec -> + val parameters = spec.parameters + parameters.acceptAll = false + parameters.projectGraph = projectGraph + parameters.dependencyTracker = dependencyTracker + parameters.log = logger + parameters.ignoreUnknownProjects = true + parameters.projectSubset = subset + parameters.modules = modules + parameters.config = config + parameters.gitChangedFilesProvider = gitClient.findChangedFiles(rootProject) + parameters.gitRoot.set(gitClient.getGitRoot()) } + logger.info("Using real detector with $subset") + instance.wrapped = provider } - private fun setInstance( + private fun setupWithParams( rootProject: Project, - detector: AffectedModuleDetector - ) { + configureAction: Action> + ): Provider { if (!rootProject.isRoot) { - throw IllegalArgumentException( - "This should've been the root project, instead found ${rootProject.path}" - ) + throw IllegalArgumentException("this should've been the root project") } - rootProject.extensions.add(ROOT_PROP_NAME, detector) + return rootProject.gradle.sharedServices.registerIfAbsent( + SERVICE_NAME, + AffectedModuleDetectorLoader::class.java, + configureAction + ) } private fun getInstance(project: Project): AffectedModuleDetector? { val extensions = project.rootProject.extensions - - return try { - extensions.getByName(ROOT_PROP_NAME) as? AffectedModuleDetector - } catch (e: UnknownDomainObjectException) { - null - } + val detector = extensions.findByName(ROOT_PROP_NAME) as? AffectedModuleDetector + return detector!! } private fun getOrThrow(project: Project): AffectedModuleDetector { @@ -243,7 +289,7 @@ abstract class AffectedModuleDetector { task.onlyIf { getOrThrow( task.project - ).shouldInclude(task.project) + ).shouldInclude(task.project.projectPath) } } @@ -257,7 +303,7 @@ abstract class AffectedModuleDetector { fun isProjectAffected(project: Project): Boolean { return getOrThrow( project - ).shouldInclude(project) + ).shouldInclude(project.projectPath) } /** @@ -272,6 +318,28 @@ abstract class AffectedModuleDetector { ).hasAffectedProjects() } + /** + * Returns a set of all affected project paths + * + * Can only be called during the execution phase + */ + fun affectedProjects(project: Project): Set { + return getOrThrow( + project + ).getAllAffectedProjects() + } + + /** + * Returns a set of all changed project paths + * + * Can only be called during the execution phase + */ + fun changedProjects(project: Project): Set { + return getOrThrow( + project + ).getAllChangedProjects() + } + /** * Returns true if the project was provided via [MODULES_ARG] or no [MODULES_ARG] was set * @@ -290,28 +358,109 @@ abstract class AffectedModuleDetector { } } +class AffectedModuleDetectorWrapper : AffectedModuleDetector(logger = null) { + var wrapped: Provider? = null + + fun getOrThrow(): AffectedModuleDetector { + return wrapped?.get()?.detector + ?: throw GradleException( + """ + Tried to get the affected module detector implementation too early. + You cannot access it until all projects are evaluated. + """ + .trimIndent() + ) + } + + override fun shouldInclude(project: ProjectPath): Boolean { + return getOrThrow().shouldInclude(project) + } + + override fun hasAffectedProjects(): Boolean { + return getOrThrow().hasAffectedProjects() + } + + override fun isProjectProvided2(project: ProjectPath): Boolean { + return getOrThrow().isProjectProvided2(project) + } + + override fun getSubset(project: Project): ProjectSubset { + return getOrThrow().getSubset(project) + } + + override fun getAllAffectedProjects(): Set { + return getOrThrow().getAllAffectedProjects() + } + + override fun getAllChangedProjects(): Set { + return getOrThrow().getAllChangedProjects() + } +} + +abstract class AffectedModuleDetectorLoader : + BuildService { + interface Parameters : BuildServiceParameters { + var acceptAll: Boolean + var projectGraph: ProjectGraph + var dependencyTracker: DependencyTracker + var log: FileLogger + var ignoreUnknownProjects: Boolean + var projectSubset: ProjectSubset + var modules: Set? + var gitChangedFilesProvider: Provider> + var config: AffectedModuleConfiguration + val gitRoot: DirectoryProperty + } + + val detector: AffectedModuleDetector by lazy { + val logger = parameters.log.toLogger() + if (parameters.acceptAll) { + AcceptAll(logger) + } else { + AffectedModuleDetectorImpl( + projectGraph = parameters.projectGraph, + dependencyTracker = parameters.dependencyTracker, + logger = logger, + ignoreUnknownProjects = parameters.ignoreUnknownProjects, + projectSubset = parameters.projectSubset, + modules = parameters.modules, + config = parameters.config, + changedFilesProvider = parameters.gitChangedFilesProvider, + gitRoot = parameters.gitRoot.get().asFile + ) + } + } +} + /** * Implementation that accepts everything without checking. */ private class AcceptAll( - private val wrapped: AffectedModuleDetector? = null, - private val logger: Logger? = null -) : AffectedModuleDetector() { - override fun shouldInclude(project: Project): Boolean { - val wrappedResult = wrapped?.shouldInclude(project) - logger?.info("[AcceptAll] wrapper returned $wrappedResult but I'll return true") + logger: Logger? = null +) : AffectedModuleDetector(logger) { + override fun shouldInclude(project: ProjectPath): Boolean { + logger?.info("[AcceptAll] acceptAll.shouldInclude returning true") return true } override fun hasAffectedProjects() = true - override fun isProjectProvided2(project: Project) = true + override fun isProjectProvided2(project: ProjectPath) = true override fun getSubset(project: Project): ProjectSubset { - val wrappedResult = wrapped?.getSubset(project) - logger?.info("[AcceptAll] wrapper returned $wrappedResult but I'll return CHANGED_PROJECTS") + logger?.info("[AcceptAll] AcceptAll.getSubset returning CHANGED_PROJECTS") return ProjectSubset.CHANGED_PROJECTS } + + override fun getAllAffectedProjects(): Set { + logger?.info("[AcceptAll] AcceptAll.getAllAffectedProjects returning empty set") + return emptySet() + } + + override fun getAllChangedProjects(): Set { + logger?.info("[AcceptAll] AcceptAll.getAllChangedProjects returning empty set") + return emptySet() + } } /** @@ -321,48 +470,31 @@ private class AcceptAll( * * When a file in a module is changed, all modules that depend on it are considered as changed. */ -class AffectedModuleDetectorImpl constructor( - private val rootProject: Project, - private val logger: Logger?, +class AffectedModuleDetectorImpl( + private val projectGraph: ProjectGraph, + private val dependencyTracker: DependencyTracker, + logger: Logger?, // used for debugging purposes when we want to ignore non module files private val ignoreUnknownProjects: Boolean = false, private val projectSubset: ProjectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - private val injectedGitClient: GitClient? = null, private val modules: Set? = null, - private val config: AffectedModuleConfiguration -) : AffectedModuleDetector() { + private val config: AffectedModuleConfiguration, + private val changedFilesProvider: Provider>, + private val gitRoot: File, +) : AffectedModuleDetector(logger) { init { logger?.info("Modules provided: ${modules?.joinToString(separator = ",")}") } - private val git by lazy { - injectedGitClient ?: GitClientImpl( - rootProject.projectDir, - logger, - commitShaProvider = CommitShaProvider.fromString(config.compareFrom, config.specifiedBranch, config.specifiedRawCommitSha), - ignoredFiles = config.ignoredFiles - ) - } - - private val dependencyTracker by lazy { - DependencyTracker(rootProject, logger) - } - - private val allProjects by lazy { - rootProject.subprojects.associateBy { it.projectPath } - } - - private val projectGraph by lazy { - ProjectGraph(rootProject, git.getGitRoot(), logger) - } + private val allProjects by lazy { projectGraph.allProjects } val affectedProjects by lazy { findAffectedProjects() } private val changedProjects by lazy { - findChangedProjects(config.top, config.includeUncommitted) + findChangedProjects() } private val dependentProjects by lazy { @@ -373,15 +505,14 @@ class AffectedModuleDetectorImpl constructor( private var unknownFiles: MutableSet = mutableSetOf() - override fun shouldInclude(project: Project): Boolean { - val isRootProject = project.isRoot - val isProjectAffected = affectedProjects.contains(project.projectPath) + override fun shouldInclude(project: ProjectPath): Boolean { + val isProjectAffected = affectedProjects.contains(project) val isProjectProvided = isProjectProvided2(project) - val isModuleExcludedByName = config.excludedModules.contains(project.name) + val isModuleExcludedByName = config.excludedModules.contains(project.path) || config.excludedModules.contains(project.path.substringAfter(':')) val isModuleExcludedByRegex = config.excludedModules.any { project.path.matches(it.toRegex()) } val isNotModuleExcluded = !(isModuleExcludedByName || isModuleExcludedByRegex) - val shouldInclude = (isRootProject || (isProjectAffected && isProjectProvided)) && isNotModuleExcluded + val shouldInclude = isProjectAffected && isProjectProvided && isNotModuleExcluded logger?.info("checking whether I should include ${project.path} and my answer is $shouldInclude") return shouldInclude @@ -389,7 +520,7 @@ class AffectedModuleDetectorImpl constructor( override fun hasAffectedProjects() = affectedProjects.isNotEmpty() - override fun isProjectProvided2(project: Project): Boolean { + override fun isProjectProvided2(project: ProjectPath): Boolean { if (modules == null) return true return modules.contains(project.path) } @@ -408,37 +539,40 @@ class AffectedModuleDetectorImpl constructor( } } + override fun getAllAffectedProjects(): Set { + return affectedProjects + } + + override fun getAllChangedProjects(): Set { + return changedProjects + } + /** * Finds only the set of projects that were directly changed in the commit. * * Also populates the unknownFiles var which is used in findAffectedProjects */ - private fun findChangedProjects( - top: Sha, - includeUncommitted: Boolean = true - ): Map { - git.findChangedFiles( - top = top, - includeUncommitted = includeUncommitted - ).forEach { fileName -> + private fun findChangedProjects(): Set { + (changedFilesProvider.getOrNull() ?: return allProjects).forEach { fileName -> if (affectsAllModules(fileName)) { + logger?.info("File $fileName affects all modules") return allProjects } changedFiles.add(fileName) } - val changedProjects = mutableMapOf() + val changedProjects = mutableSetOf() for (filePath in changedFiles) { val containingProject = findContainingProject(filePath) if (containingProject == null) { unknownFiles.add(filePath) logger?.info( - "Couldn't find containing project for file$filePath. " + + "Couldn't find containing project for file $filePath. " + "Adding to unknownFiles." ) } else { - changedProjects[containingProject.projectPath] = containingProject + changedProjects.add(containingProject) logger?.info( "For file $filePath containing project is $containingProject. " + "Adding to changedProjects." @@ -453,10 +587,10 @@ class AffectedModuleDetectorImpl constructor( * Gets all dependent projects from the set of changedProjects. This doesn't include the * original changedProjects. Always build is still here to ensure at least 1 thing is built */ - private fun findDependentProjects(): Map { - return changedProjects.flatMap { (_, project) -> - dependencyTracker.findAllDependents(project).entries - }.associate { it.key to it.value } + private fun findDependentProjects(): Set { + return changedProjects.flatMap { path -> + dependencyTracker.findAllDependents(path) + }.toSet() } /** @@ -472,7 +606,7 @@ class AffectedModuleDetectorImpl constructor( * Also detects modules whose tests are codependent at runtime. */ @Suppress("ComplexMethod") - private fun findAffectedProjects(): Map { + private fun findAffectedProjects(): Set { // In this case we don't care about any of the logic below, we're only concerned with // running the changed projects in this test runner if (projectSubset == ProjectSubset.CHANGED_PROJECTS) { @@ -510,20 +644,18 @@ class AffectedModuleDetectorImpl constructor( } private fun affectsAllModules(relativeFilePath: String): Boolean { - logger?.info("Paths affecting all modules: ${config.pathsAffectingAllModules}") - val rootProjectDir = if (config.baseDir != null) { File(config.baseDir!!) } else { - rootProject.projectDir + File(projectGraph.getRootProjectPath()!!.path) } - val pathSections = relativeFilePath.toPathSections(rootProjectDir, git.getGitRoot()) + val pathSections = relativeFilePath.toPathSections(rootProjectDir, gitRoot) val projectRelativePath = pathSections.joinToString(File.separatorChar.toString()) return config.pathsAffectingAllModules.any { projectRelativePath.startsWith(it) } } - private fun findContainingProject(filePath: String): Project? { + private fun findContainingProject(filePath: String): ProjectPath? { return projectGraph.findContainingProject(filePath).also { logger?.info("search result for $filePath resulted in ${it?.path}") } diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt index 1c9dd6a..c058be6 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorPlugin.kt @@ -48,11 +48,11 @@ class AffectedModuleDetectorPlugin : Plugin { registerSubprojectConfiguration(project) registerMainConfiguration(project) - AffectedModuleDetector.configure(project) registerCustomTasks(project) registerTestTasks(project) project.gradle.projectsEvaluated { + AffectedModuleDetector.configure(project) filterAndroidTests(project) filterJvmTests(project) filterCustomTasks(project) @@ -229,12 +229,11 @@ class AffectedModuleDetectorPlugin : Plugin { val tracker = DependencyTracker(project, null) project.tasks.configureEach { task -> if (task.name.contains(ANDROID_TEST_PATTERN)) { - tracker.findAllDependents(project).forEach { (_, dependentProject) -> - dependentProject.tasks.forEach { dependentTask -> + tracker.findAllDependents(project.projectPath).forEach { dependentProject -> + project.rootProject.findProject(dependentProject.path)?.tasks?.forEach { dependentTask -> AffectedModuleDetector.configureTaskGuard(dependentTask) } } - AffectedModuleDetector.configureTaskGuard(task) } } } diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleTaskType.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleTaskType.kt index 903ba34..6bb27aa 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleTaskType.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleTaskType.kt @@ -1,12 +1,14 @@ package com.dropbox.affectedmoduledetector +import java.io.Serializable + /** * For creating a custom task which will be run only if module was affected * just override fields in your data structure which implements this interface. * * Your data structure must override all this variable */ -interface AffectedModuleTaskType { +interface AffectedModuleTaskType : Serializable { /** * Console command `./gradlew [commandByImpact]` which will run the original diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/DependencyTracker.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/DependencyTracker.kt index 1a1c343..3d5f519 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/DependencyTracker.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/DependencyTracker.kt @@ -22,54 +22,43 @@ package com.dropbox.affectedmoduledetector import org.gradle.api.Project import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.logging.Logger +import java.io.Serializable /** - * Utility class that traverses all project dependencies and discover which modules depend - * on each other. This is mainly used by [AffectedModuleDetector] to find out which projects - * should be run. + * Utility class that traverses all project dependencies and discover which modules depend on each + * other. This is mainly used by [AffectedModuleDetector] to find out which projects should be run. */ -class DependencyTracker constructor( - private val rootProject: Project, - private val logger: Logger? -) { - private val dependentList: Map> by lazy { - val result = mutableMapOf>() +class DependencyTracker(rootProject: Project, logger: Logger?) : Serializable { + val dependentList: Map> + + init { + val result = mutableMapOf>() + val stringBuilder = StringBuilder() rootProject.subprojects.forEach { project -> - logger?.info("checking ${project.path} for dependencies") project.configurations.forEach { config -> - logger?.info("checking config ${project.path}/$config for dependencies") - config - .dependencies - .filterIsInstance(ProjectDependency::class.java) - .forEach { - logger?.info( - "there is a dependency from ${project.path} to " + - it.dependencyProject.path - ) - result.getOrPut(it.dependencyProject) { mutableSetOf() } - .add(project) - } + config.dependencies.filterIsInstance().forEach { + stringBuilder.append( + "there is a dependency from ${project.path} (${config.name}) to " + + it.dependencyProject.path + + "\n" + ) + result.getOrPut(it.dependencyProject.projectPath) { mutableSetOf() }.add(project.projectPath) + } } } - result + logger?.info(stringBuilder.toString()) + dependentList = result } - fun findAllDependents(project: Project): Map { - logger?.info("finding dependents of ${project.path}") - val result = mutableMapOf() - fun addAllDependents(project: Project) { - if (result.put(project.projectPath, project) == null) { - dependentList[project]?.forEach(::addAllDependents) + fun findAllDependents(projectPath: ProjectPath): Set { + val result = mutableSetOf() + fun addAllDependents(projectPath: ProjectPath) { + if (result.add(projectPath)) { + dependentList[projectPath]?.forEach(::addAllDependents) } } - addAllDependents(project) - logger?.info( - "dependents of ${project.path} is ${result.map { (path, _) -> - path.path - }}" - ) - // the project isn't a dependent of itself - result.remove(project.projectPath) - return result + addAllDependents(projectPath) + // the projectPath isn't a dependent of itself + return result.minus(projectPath) } } diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/FileLogger.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/FileLogger.kt new file mode 100644 index 0000000..7fb60e2 --- /dev/null +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/FileLogger.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.dropbox.affectedmoduledetector + +import org.gradle.api.logging.LogLevel +import org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger +import org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext +import org.gradle.internal.time.Clock +import java.io.File +import java.io.Serializable + +/** Gradle logger that logs to a file */ +class FileLogger(val file: File) : Serializable { + @Transient var impl: OutputEventListenerBackedLogger? = null + + fun toLogger(): OutputEventListenerBackedLogger { + if (impl == null) { + impl = + OutputEventListenerBackedLogger( + "amd", + OutputEventListenerBackedLoggerContext(Clock { System.currentTimeMillis() }) + .also { + it.level = LogLevel.DEBUG + it.setOutputEventListener { file.appendText(it.toString() + "\n") } + }, + Clock { System.currentTimeMillis() } + ) + } + return impl!! + } + + fun lifecycle(text: String) { + toLogger().lifecycle(text) + } + + fun info(text: String) { + toLogger().info(text) + } +} diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/GitClient.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/GitClient.kt index d8e0913..5b3e836 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/GitClient.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/GitClient.kt @@ -20,18 +20,29 @@ package com.dropbox.affectedmoduledetector -import com.dropbox.affectedmoduledetector.commitshaproviders.CommitShaProvider +import com.dropbox.affectedmoduledetector.GitClientImpl.Companion.CHANGED_FILES_CMD_PREFIX +import com.dropbox.affectedmoduledetector.commitshaproviders.CommitShaProviderConfiguration +import com.dropbox.affectedmoduledetector.commitshaproviders.ForkCommit +import com.dropbox.affectedmoduledetector.commitshaproviders.PreviousCommit +import com.dropbox.affectedmoduledetector.commitshaproviders.SpecifiedBranchCommit +import com.dropbox.affectedmoduledetector.commitshaproviders.SpecifiedBranchCommitMergeBase +import com.dropbox.affectedmoduledetector.commitshaproviders.SpecifiedRawCommitSha import com.dropbox.affectedmoduledetector.util.toOsSpecificLineEnding import com.dropbox.affectedmoduledetector.util.toOsSpecificPath +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty import org.gradle.api.logging.Logger +import org.gradle.api.provider.Provider +import org.gradle.api.provider.SetProperty +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters import java.io.File import java.util.concurrent.TimeUnit interface GitClient { fun findChangedFiles( - top: Sha = "HEAD", - includeUncommitted: Boolean = false - ): List + project: Project, + ): Provider> fun getGitRoot(): File @@ -62,17 +73,14 @@ typealias Sha = String * A simple git client that uses system process commands to communicate with the git setup in the * given working directory. */ +@Suppress("UnstableApiUsage") internal class GitClientImpl( /** * The root location for git */ private val workingDir: File, - private val logger: Logger?, - private val commandRunner: GitClient.CommandRunner = RealCommandRunner( - workingDir = workingDir, - logger = logger - ), - private val commitShaProvider: CommitShaProvider, + private val logger: FileLogger?, + private val commitShaProviderConfiguration: CommitShaProviderConfiguration, private val ignoredFiles: Set? ) : GitClient { @@ -80,26 +88,132 @@ internal class GitClientImpl( * Finds changed file paths */ override fun findChangedFiles( - top: Sha, - includeUncommitted: Boolean - ): List { - val sha = commitShaProvider.get(commandRunner) + project: Project, + ): Provider> { + return project.providers.of(GitChangedFilesSource::class.java) { + it.parameters.commitShaProvider = commitShaProviderConfiguration + it.parameters.workingDir.set(workingDir) + it.parameters.logger = logger + it.parameters.ignoredFiles.set(ignoredFiles) + } + } + + private fun findGitDirInParentFilepath(filepath: File): File? { + var curDirectory: File = filepath + while (curDirectory.path != "/") { + if (File("$curDirectory/.git").exists()) { + return curDirectory + } + curDirectory = curDirectory.parentFile + } + return null + } + + override fun getGitRoot(): File { + return findGitDirInParentFilepath(workingDir) ?: workingDir + } + + companion object { + // -M95 is necessary to detect certain file moves. See https://github.com/dropbox/AffectedModuleDetector/issues/60 + const val CHANGED_FILES_CMD_PREFIX = "git --no-pager diff --name-only -M95" + } +} + +private class RealCommandRunner( + private val workingDir: File, + private val logger: Logger? +) : GitClient.CommandRunner { + override fun execute(command: String): String { + val parts = command.split("\\s".toRegex()) + logger?.info("running command $command in $workingDir") + val proc = ProcessBuilder(*parts.toTypedArray()) + .directory(workingDir) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + val stdout = proc + .inputStream + .bufferedReader() + .readText() + val stderr = proc + .errorStream + .bufferedReader() + .readText() + + proc.waitFor(5, TimeUnit.MINUTES) + + val message = stdout + stderr + if (stderr != "") { + logger?.error("Response: $message") + } else { + logger?.info("Response: $message") + } + check(proc.exitValue() == 0) { "Nonzero exit value running git command." } + return stdout + } + + override fun executeAndParse(command: String): List { + return execute(command).toOsSpecificLineEnding() + .split(System.lineSeparator()) + .map { it.toOsSpecificPath() } + .filterNot { it.isEmpty() } + } + + override fun executeAndParseFirst(command: String): String { + return requireNotNull( + executeAndParse(command) + .firstOrNull() + ?.split(" ") + ?.firstOrNull() + ) { + "No value from command: $command provided" + } + } +} + +/** Provides changed files since the last merge by calling git in [Parameters.workingDir]. */ +@Suppress("UnstableApiUsage") +internal abstract class GitChangedFilesSource : + ValueSource, GitChangedFilesSource.Parameters> { + interface Parameters : ValueSourceParameters { + var commitShaProvider: CommitShaProviderConfiguration + val workingDir: DirectoryProperty + var logger: FileLogger? + val ignoredFiles: SetProperty + } + + private val gitRoot by lazy { + findGitDirInParentFilepath(parameters.workingDir.get().asFile) + } + + private val commandRunner: GitClient.CommandRunner by lazy { + RealCommandRunner( + workingDir = gitRoot ?: parameters.workingDir.get().asFile, + logger = null + ) + } + + override fun obtain(): List { + val top = parameters.commitShaProvider.top + val sha = getSha() // use this if we don't want local changes val changedFiles = commandRunner.executeAndParse( - if (includeUncommitted) { + if (parameters.commitShaProvider.includeUncommitted) { "$CHANGED_FILES_CMD_PREFIX $sha" } else { "$CHANGED_FILES_CMD_PREFIX $top..$sha" } ) - return ignoredFiles + return parameters.ignoredFiles.orNull .orEmpty() .map { it.toRegex() } .foldRight(changedFiles) { ignoredFileRegex: Regex, fileList: List -> fileList.filterNot { it.matches(ignoredFileRegex) } } + .filterNot { it.isEmpty() } } private fun findGitDirInParentFilepath(filepath: File): File? { @@ -113,111 +227,32 @@ internal class GitClientImpl( return null } - @Suppress("LongParameterList") - private fun parseCommitLogString( - commitLogString: String, - commitStartDelimiter: String, - commitSHADelimiter: String, - subjectDelimiter: String, - authorEmailDelimiter: String, - localProjectDir: String - ): List { - // Split commits string out into individual commits (note: this removes the deliminter) - val gitLogStringList: List? = commitLogString.split(commitStartDelimiter) - val commitLog: MutableList = mutableListOf() - gitLogStringList?.filter { gitCommit -> - gitCommit.trim() != "" - }?.forEach { gitCommit -> - commitLog.add( - Commit( - gitCommit, - localProjectDir, - commitSHADelimiter = commitSHADelimiter, - subjectDelimiter = subjectDelimiter, - authorEmailDelimiter = authorEmailDelimiter - ) - ) - } - return commitLog.toList() - } - - override fun getGitRoot(): File { - return findGitDirInParentFilepath(workingDir) ?: workingDir - } - - private class RealCommandRunner( - private val workingDir: File, - private val logger: Logger? - ) : GitClient.CommandRunner { - override fun execute(command: String): String { - val parts = command.split("\\s".toRegex()) - logger?.info("running command $command in $workingDir") - val proc = ProcessBuilder(*parts.toTypedArray()) - .directory(workingDir) - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectError(ProcessBuilder.Redirect.PIPE) - .start() - - val stdout = proc - .inputStream - .bufferedReader() - .readText() - val stderr = proc - .errorStream - .bufferedReader() - .readText() - - proc.waitFor(5, TimeUnit.MINUTES) - - val message = stdout + stderr - if (stderr != "") { - logger?.error("Response: $message") - } else { - logger?.info("Response: $message") + private fun getSha(): Sha { + val specifiedBranch = parameters.commitShaProvider.specifiedBranch + val specifiedSha = parameters.commitShaProvider.specifiedSha + val type = when (parameters.commitShaProvider.type) { + "PreviousCommit" -> PreviousCommit() + "ForkCommit" -> ForkCommit() + "SpecifiedBranchCommit" -> { + requireNotNull(specifiedBranch) { + "Specified branch must be defined" + } + SpecifiedBranchCommit(specifiedBranch) } - check(proc.exitValue() == 0) { "Nonzero exit value running git command." } - return stdout - } - - override fun executeAndParse(command: String): List { - return execute(command).toOsSpecificLineEnding() - .split(System.lineSeparator()) - .map { it.toOsSpecificPath() } - .filterNot { it.isEmpty() } - } - - override fun executeAndParseFirst(command: String): String { - return requireNotNull( - executeAndParse(command) - .firstOrNull() - ?.split(" ") - ?.firstOrNull() - ) { - "No value from command: $command provided" + "SpecifiedBranchCommitMergeBase" -> { + requireNotNull(specifiedBranch) { + "Specified branch must be defined" + } + SpecifiedBranchCommitMergeBase(specifiedBranch) } + "SpecifiedRawCommitSha" -> { + requireNotNull(specifiedSha) { + "Provide a Commit SHA for the specifiedRawCommitSha property when using SpecifiedRawCommitSha comparison strategy." + } + SpecifiedRawCommitSha(specifiedSha) + } + else -> throw IllegalArgumentException("Unsupported compareFrom type") } - } - - companion object { - // -M95 is necessary to detect certain file moves. See https://github.com/dropbox/AffectedModuleDetector/issues/60 - const val CHANGED_FILES_CMD_PREFIX = "git --no-pager diff --name-only -M95" + return type.get(commandRunner) } } - -/** - * Class implementation of a git commit. It uses the input delimiters to parse the commit - * - * @property gitCommit a string representation of a git commit - * @property projectDir the project directory for which to parse file paths from a commit - * @property commitSHADelimiter the term to use to search for the commit SHA - * @property subjectDelimiter the term to use to search for the subject (aka commit summary) - * message - * @property authorEmailDelimiter the term to use to search for the author email - */ -data class Commit( - val gitCommit: String, - val projectDir: String, - private val commitSHADelimiter: String = "_CommitSHA:", - private val subjectDelimiter: String = "_Subject:", - private val authorEmailDelimiter: String = "_Author:" -) diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ProjectGraph.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ProjectGraph.kt index ba97104..75c7d58 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ProjectGraph.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ProjectGraph.kt @@ -1,18 +1,18 @@ /* -* Copyright 2018 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /* * Copyright (c) 2020, Dropbox, Inc. All rights reserved. @@ -20,23 +20,28 @@ package com.dropbox.affectedmoduledetector -import com.dropbox.affectedmoduledetector.util.toPathSections import org.gradle.api.Project import org.gradle.api.logging.Logger import java.io.File +import java.io.Serializable -/** - * Creates a project graph for fast lookup by file path - */ -internal class ProjectGraph(project: Project, val gitRoot: File, val logger: Logger? = null) { +/** Creates a project graph for fast lookup by file path */ +class ProjectGraph(project: Project, logger: Logger? = null) : Serializable { private val rootNode: Node - private val rootProjectDir: File init { + // always use cannonical file: b/112205561 logger?.info("initializing ProjectGraph") - rootNode = Node(logger) - rootProjectDir = project.getSupportRootFolder().canonicalFile - project.subprojects.forEach { + rootNode = Node() + val rootProjectDir = project.getSupportRootFolder().canonicalFile + val projects = + if (rootProjectDir == project.rootDir.canonicalFile) { + project.subprojects + } else { + // include root project if it is not the main AndroidX project. + project.subprojects + project + } + projects.forEach { logger?.info("creating node for ${it.path}") val relativePath = it.projectDir.canonicalFile.toRelativeString(rootProjectDir) val sections = relativePath.split(File.separatorChar) @@ -49,55 +54,61 @@ internal class ProjectGraph(project: Project, val gitRoot: File, val logger: Log val realSections = sections.filter { section -> section != ".." } logger?.info("relative path: $relativePath , sections: $realSections") - val leaf = realSections.fold(rootNode) { left, right -> - left.getOrCreateNode(right) - } - leaf.project = it + val leaf = realSections.fold(rootNode) { left, right -> left.getOrCreateNode(right) } + leaf.projectPath = it.projectPath } - logger?.info("finished creating ProjectGraph $rootNode") + logger?.info("finished creating ProjectGraph") } /** - * Finds the project that contains the given file. - * The file's path prefix should match the project's path. + * Finds the project that contains the given file. The file's path prefix should match the + * project's path. */ - fun findContainingProject(relativeFilePath: String): Project? { - val pathSections = relativeFilePath.toPathSections(rootProjectDir, gitRoot) + fun findContainingProject(filePath: String, logger: Logger? = null): ProjectPath? { + val sections = filePath.split(File.separatorChar) + logger?.info("finding containing project for $filePath , sections: $sections") + return rootNode.find(sections, 0, logger) + } - logger?.info("finding containing project for $relativeFilePath , sections: $pathSections") - return rootNode.find(pathSections, 0) + fun getRootProjectPath(): ProjectPath? { + return rootNode.projectPath } - private class Node(val logger: Logger? = null) { - var project: Project? = null + val allProjects by lazy { + val result = mutableSetOf() + rootNode.addAllProjectPaths(result) + result + } + + private class Node() : Serializable { + var projectPath: ProjectPath? = null private val children = mutableMapOf() fun getOrCreateNode(key: String): Node { - return children.getOrPut(key) { - Node( - logger - ) - } + return children.getOrPut(key) { Node() } } - fun find(sections: List, index: Int): Project? { - logger?.info("finding $sections with index $index in ${project?.path ?: "root"}") + fun find(sections: List, index: Int, logger: Logger?): ProjectPath? { if (sections.size <= index) { logger?.info("nothing") - return project + return projectPath } val child = children[sections[index]] return if (child == null) { - logger?.info("no child found, returning ${project?.path ?: "root"}") - project + logger?.info("no child found, returning ${projectPath ?: "root"}") + projectPath } else { - child.find(sections, index + 1) + child.find(sections, index + 1, logger) + } + } + + fun addAllProjectPaths(collection: MutableSet) { + projectPath?.let { path -> collection.add(path) } + for (child in children.values) { + child.addAllProjectPaths(collection) } } } } -/** - * Returns the path to the canonical root project directory, e.g. {@code frameworks/support}. - */ fun Project.getSupportRootFolder(): File = project.rootDir diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ToStringLogger.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ToStringLogger.kt deleted file mode 100644 index 14af462..0000000 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ToStringLogger.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Copyright (c) 2020, Dropbox, Inc. All rights reserved. - */ - -package com.dropbox.affectedmoduledetector - -import org.gradle.api.Project -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.logging.LogLevel -import org.gradle.api.logging.Logger -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.services.BuildService -import org.gradle.api.services.BuildServiceParameters -import org.gradle.internal.build.event.BuildEventListenerRegistryInternal -import org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger -import org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext -import org.gradle.internal.operations.BuildOperationDescriptor -import org.gradle.internal.operations.BuildOperationListener -import org.gradle.internal.operations.OperationFinishEvent -import org.gradle.internal.operations.OperationIdentifier -import org.gradle.internal.operations.OperationProgressEvent -import org.gradle.internal.operations.OperationStartEvent -import org.gradle.internal.time.Clock -import org.gradle.invocation.DefaultGradle -import java.io.File - -/** - * Gradle logger that logs to a string. - */ -internal open class ToStringLogger( - private val loggerProvider: Provider? -) : OutputEventListenerBackedLogger( - "amd", - OutputEventListenerBackedLoggerContext { - System.currentTimeMillis() - }.also { - it.level = LogLevel.DEBUG - it.setOutputEventListener { outputEvent -> - loggerProvider?.get()?.parameters?.getStringBuilderProperty()?.get()?.appendLine(outputEvent.toString()) - } - }, - Clock { - System.currentTimeMillis() - } -) { - - /** - * Returns the current log. - */ - fun buildString() = loggerProvider?.get()?.parameters?.getStringBuilderProperty()?.get()?.toString() - - companion object { - internal abstract class ToStringLoggerBuildService : BuildService, BuildOperationListener, AutoCloseable { - interface ToStringLoggerBuildServiceParameters : BuildServiceParameters { - fun getStringBuilderProperty(): Property - fun getOutputFileProperty(): RegularFileProperty - } - - override fun started(p0: BuildOperationDescriptor, p1: OperationStartEvent) { } - - override fun progress(p0: OperationIdentifier, p1: OperationProgressEvent) { } - - override fun finished(buildOperationDescriptor: BuildOperationDescriptor, operationFinishEvent: OperationFinishEvent) { } - - override fun close() { - val outputFile = parameters.getOutputFileProperty().orNull?.asFile ?: return - outputFile.appendText(parameters.getStringBuilderProperty().get().toString()) - println("Wrote dependency log to ${outputFile.absolutePath}") - } - } - - /** - * Creates the [ToStringLogger] - * - * @param project the current project to apply to - * @param logFilename the filename for the logs to go - * @param logFolder the path to where the log should output. if null doesn't output - */ - fun createWithLifecycle( - project: Project, - logFilename: String, - logFolder: String? = null - ): Logger { - val gradle = project.gradle - val stringBuilder = StringBuilder() - val toStringLoggerBuildService = gradle.sharedServices.registerIfAbsent("to-string-logger-build-listener", ToStringLoggerBuildService::class.java) { service -> - service.parameters.getStringBuilderProperty().set(stringBuilder) - if (logFolder != null) { - val distDir = File(logFolder) - if (!distDir.exists()) { - distDir.mkdirs() - } - val outputFile = distDir.resolve(logFilename) - service.parameters.getOutputFileProperty().set(outputFile) - } - } - val logger = ToStringLogger(toStringLoggerBuildService) - (gradle as DefaultGradle).services[BuildEventListenerRegistryInternal::class.java].onOperationCompletion(toStringLoggerBuildService) - return logger - } - } -} diff --git a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/commitshaproviders/CommitShaProvider.kt b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/commitshaproviders/CommitShaProvider.kt index c260c40..99e0ff9 100644 --- a/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/commitshaproviders/CommitShaProvider.kt +++ b/affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/commitshaproviders/CommitShaProvider.kt @@ -2,40 +2,16 @@ package com.dropbox.affectedmoduledetector.commitshaproviders import com.dropbox.affectedmoduledetector.GitClient import com.dropbox.affectedmoduledetector.Sha +import java.io.Serializable -interface CommitShaProvider { - +interface CommitShaProvider : Serializable { fun get(commandRunner: GitClient.CommandRunner): Sha - - companion object { - fun fromString( - string: String, - specifiedBranch: String? = null, - specifiedRawCommitSha: String? = null - ): CommitShaProvider { - return when (string) { - "PreviousCommit" -> PreviousCommit() - "ForkCommit" -> ForkCommit() - "SpecifiedBranchCommit" -> { - requireNotNull(specifiedBranch) { - "Specified branch must be defined" - } - SpecifiedBranchCommit(specifiedBranch) - } - "SpecifiedBranchCommitMergeBase" -> { - requireNotNull(specifiedBranch) { - "Specified branch must be defined" - } - SpecifiedBranchCommitMergeBase(specifiedBranch) - } - "SpecifiedRawCommitSha" -> { - requireNotNull(specifiedRawCommitSha) { - "Specified raw commit sha must be defined" - } - SpecifiedRawCommitSha(specifiedRawCommitSha) - } - else -> throw IllegalArgumentException("Unsupported compareFrom type") - } - } - } } + +data class CommitShaProviderConfiguration( + val type: String, + val specifiedBranch: String? = null, + val specifiedSha: String? = null, + val top: Sha, + val includeUncommitted: Boolean +) : Serializable diff --git a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorImplTest.kt b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorImplTest.kt index 8a791ae..2c8a4be 100644 --- a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorImplTest.kt +++ b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorImplTest.kt @@ -3,6 +3,7 @@ package com.dropbox.affectedmoduledetector import com.google.common.truth.Truth import org.gradle.api.Project import org.gradle.api.plugins.ExtraPropertiesExtension +import org.gradle.api.provider.Provider import org.gradle.testfixtures.ProjectBuilder import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert @@ -20,7 +21,7 @@ class AffectedModuleDetectorImplTest { @Rule @JvmField val attachLogsRule = AttachLogsTestRule() - private val logger = attachLogsRule.logger + private val logger by lazy { attachLogsRule.logger } @Rule @JvmField @@ -35,8 +36,14 @@ class AffectedModuleDetectorImplTest { val tmpFolder3 = TemporaryFolder() private lateinit var root: Project + private lateinit var rootProjectGraph: ProjectGraph + private lateinit var rootDependencyTracker: DependencyTracker private lateinit var root2: Project + private lateinit var root2ProjectGraph: ProjectGraph + private lateinit var root2DependencyTracker: DependencyTracker private lateinit var root3: Project + private lateinit var root3ProjectGraph: ProjectGraph + private lateinit var root3DependencyTracker: DependencyTracker private lateinit var p1: Project private lateinit var p2: Project private lateinit var p3: Project @@ -220,6 +227,13 @@ class AffectedModuleDetectorImplTest { val p19config = p19.configurations.create("p19config") p19config.dependencies.add(p19.dependencies.project(mutableMapOf("path" to ":p18"))) + rootProjectGraph = ProjectGraph(root, null) + rootDependencyTracker = DependencyTracker(root, null) + root2ProjectGraph = ProjectGraph(root2, null) + root2DependencyTracker = DependencyTracker(root2, null) + root3ProjectGraph = ProjectGraph(root3, null) + root3DependencyTracker = DependencyTracker(root3, null) + affectedModuleConfiguration = AffectedModuleConfiguration().also { it.baseDir = tmpDir.absolutePath it.pathsAffectingAllModules = pathsAffectingAllModules @@ -229,30 +243,32 @@ class AffectedModuleDetectorImplTest { @Test fun noChangeCLs() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + gitRoot = tmpFolder.root, + config = affectedModuleConfiguration, + changedFilesProvider = MockGitClient( changedFiles = emptyList(), tmpFolder = tmpFolder.root - ), - config = affectedModuleConfiguration + ).findChangedFiles(root), ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p2.projectPath to p2, - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5, - p6.projectPath to p6, - p7.projectPath to p7, - p8.projectPath to p8, - p9.projectPath to p9, - p10.projectPath to p10 + setOf( + p1.projectPath, + p2.projectPath, + p3.projectPath, + p4.projectPath, + p5.projectPath, + p6.projectPath, + p7.projectPath, + p8.projectPath, + p9.projectPath, + p10.projectPath, ) ) ) @@ -261,30 +277,32 @@ class AffectedModuleDetectorImplTest { @Test fun noChangeCLsOnlyDependent() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = emptyList(), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p2.projectPath to p2, - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5, - p6.projectPath to p6, - p7.projectPath to p7, - p8.projectPath to p8, - p9.projectPath to p9, - p10.projectPath to p10 + setOf( + p1.projectPath, + p2.projectPath, + p3.projectPath, + p4.projectPath, + p5.projectPath, + p6.projectPath, + p7.projectPath, + p8.projectPath, + p9.projectPath, + p10.projectPath, ) ) ) @@ -293,20 +311,22 @@ class AffectedModuleDetectorImplTest { @Test fun noChangeCLsOnlyChanged() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = emptyList(), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -314,24 +334,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInOne() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5 + setOf( + p1.projectPath, + p3.projectPath, + p4.projectPath, + p5.projectPath, ) ) ) @@ -339,23 +361,26 @@ class AffectedModuleDetectorImplTest { @Test fun noChangeSkipAll() { + affectedModuleConfiguration = affectedModuleConfiguration.also { + it.buildAllWhenNoProjectsChanged = false + } val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = emptyList(), tmpFolder = tmpFolder.root - ), - config = affectedModuleConfiguration.also { - it.buildAllWhenNoProjectsChanged = false - } + ).findChangedFiles(root), + gitRoot = tmpFolder.root, + config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - emptyMap() + emptySet() ) ) } @@ -363,23 +388,25 @@ class AffectedModuleDetectorImplTest { @Test fun changeInOneOnlyDependent() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5 + setOf( + p3.projectPath, + p4.projectPath, + p5.projectPath, ) ) ) @@ -388,20 +415,22 @@ class AffectedModuleDetectorImplTest { @Test fun changeInOneOnlyChanged() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p1.projectPath to p1) + setOf(p1.projectPath) ) ) } @@ -409,29 +438,31 @@ class AffectedModuleDetectorImplTest { @Test fun changeInTwo() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d1", "foo.java"), convertToFilePath("d2", "bar.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p2.projectPath to p2, - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5, - p6.projectPath to p6 + setOf( + p1.projectPath, + p2.projectPath, + p3.projectPath, + p4.projectPath, + p5.projectPath, + p6.projectPath, ) ) ) @@ -440,27 +471,29 @@ class AffectedModuleDetectorImplTest { @Test fun changeInTwoOnlyDependent() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d1", "foo.java"), convertToFilePath("d2", "bar.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5, - p6.projectPath to p6 + setOf( + p3.projectPath, + p4.projectPath, + p5.projectPath, + p6.projectPath, ) ) ) @@ -469,25 +502,27 @@ class AffectedModuleDetectorImplTest { @Test fun changeInTwoOnlyChanged() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d1", "foo.java"), convertToFilePath("d2", "bar.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p2.projectPath to p2 + setOf( + p1.projectPath, + p2.projectPath, ) ) ) @@ -496,20 +531,22 @@ class AffectedModuleDetectorImplTest { @Test fun changeInRootOnlyChanged_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf("foo.java"), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -517,20 +554,22 @@ class AffectedModuleDetectorImplTest { @Test fun changeInRootOnlyChanged_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf("foo.java"), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -538,20 +577,22 @@ class AffectedModuleDetectorImplTest { @Test fun changeInRootAndSubproject_onlyChanged() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf("foo.java", convertToFilePath("d7", "bar.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p7.projectPath to p7) + setOf(p7.projectPath) ) ) } @@ -559,24 +600,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUi_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "compose", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p12.projectPath to p12) + setOf(p12.projectPath) ) ) } @@ -584,24 +627,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUiOnlyChanged_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "compose", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p12.projectPath to p12) + setOf(p12.projectPath) ) ) } @@ -609,24 +654,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUiOnlyDependent_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "compose", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -634,24 +681,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUi_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "compose", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -659,24 +708,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUiOnlyChanged_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "compose", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -684,24 +735,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUiOnlyDependent_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "compose", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -709,24 +762,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInNormal_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "d8", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p8.projectPath to p8) + setOf(p8.projectPath) ) ) } @@ -734,24 +789,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInNormalOnlyChanged_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "d8", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p8.projectPath to p8) + setOf(p8.projectPath) ) ) } @@ -759,24 +816,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInNormalOnlyDependent_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "d8", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -784,24 +843,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInNormal_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "d8", "foo.java" ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -809,23 +870,25 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBoth_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d7", "foo.java"), convertToFilePath("compose", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p12.projectPath to p12) + setOf(p12.projectPath) ) ) } @@ -833,23 +896,25 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBothOnlyChanged_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d7", "foo.java"), convertToFilePath("compose", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p12.projectPath to p12) + setOf(p12.projectPath) ) ) } @@ -857,23 +922,25 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBothOnlyDependent_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d7", "foo.java"), convertToFilePath("compose", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() // a change to a project in the normal build doesn't affect the ui build + setOf() // a change to a project in the normal build doesn't affect the ui build ) ) // and compose is in changed and so excluded from dependent } @@ -881,23 +948,25 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBoth_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d7", "foo.java"), convertToFilePath("compose", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p7.projectPath to p7) // a change in compose is known not to matter to the normal build + setOf(p7.projectPath) // a change in compose is known not to matter to the normal build ) ) } @@ -905,23 +974,25 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBothOnlyChanged_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d7", "foo.java"), convertToFilePath("compose", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p7.projectPath to p7) + setOf(p7.projectPath) ) ) } @@ -929,23 +1000,25 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBothOnlyDependent_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d7", "foo.java"), convertToFilePath("compose", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() // a change in compose is known not to matter to the normal build + setOf() // a change in compose is known not to matter to the normal build ) ) // and p7 is in changed and so not in dependent } @@ -953,22 +1026,24 @@ class AffectedModuleDetectorImplTest { @Test fun changeInNormalRoot_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("..", "gradle.properties") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() // a change in androidx root normally doesn't affect the ui build + setOf() // a change in androidx root normally doesn't affect the ui build ) ) // unless otherwise specified (e.g. gradlew) } @@ -976,22 +1051,24 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUiRoot_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("gradle.properties") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - emptyMap() + emptySet() ) ) // a change in ui/root affects all ui projects } @@ -999,32 +1076,34 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBuildSrc_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("tools", "android", "buildSrc", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p2.projectPath to p2, - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5, - p6.projectPath to p6, - p7.projectPath to p7, - p8.projectPath to p8, - p9.projectPath to p9, - p10.projectPath to p10 + setOf( + p1.projectPath, + p2.projectPath, + p3.projectPath, + p4.projectPath, + p5.projectPath, + p6.projectPath, + p7.projectPath, + p8.projectPath, + p9.projectPath, + p10.projectPath, ) ) ) // a change to buildSrc affects everything in both builds @@ -1033,25 +1112,27 @@ class AffectedModuleDetectorImplTest { @Test fun changeInBuildSrc_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("tools", "android", "buildSrc", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p12.projectPath to p12, - p13.projectPath to p13 + setOf( + p12.projectPath, + p13.projectPath, ) // a change to buildSrc affects everything in both builds ) ) @@ -1060,22 +1141,24 @@ class AffectedModuleDetectorImplTest { @Test fun changeInUiGradlew_normalBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("ui", "gradlew") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() // a change to ui gradlew affects only the ui build + setOf() // a change to ui gradlew affects only the ui build ) ) } @@ -1083,24 +1166,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInNormalGradlew_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("android", "gradlew") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p12.projectPath to p12, - p13.projectPath to p13 + setOf( + p12.projectPath, + p13.projectPath, ) // a change to root gradlew affects everything in both builds ) ) @@ -1109,24 +1194,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInDevelopment_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("tools", "android", "buildSrc", "foo.sh") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root2), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p12.projectPath to p12, - p13.projectPath to p13 + setOf( + p12.projectPath, + p13.projectPath, ) // a change to development affects everything in both builds ) ) @@ -1135,11 +1222,12 @@ class AffectedModuleDetectorImplTest { @Test fun changeInTools_uiBuild() { val detector = AffectedModuleDetectorImpl( - rootProject = root2, - logger = logger, + projectGraph = root2ProjectGraph, + dependencyTracker = root2DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath( "tools", @@ -1149,15 +1237,16 @@ class AffectedModuleDetectorImplTest { ) ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p12.projectPath to p12, - p13.projectPath to p13 + setOf( + p12.projectPath, + p13.projectPath, ) // not sure what this folder is for, but it affects all of both? ) ) @@ -1166,21 +1255,23 @@ class AffectedModuleDetectorImplTest { @Test fun projectSubset_changed() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) // Verify expectations on affected projects MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p1.projectPath to p1) + setOf(p1.projectPath) ) ) // Test changed @@ -1209,24 +1300,26 @@ class AffectedModuleDetectorImplTest { @Test fun projectSubset_dependent() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) // Verify expectations on affected projects MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5 + setOf( + p3.projectPath, + p4.projectPath, + p5.projectPath, ) ) ) @@ -1256,25 +1349,27 @@ class AffectedModuleDetectorImplTest { @Test fun projectSubset_all() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) // Verify expectations on affected projects MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5 + setOf( + p1.projectPath, + p3.projectPath, + p4.projectPath, + p5.projectPath, ) ) ) @@ -1304,24 +1399,26 @@ class AffectedModuleDetectorImplTest { @Test fun noChangeCLs_quixotic() { val detector = AffectedModuleDetectorImpl( - rootProject = root3, - logger = logger, + projectGraph = root3ProjectGraph, + dependencyTracker = root3DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = emptyList(), tmpFolder = root3.projectDir - ), + ).findChangedFiles(root3), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p16.projectPath to p16, - p17.projectPath to p17, - p18.projectPath to p18, - p19.projectPath to p19 + setOf( + p16.projectPath, + p17.projectPath, + p18.projectPath, + p19.projectPath, ) ) ) @@ -1330,24 +1427,26 @@ class AffectedModuleDetectorImplTest { @Test fun noChangeCLsOnlyDependent_quixotic() { val detector = AffectedModuleDetectorImpl( - rootProject = root3, - logger = logger, + projectGraph = root3ProjectGraph, + dependencyTracker = root3DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.DEPENDENT_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = emptyList(), tmpFolder = root3.projectDir - ), + ).findChangedFiles(root3), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p16.projectPath to p16, - p17.projectPath to p17, - p18.projectPath to p18, - p19.projectPath to p19 + setOf( + p16.projectPath, + p17.projectPath, + p18.projectPath, + p19.projectPath, ) ) ) @@ -1356,20 +1455,22 @@ class AffectedModuleDetectorImplTest { @Test fun noChangeCLsOnlyChanged_quixotic() { val detector = AffectedModuleDetectorImpl( - rootProject = root3, - logger = logger, + projectGraph = root3ProjectGraph, + dependencyTracker = root3DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = emptyList(), tmpFolder = root3.projectDir - ), + ).findChangedFiles(root3), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -1377,24 +1478,26 @@ class AffectedModuleDetectorImplTest { @Test fun changeInOne_quixotic_main_module() { val detector = AffectedModuleDetectorImpl( - rootProject = root3, - logger = logger, + projectGraph = root3ProjectGraph, + dependencyTracker = root3DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d14", "d16", "foo.java")), tmpFolder = root3.projectDir - ), + ).findChangedFiles(root3), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p16.projectPath to p16, - p17.projectPath to p17, - p18.projectPath to p18, - p19.projectPath to p19 + setOf( + p16.projectPath, + p17.projectPath, + p18.projectPath, + p19.projectPath, ) ) ) @@ -1403,22 +1506,24 @@ class AffectedModuleDetectorImplTest { @Test fun changeInOne_quixotic_common_module_with_a_dependency() { val detector = AffectedModuleDetectorImpl( - rootProject = root3, - logger = logger, + projectGraph = root3ProjectGraph, + dependencyTracker = root3DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d15", "d18", "foo.java")), tmpFolder = root3.projectDir - ), + ).findChangedFiles(root3), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p18.projectPath to p18, - p19.projectPath to p19 + setOf( + p18.projectPath, + p19.projectPath, ) ) ) @@ -1427,20 +1532,22 @@ class AffectedModuleDetectorImplTest { @Test fun changeInOne_quixotic_common_module_without_a_dependency() { val detector = AffectedModuleDetectorImpl( - rootProject = root3, - logger = logger, + projectGraph = root3ProjectGraph, + dependencyTracker = root3DependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d15", "d19", "foo.java")), tmpFolder = root3.projectDir - ), + ).findChangedFiles(root3), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf(p19.projectPath to p19) + setOf(p19.projectPath) ) ) } @@ -1484,60 +1591,66 @@ class AffectedModuleDetectorImplTest { @Test fun `GIVEN all affected modules WHEN modules parameter is passed THEN modules parameter is observed`() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, modules = setOf(":p1"), - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) - Truth.assertThat(detector.shouldInclude(p1)).isTrue() - Truth.assertThat(detector.shouldInclude(p4)).isFalse() - Truth.assertThat(detector.shouldInclude(p5)).isFalse() + Truth.assertThat(detector.shouldInclude(p1.projectPath)).isTrue() + Truth.assertThat(detector.shouldInclude(p4.projectPath)).isFalse() + Truth.assertThat(detector.shouldInclude(p5.projectPath)).isFalse() } @Test fun `GIVEN all affected modules WHEN modules parameter is empty THEN no affected modules `() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, modules = emptySet(), - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) - Truth.assertThat(detector.shouldInclude(p1)).isFalse() - Truth.assertThat(detector.shouldInclude(p3)).isFalse() - Truth.assertThat(detector.shouldInclude(p4)).isFalse() - Truth.assertThat(detector.shouldInclude(p5)).isFalse() + Truth.assertThat(detector.shouldInclude(p1.projectPath)).isFalse() + Truth.assertThat(detector.shouldInclude(p3.projectPath)).isFalse() + Truth.assertThat(detector.shouldInclude(p4.projectPath)).isFalse() + Truth.assertThat(detector.shouldInclude(p5.projectPath)).isFalse() } @Test fun `GIVEN all affected modules WHEN modules parameter is null THEN all affected modules are returned `() { val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, modules = null, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) - Truth.assertThat(detector.shouldInclude(p1)).isTrue() - Truth.assertThat(detector.shouldInclude(p3)).isTrue() - Truth.assertThat(detector.shouldInclude(p4)).isTrue() - Truth.assertThat(detector.shouldInclude(p5)).isTrue() + Truth.assertThat(detector.shouldInclude(p1.projectPath)).isTrue() + Truth.assertThat(detector.shouldInclude(p3.projectPath)).isTrue() + Truth.assertThat(detector.shouldInclude(p4.projectPath)).isTrue() + Truth.assertThat(detector.shouldInclude(p5.projectPath)).isTrue() } @Test @@ -1546,20 +1659,22 @@ class AffectedModuleDetectorImplTest { it.excludedModules = setOf("p1") } val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, modules = null, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(convertToFilePath("d1", "foo.java")), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) - Truth.assertThat(detector.shouldInclude(p1)).isFalse() - Truth.assertThat(detector.shouldInclude(p4)).isTrue() - Truth.assertThat(detector.shouldInclude(p5)).isTrue() + Truth.assertThat(detector.shouldInclude(p1.projectPath)).isFalse() + Truth.assertThat(detector.shouldInclude(p4.projectPath)).isTrue() + Truth.assertThat(detector.shouldInclude(p5.projectPath)).isTrue() } @Test @@ -1568,12 +1683,13 @@ class AffectedModuleDetectorImplTest { it.excludedModules = setOf(":p1:p3:[a-zA-Z0-9:]+") } val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS, modules = null, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf( convertToFilePath("d1/d3", "foo.java"), convertToFilePath("d1/d3/d4", "foo.java"), @@ -1581,13 +1697,14 @@ class AffectedModuleDetectorImplTest { convertToFilePath("d1/d3/d6", "foo.java") ), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) - Truth.assertThat(detector.shouldInclude(p3)).isTrue() - Truth.assertThat(detector.shouldInclude(p4)).isFalse() - Truth.assertThat(detector.shouldInclude(p5)).isTrue() - Truth.assertThat(detector.shouldInclude(p6)).isFalse() + Truth.assertThat(detector.shouldInclude(p3.projectPath)).isTrue() + Truth.assertThat(detector.shouldInclude(p4.projectPath)).isFalse() + Truth.assertThat(detector.shouldInclude(p5.projectPath)).isTrue() + Truth.assertThat(detector.shouldInclude(p6.projectPath)).isFalse() } @Test @@ -1598,31 +1715,33 @@ class AffectedModuleDetectorImplTest { it.pathsAffectingAllModules.toMutableSet().add(changedFile) } val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, modules = null, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(changedFile), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf( - p1.projectPath to p1, - p2.projectPath to p2, - p3.projectPath to p3, - p4.projectPath to p4, - p5.projectPath to p5, - p6.projectPath to p6, - p7.projectPath to p7, - p8.projectPath to p8, - p9.projectPath to p9, - p10.projectPath to p10 + setOf( + p1.projectPath, + p2.projectPath, + p3.projectPath, + p4.projectPath, + p5.projectPath, + p6.projectPath, + p7.projectPath, + p8.projectPath, + p9.projectPath, + p10.projectPath, ) ) ) @@ -1633,21 +1752,23 @@ class AffectedModuleDetectorImplTest { val changedFile = convertToFilePath("android", "notgradle", "test.java") val detector = AffectedModuleDetectorImpl( - rootProject = root, - logger = logger, + projectGraph = rootProjectGraph, + dependencyTracker = rootDependencyTracker, + logger = logger.toLogger(), ignoreUnknownProjects = false, projectSubset = ProjectSubset.CHANGED_PROJECTS, modules = null, - injectedGitClient = MockGitClient( + changedFilesProvider = MockGitClient( changedFiles = listOf(changedFile), tmpFolder = tmpFolder.root - ), + ).findChangedFiles(root), + gitRoot = tmpFolder.root, config = affectedModuleConfiguration ) MatcherAssert.assertThat( detector.affectedProjects, CoreMatchers.`is`( - mapOf() + setOf() ) ) } @@ -1663,9 +1784,8 @@ class AffectedModuleDetectorImplTest { ) : GitClient { override fun findChangedFiles( - top: Sha, - includeUncommitted: Boolean - ) = changedFiles + project: Project, + ): Provider> = project.provider { changedFiles } override fun getGitRoot(): File { return tmpFolder diff --git a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AttachLogsTestRule.kt b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AttachLogsTestRule.kt index 6ed5e8e..2edf9aa 100644 --- a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AttachLogsTestRule.kt +++ b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AttachLogsTestRule.kt @@ -3,24 +3,30 @@ package com.dropbox.affectedmoduledetector import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -import org.mockito.kotlin.mock +import java.io.File /** * Special rule for dependency detector tests that will attach logs to a failure. */ -class AttachLogsTestRule : TestRule { - internal val logger = mock { } +class AttachLogsTestRule() : TestRule { + + private val file: File = File.createTempFile("test", "log") + + internal val logger by lazy { FileLogger(file) } override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { try { + file.deleteOnExit() base.evaluate() } catch (t: Throwable) { + val bufferedReader = file.bufferedReader() + val logs = bufferedReader.use { it.readText() } throw Exception( """ test failed with msg: ${t.message} logs: - ${logger.buildString()} + $logs """.trimIndent(), t ) diff --git a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/GitClientImplTest.kt b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/GitClientImplTest.kt deleted file mode 100644 index 5040d23..0000000 --- a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/GitClientImplTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.dropbox.affectedmoduledetector - -import com.dropbox.affectedmoduledetector.GitClientImpl.Companion.CHANGED_FILES_CMD_PREFIX -import com.dropbox.affectedmoduledetector.mocks.MockCommandRunner -import com.dropbox.affectedmoduledetector.mocks.MockCommitShaProvider -import junit.framework.TestCase.assertEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import java.io.File - -@RunWith(JUnit4::class) -class GitClientImplTest { - @Rule - @JvmField - val attachLogsRule = AttachLogsTestRule() - private val logger = attachLogsRule.logger - private val commandRunner = MockCommandRunner(logger) - /** The [GitClientImpl.workingDir] uses `System.getProperty("user.dir")` because the working - * directory passed to the [GitClientImpl] constructor needs to contain the a .git - * directory somewhere in the parent directory tree. @see [GitClientImpl] - */ - private val workingDir = File(System.getProperty("user.dir")).parentFile - private val commitShaProvider = MockCommitShaProvider() - private val client = GitClientImpl( - workingDir = workingDir, - logger = logger, - commandRunner = commandRunner, - commitShaProvider = commitShaProvider, - ignoredFiles = setOf(".*\\.ignore", ".*IGNORED_FILE") - ) - - @Test - fun givenChangedFiles_whenFindChangedFilesIncludeUncommitted_thenReturnChanges() { - val changes = listOf( - convertToFilePath("d", "e", ".ignoredNot"), - convertToFilePath("a", "b", "c.java"), - convertToFilePath("d", "e", "f.java") - ) - val changesWithIgnores = listOf( - convertToFilePath("a", "b", "anything.ignore"), - convertToFilePath("d", "e", ".ignore"), - convertToFilePath("d", "e", "IGNORED_FILE") - ) + changes - commandRunner.addReply( - "$CHANGED_FILES_CMD_PREFIX mySha", - changesWithIgnores.joinToString(System.lineSeparator()) - ) - commitShaProvider.addReply("mySha") - - assertEquals( - changes, - client.findChangedFiles(includeUncommitted = true) - ) - } - - @Test - fun findChangesSince_empty() { - commitShaProvider.addReply("mySha") - assertEquals( - emptyList(), - client.findChangedFiles() - ) - } - - @Test - fun findChangesSince_twoCls() { - val changes = listOf( - convertToFilePath("d", "e", ".ignoredNot"), - convertToFilePath("a", "b", "c.java"), - convertToFilePath("d", "e", "f.java") - ) - val changesWithIgnores = listOf( - convertToFilePath("a", "b", "anything.ignore"), - convertToFilePath("d", "e", ".ignore"), - convertToFilePath("d", "e", "IGNORED_FILE") - ) + changes - commandRunner.addReply( - "$CHANGED_FILES_CMD_PREFIX otherSha..mySha", - changesWithIgnores.joinToString(System.lineSeparator()) - ) - commitShaProvider.addReply("mySha") - assertEquals( - changes, - client.findChangedFiles( - top = "otherSha", - includeUncommitted = false - ) - ) - } - - // For both Linux/Windows - fun convertToFilePath(vararg list: String): String { - return list.toList().joinToString(File.separator) - } -} diff --git a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/ProjectGraphTest.kt b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/ProjectGraphTest.kt index 1a7f612..f88c21b 100644 --- a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/ProjectGraphTest.kt +++ b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/ProjectGraphTest.kt @@ -41,23 +41,24 @@ class ProjectGraphTest { .withName("p3") .withParent(p1) .build() - val graph = ProjectGraph(root, tmpDir) + val logFile = File.createTempFile(tmpDir.path, "log") + val graph = ProjectGraph(root, FileLogger(logFile).toLogger()) assertNull(graph.findContainingProject("nowhere")) assertNull(graph.findContainingProject("rootfile.java")) assertEquals( - p1, + p1.projectPath, graph.findContainingProject("p1/px/x.java".toLocalPath()) ) assertEquals( - p1, + p1.projectPath, graph.findContainingProject("p1/a.java".toLocalPath()) ) assertEquals( - p3, + p3.projectPath, graph.findContainingProject("p1/p3/a.java".toLocalPath()) ) assertEquals( - p2, + p2.projectPath, graph.findContainingProject("p2/a/b/c/d/e/f/a.java".toLocalPath()) ) } diff --git a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/commitshaproviders/CommitShaProviderTest.kt b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/commitshaproviders/CommitShaProviderTest.kt deleted file mode 100644 index b62ecd5..0000000 --- a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/commitshaproviders/CommitShaProviderTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.dropbox.affectedmoduledetector.commitshaproviders - -import com.google.common.truth.Truth.assertThat -import org.junit.Assert.fail -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import java.lang.IllegalArgumentException - -@RunWith(JUnit4::class) -class CommitShaProviderTest { - @Test - fun givenPreviousCommit_whenFromString_thenReturnPreviousCommit() { - val actual = CommitShaProvider.fromString("PreviousCommit") - - assertThat(actual::class).isEqualTo(PreviousCommit::class) - } - - @Test - fun givenForkCommit_whenFromString_thenReturnForkCommit() { - val actual = CommitShaProvider.fromString("ForkCommit") - - assertThat(actual::class).isEqualTo(ForkCommit::class) - } - - @Test - fun givenSpecifiedBranchCommit_whenFromString_thenReturnSpecifiedBranchCommit() { - val actual = CommitShaProvider.fromString("SpecifiedBranchCommit", "branch") - - assertThat(actual::class).isEqualTo(SpecifiedBranchCommit::class) - } - - @Test - fun givenSpecifiedBranchCommitAndSpecifiedBranchNull_whenFromString_thenReturnSpecifiedBranchCommit() { - try { - CommitShaProvider.fromString("SpecifiedBranchCommit") - fail() - } catch (e: Exception) { - assertThat(e::class).isEqualTo(IllegalArgumentException::class) - assertThat(e.message).isEqualTo("Specified branch must be defined") - } - } - - @Test - fun givenSpecifiedBranchCommitMergeBaseAndSpecifiedBranchNull_whenFromString_thenThrowException() { - try { - CommitShaProvider.fromString("SpecifiedBranchCommitMergeBase") - } catch (e: Exception) { - assertThat(e::class).isEqualTo(IllegalArgumentException::class) - assertThat(e.message).isEqualTo("Specified branch must be defined") - } - } - - @Test - fun givenSpecifiedBranchCommitMergeBase_whenFromString_thenReturnSpecifiedBranchCommitMergeBase() { - val actual = CommitShaProvider.fromString("SpecifiedBranchCommitMergeBase", "branch") - - assertThat(actual::class).isEqualTo(SpecifiedBranchCommitMergeBase::class) - } - - @Test - fun givenSpecifiedRawCommitSha_whenFromString_thenReturnSpecifiedRawCommitSha() { - val actual = CommitShaProvider.fromString("SpecifiedRawCommitSha", specifiedRawCommitSha = "sha") - - assertThat(actual::class).isEqualTo(SpecifiedRawCommitSha::class) - } - - @Test - fun givenSpecifiedRawCommitShaAndSpecifiedRawCommitShaNull_whenFromString_thenThrowException() { - try { - CommitShaProvider.fromString("SpecifiedRawCommitSha") - } catch (e: Exception) { - assertThat(e::class).isEqualTo(IllegalArgumentException::class) - assertThat(e.message).isEqualTo("Specified raw commit sha must be defined") - } - } - - @Test - fun givenInvalidCommitString_whenFromString_thenExceptionThrown() { - try { - CommitShaProvider.fromString("Invalid") - fail() - } catch (e: Exception) { - assertThat(e::class).isEqualTo(IllegalArgumentException::class) - assertThat(e.message).isEqualTo("Unsupported compareFrom type") - } - } -} diff --git a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/mocks/MockCommandRunner.kt b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/mocks/MockCommandRunner.kt index c3a822f..49d5f41 100644 --- a/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/mocks/MockCommandRunner.kt +++ b/affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/mocks/MockCommandRunner.kt @@ -1,9 +1,9 @@ package com.dropbox.affectedmoduledetector.mocks +import com.dropbox.affectedmoduledetector.FileLogger import com.dropbox.affectedmoduledetector.GitClient -import com.dropbox.affectedmoduledetector.ToStringLogger -internal class MockCommandRunner(private val logger: ToStringLogger) : GitClient.CommandRunner { +internal class MockCommandRunner(private val logger: FileLogger) : GitClient.CommandRunner { private val replies = mutableMapOf>() fun addReply(command: String, response: String) { diff --git a/sample/buildSrc/src/main/kotlin/com/dropbox/sample/tasks/AffectedTasksPlugin.kt b/sample/buildSrc/src/main/kotlin/com/dropbox/sample/tasks/AffectedTasksPlugin.kt index 6fd05c9..eba6526 100644 --- a/sample/buildSrc/src/main/kotlin/com/dropbox/sample/tasks/AffectedTasksPlugin.kt +++ b/sample/buildSrc/src/main/kotlin/com/dropbox/sample/tasks/AffectedTasksPlugin.kt @@ -2,6 +2,7 @@ package com.dropbox.affectedmoduledetector.tasks import com.dropbox.affectedmoduledetector.AffectedModuleDetector import com.dropbox.affectedmoduledetector.DependencyTracker +import com.dropbox.affectedmoduledetector.projectPath import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task @@ -110,8 +111,8 @@ class AffectedTasksPlugin : Plugin { val tracker = DependencyTracker(project, null) project.tasks.configureEach { task -> if (task.name.contains(ANDROID_TEST_BUILD_VARIANT)) { - tracker.findAllDependents(project).forEach { (_, dependentProject) -> - dependentProject.tasks.forEach { dependentTask -> + tracker.findAllDependents(project.projectPath).forEach { dependentProject -> + project.findProject(dependentProject.path)?.tasks?.forEach { dependentTask -> AffectedModuleDetector.configureTaskGuard(dependentTask) } }