From ef1ee2ba0fd9241fc319d4a54e01a455baf1cbd2 Mon Sep 17 00:00:00 2001 From: Danny Thomas Date: Wed, 18 May 2016 14:09:31 -0700 Subject: [PATCH 1/4] Add version matchers to align rules --- .../AbstractAlignRulesSpec.groovy | 23 + .../AlignRulesBasicSpec.groovy | 604 ++++++++ .../AlignRulesDirectDependenciesSpec.groovy | 198 +++ .../AlignRulesForceSpec.groovy | 224 +++ .../resolutionrules/AlignRulesSpec.groovy | 1210 ----------------- ...lignRulesTransitiveDependenciesSpec.groovy | 184 +++ .../AlignRulesVersionMatchSpec.groovy | 87 ++ .../plugin/resolutionrules/Rules.groovy | 61 +- 8 files changed, 1370 insertions(+), 1221 deletions(-) create mode 100644 src/functionalTest/groovy/nebula/plugin/resolutionrules/AbstractAlignRulesSpec.groovy create mode 100644 src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesBasicSpec.groovy create mode 100644 src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesDirectDependenciesSpec.groovy create mode 100644 src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesForceSpec.groovy delete mode 100644 src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesSpec.groovy create mode 100644 src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesTransitiveDependenciesSpec.groovy create mode 100644 src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesVersionMatchSpec.groovy diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AbstractAlignRulesSpec.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AbstractAlignRulesSpec.groovy new file mode 100644 index 0000000..6f6ecf6 --- /dev/null +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AbstractAlignRulesSpec.groovy @@ -0,0 +1,23 @@ +package nebula.plugin.resolutionrules + +import nebula.test.IntegrationSpec + +class AbstractAlignRulesSpec extends IntegrationSpec { + def rulesJsonFile + + def setup() { + rulesJsonFile = new File(projectDir, "${moduleName}.json") + buildFile << """\ + ${applyPlugin(ResolutionRulesPlugin)} + apply plugin: 'java' + + dependencies { + resolutionRules files('$rulesJsonFile') + } + """.stripIndent() + + settingsFile << '''\ + rootProject.name = 'aligntest' + '''.stripIndent() + } +} diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesBasicSpec.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesBasicSpec.groovy new file mode 100644 index 0000000..65f5786 --- /dev/null +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesBasicSpec.groovy @@ -0,0 +1,604 @@ +/* + * Copyright 2016 Netflix, Inc. + * + * 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 nebula.plugin.resolutionrules + +import nebula.test.dependencies.DependencyGraphBuilder +import nebula.test.dependencies.GradleDependencyGenerator +import nebula.test.dependencies.ModuleBuilder + +class AlignRulesBasicSpec extends AbstractAlignRulesSpec { + def 'align rules do not replace changes made by other rules'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:2.0.0') + .addModule(new ModuleBuilder('test.nebula:b:1.0.0').addDependency('test.nebula:a:1.0.0').build()) + .addModule(new ModuleBuilder('test.nebula.ext:b:1.0.0').addDependency('test.nebula:a:1.0.0').build()) + .addModule(new ModuleBuilder('test.nebula.ext:b:2.0.0').addDependency('test.nebula:a:2.0.0').build()) + .addModule(new ModuleBuilder('test.other:c:1.0.0').addDependency('test.nebula:b:1.0.0').build()) + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "substitute": [ + { + "module": "test.nebula:b", + "with": "test.nebula.ext:b:1.0.0", + "reason": "Library was published with incorrect coordinates", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ], + "align": [ + { + "group": "(test.nebula|test.nebula.ext)", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.other:c:1.0.0' + compile 'test.nebula:a:2.0.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '\\--- test.nebula:b:1.0.0 -> test.nebula.ext:b:2.0.0\n' + } + + def 'can align some dependencies in a group'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:0.42.0') + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:1.1.0') + .addModule('test.nebula:b:0.42.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:1.1.0') + .addModule('test.nebula:c:0.42.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:1.1.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "includes": [ "a", "b" ], + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:1.1.0' + compile 'test.nebula:c:0.42.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 1.1.0' + result.standardOutput.contains '+--- test.nebula:b:1.1.0' + result.standardOutput.contains '\\--- test.nebula:c:0.42.0' + } + + def 'skip aligning some dependencies in a group'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:0.42.0') + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:1.1.0') + .addModule('test.nebula:b:0.42.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:1.1.0') + .addModule('test.nebula:c:0.42.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:1.1.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "excludes": [ "a" ], + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:1.1.0' + compile 'test.nebula:c:0.42.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '+--- test.nebula:b:1.1.0\n' + result.standardOutput.contains '\\--- test.nebula:c:0.42.0 -> 1.1.0' + } + + def 'a project can build in presence of align rules for jars it produces'() { + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "includes": ["aligntest", "b"], + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << '''\ + group = 'test.nebula' + version = '0.1.0' + + apply plugin: 'maven-publish' + + publishing { + publications { + test(MavenPublication) { + from components.java + } + } + repositories { + maven { + name 'repo' + url 'build/repo' + } + } + } + '''.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + noExceptionThrown() + } + + def 'multiple align rules'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:1.1.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:1.1.0') + .addModule('test.other:c:0.12.2') + .addModule('test.other:c:1.0.0') + .addModule('test.other:d:0.12.2') + .addModule('test.other:d:1.0.0') + .build() + def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen") + mavenrepo.generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + }, + { + "name": "testOther", + "group": "test.other", + "reason": "Aligning test", + "author": "Example Tester ", + "date": "2016-04-05T19:19:49.495Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + ${mavenrepo.mavenRepositoryBlock} + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:1.1.0' + compile 'test.other:c:1.0.0' + compile 'test.other:d:0.12.+' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains 'test.nebula:a:1.0.0 -> 1.1.0\n' + result.standardOutput.contains 'test.nebula:b:1.1.0\n' + result.standardOutput.contains 'test.other:c:1.0.0\n' + result.standardOutput.contains 'test.other:d:0.12.+ -> 1.0.0\n' + } + + def 'substitute and align work together'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .addModule('old.org:sub:0.1.0') + .addModule('new.org:sub:0.2.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], + "substitute": [ + { + "module" : "old.org:sub", + "with" : "new.org:sub:latest.release", + "reason" : "swap old.org to new.org", + "author" : "Example Person ", + "date" : "2016-03-18T20:21:20.368Z" + } + ], + "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:0.15.0' + compile 'old.org:sub:latest.release' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '+--- test.nebula:b:0.15.0 -> 1.0.0\n' + result.standardOutput.contains '\\--- old.org:sub:latest.release -> new.org:sub:0.2.0\n' + } + + def 'jcenter align'() { + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "com.google.guava", + "reason": "Align guava", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { jcenter() } + dependencies { + compile 'com.google.guava:guava:12.0' + } + """ + + when: + runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + noExceptionThrown() + } + + def 'can add additional resolution rules outside of plugin'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .addModule('test.example:c:0.1.0') + .addModule('test.example:c:0.2.0') + .addModule('test:x:1.0.0') + .addModule('test:x:1.0.1') + .addModule('test:y:1.0.0') + .addModule('test:y:1.0.1') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + configurations.all { + resolutionStrategy { + force 'test.example:c:0.1.0' + eachDependency { details -> + if (details.requested.group == 'test') { + details.useTarget group: details.requested.group, name: details.requested.name, version: '1.0.0' + } + } + } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:0.15.0' + compile 'test.example:c:latest.release' + compile 'test:x:1.+' + compile 'test:y:1.+' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '+--- test.nebula:b:0.15.0 -> 1.0.0\n' + result.standardOutput.contains '+--- test.example:c:latest.release -> 0.1.0\n' + result.standardOutput.contains '+--- test:x:1.+ -> 1.0.0\n' + result.standardOutput.contains '\\--- test:y:1.+ -> 1.0.0\n' + } + + def 'regular expressions supported in groups'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula.one:a:1.0.0') + .addModule('test.nebula.one:a:0.15.0') + .addModule('test.nebula.two:b:1.0.0') + .addModule('test.nebula.two:b:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula.*", + "reason": "Align test.nebula.* dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula.one:a:1.0.0' + compile 'test.nebula.two:b:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula.one:a:1.0.0\n' + result.standardOutput.contains '\\--- test.nebula.two:b:0.15.0 -> 1.0.0\n' + } + + def 'regular expressions supported in includes'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "includes": ["(a|b)"], + "reason": "Align test.nebula a and b dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:0.15.0' + compile 'test.nebula:c:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '+--- test.nebula:b:0.15.0 -> 1.0.0\n' + result.standardOutput.contains '\\--- test.nebula:c:0.15.0\n' + } + + def 'regular expressions supported in excludes'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "excludes": ["(b|c)"], + "reason": "Align test.nebula a and b dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:0.15.0' + compile 'test.nebula:c:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '+--- test.nebula:b:0.15.0\n' + result.standardOutput.contains '\\--- test.nebula:c:0.15.0\n' + } + + def 'alignment does not apply to dependencies that already have the expected version'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencyInsight', '--configuration', 'compile', '--dependency', 'test.nebula') + + then: + result.standardOutput.contains 'Resolution rules ruleset alignment-does-not-apply-to-dependencies-that-already-have-the-expected-version rule [group: test.nebula] aligning test.nebula:b to 1.0.0' + result.standardOutput.contains 'test.nebula:a:1.0.0\n' + result.standardOutput.contains 'test.nebula:b:0.15.0 -> 1.0.0\n' + } +} diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesDirectDependenciesSpec.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesDirectDependenciesSpec.groovy new file mode 100644 index 0000000..681cba9 --- /dev/null +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesDirectDependenciesSpec.groovy @@ -0,0 +1,198 @@ +package nebula.plugin.resolutionrules + +import nebula.test.dependencies.DependencyGraphBuilder +import nebula.test.dependencies.GradleDependencyGenerator + +class AlignRulesDirectDependenciesSpec extends AbstractAlignRulesSpec { + def 'can align direct dependencies if necessary'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n' + } + + def 'can align direct dependencies from ivy repositories'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .build() + GradleDependencyGenerator ivyrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen") + ivyrepo.generateTestIvyRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + ${ivyrepo.ivyRepositoryBlock} + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n' + } + + def 'can align dynamic dependencies'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:1.0.1') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.+' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '\\--- test.nebula:a:1.+ -> 1.0.1\n' + } + + def 'can align dynamic range dependencies'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:1.0.1') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:[1.0.0, 2.0.0)' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '\\--- test.nebula:a:[1.0.0, 2.0.0) -> 1.0.1\n' + } + + def 'unresolvable dependencies cause warnings to be output'() { + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "com.google.guava", + "reason": "Align guava", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { jcenter() } + dependencies { + compile 'org.slf4j:slf4j-api:1.7.21' + compile 'com.google.guava:guava:oops' + } + """ + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains("Resolution rules could not resolve all dependencies to align in configuration 'compile' should also fail to resolve") + } +} diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesForceSpec.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesForceSpec.groovy new file mode 100644 index 0000000..77abe67 --- /dev/null +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesForceSpec.groovy @@ -0,0 +1,224 @@ +package nebula.plugin.resolutionrules + +import nebula.test.dependencies.DependencyGraphBuilder +import nebula.test.dependencies.GradleDependencyGenerator + +class AlignRulesForceSpec extends AbstractAlignRulesSpec { + def 'alignment uses forced version, rather than highest version, when a force is present'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:0.15.0') + .addModule('test.nebula.other:a:1.0.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b:1.0.0' + compile 'test.nebula:c:0.15.0' + compile 'test.nebula.other:a:1.0.0' + } + configurations.compile.resolutionStrategy { + force 'test.nebula:a:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains 'Found force(s) [test.nebula:a:0.15.0] that supersede resolution ruleset alignment-uses-forced-version-rather-than-highest-version-when-a-force-is-present align rule [group: test.nebula]. Will use 0.15.0 instead of 1.0.0' + result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 0.15.0\n' + result.standardOutput.contains '+--- test.nebula:b:1.0.0 -> 0.15.0\n' + result.standardOutput.contains '+--- test.nebula:c:0.15.0\n' + result.standardOutput.contains '\\--- test.nebula.other:a:1.0.0\n' + } + + def 'alignment uses lowest forced version, when multiple forces are present'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:2.0.0') + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:2.0.0') + .addModule('test.nebula:b:1.00.0') + .addModule('test.nebula:b:0.15.0') + .addModule('test.nebula:c:2.0.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:2.0.0' + compile 'test.nebula:b:2.0.0' + compile 'test.nebula:c:1.0.0' + } + configurations.compile.resolutionStrategy { + force 'test.nebula:a:2.0.0' + force 'test.nebula:b:1.0.0' + force 'test.nebula:c:0.15.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:2.0.0 -> 0.15.0\n' + result.standardOutput.contains '+--- test.nebula:b:2.0.0 -> 0.15.0\n' + result.standardOutput.contains '\\--- test.nebula:c:1.0.0 -> 0.15.0\n' + } + + def 'alignment outputs warnings and honors static force when dynamic forces are present'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:2.0.0') + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:2.0.0') + .addModule('test.nebula:b:1.00.0') + .addModule('test.nebula:b:0.15.0') + .addModule('test.nebula:c:2.0.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:2.0.0' + compile 'test.nebula:b:2.0.0' + compile 'test.nebula:c:1.0.0' + } + configurations.compile.resolutionStrategy { + force 'test.nebula:a:latest.release' + force 'test.nebula:b:1.+' + force 'test.nebula:c:0.15.0' + } + """.stripIndent() + + when: + def standardOutput = runTasksSuccessfully('dependencies', '--configuration', 'compile').standardOutput + + then: + standardOutput.contains('Resolution rules ruleset alignment-outputs-warnings-and-honors-static-force-when-dynamic-forces-are-present align rule [group: test.nebula] is unable to honor forced versions [latest.release, 1.+]. For a force to take precedence on an align rule, it must use a static version') + standardOutput.contains '+--- test.nebula:a:2.0.0 -> 0.15.0\n' + standardOutput.contains '+--- test.nebula:b:2.0.0 -> 0.15.0\n' + standardOutput.contains '\\--- test.nebula:c:1.0.0 -> 0.15.0\n' + } + + def 'alignment outputs warnings and falls back to default logic, when only dynamic forces are present'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:2.0.0') + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:0.15.0') + .addModule('test.nebula:b:2.0.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:0.15.0') + .addModule('test.nebula:c:2.0.0') + .addModule('test.nebula:c:1.0.0') + .addModule('test.nebula:c:0.15.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:2.0.0' + compile 'test.nebula:b:1.0.0' + compile 'test.nebula:c:0.15.0' + } + configurations.compile.resolutionStrategy { + force 'test.nebula:a:latest.release' + force 'test.nebula:b:1.+' + force 'test.nebula:c:2.+' + } + """.stripIndent() + + when: + def standardOutput = runTasksSuccessfully('dependencies', '--configuration', 'compile').standardOutput + + then: + standardOutput.contains('Resolution rules ruleset alignment-outputs-warnings-and-falls-back-to-default-logic-when-only-dynamic-forces-are-present align rule [group: test.nebula] is unable to honor forced versions [latest.release, 1.+, 2.+]. For a force to take precedence on an align rule, it must use a static version') + standardOutput.contains('No static forces found for ruleset alignment-outputs-warnings-and-falls-back-to-default-logic-when-only-dynamic-forces-are-present align rule [group: test.nebula]. Falling back to default alignment logic') + standardOutput.contains '+--- test.nebula:a:2.0.0\n' + standardOutput.contains '+--- test.nebula:b:1.0.0 -> 2.0.0\n' + standardOutput.contains '\\--- test.nebula:c:0.15.0 -> 2.0.0\n' + } +} diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesSpec.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesSpec.groovy deleted file mode 100644 index be0f6f8..0000000 --- a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesSpec.groovy +++ /dev/null @@ -1,1210 +0,0 @@ -/* - * Copyright 2016 Netflix, Inc. - * - * 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 nebula.plugin.resolutionrules - -import nebula.test.IntegrationSpec -import nebula.test.dependencies.DependencyGraphBuilder -import nebula.test.dependencies.GradleDependencyGenerator -import nebula.test.dependencies.ModuleBuilder - -class AlignRulesSpec extends IntegrationSpec { - def rulesJsonFile - - def setup() { - rulesJsonFile = new File(projectDir, "${moduleName}.json") - buildFile << """\ - ${applyPlugin(ResolutionRulesPlugin)} - apply plugin: 'java' - - dependencies { - resolutionRules files('$rulesJsonFile') - } - """.stripIndent() - - settingsFile << '''\ - rootProject.name = 'aligntest' - '''.stripIndent() - } - - def 'can align direct dependencies if necessary'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n' - } - - def 'can align direct dependencies from ivy repositories'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .build() - GradleDependencyGenerator ivyrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen") - ivyrepo.generateTestIvyRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - ${ivyrepo.ivyRepositoryBlock} - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n' - } - - def 'can align dynamic dependencies'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:1.0.1') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.+' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '\\--- test.nebula:a:1.+ -> 1.0.1\n' - } - - def 'can align dynamic range dependencies'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:1.0.1') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:[1.0.0, 2.0.0)' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '\\--- test.nebula:a:[1.0.0, 2.0.0) -> 1.0.1\n' - } - - def 'can align transitive dependencies'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:1.1.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:1.1.0') - .addModule(new ModuleBuilder('test.other:c:1.0.0').addDependency('test.nebula:b:1.1.0').build()) - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.other:c:1.0.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 1.1.0\n' - result.standardOutput.contains '\\--- test.other:c:1.0.0\n' - result.standardOutput.contains ' \\--- test.nebula:b:1.1.0\n' - } - - def 'can align deeper transitive dependencies'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:1.1.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:1.1.0') - .addModule(new ModuleBuilder('test.other:c:1.0.0').addDependency('test.nebula:b:1.1.0').build()) - .addModule(new ModuleBuilder('test.other:d:1.0.0').addDependency('test.other:c:1.0.0').build()) - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.other:d:1.0.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 1.1.0\n' - result.standardOutput.contains '\\--- test.other:d:1.0.0\n' - result.standardOutput.contains ' \\--- test.other:c:1.0.0\n' - result.standardOutput.contains ' \\--- test.nebula:b:1.1.0\n' - } - - def 'align rules do not replace changes made by other rules'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:2.0.0') - .addModule(new ModuleBuilder('test.nebula:b:1.0.0').addDependency('test.nebula:a:1.0.0').build()) - .addModule(new ModuleBuilder('test.nebula.ext:b:1.0.0').addDependency('test.nebula:a:1.0.0').build()) - .addModule(new ModuleBuilder('test.nebula.ext:b:2.0.0').addDependency('test.nebula:a:2.0.0').build()) - .addModule(new ModuleBuilder('test.other:c:1.0.0').addDependency('test.nebula:b:1.0.0').build()) - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "substitute": [ - { - "module": "test.nebula:b", - "with": "test.nebula.ext:b:1.0.0", - "reason": "Library was published with incorrect coordinates", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ], - "align": [ - { - "group": "(test.nebula|test.nebula.ext)", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.other:c:1.0.0' - compile 'test.nebula:a:2.0.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '\\--- test.nebula:b:1.0.0 -> test.nebula.ext:b:2.0.0\n' - } - - def 'can align some dependencies in a group'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:0.42.0') - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:1.1.0') - .addModule('test.nebula:b:0.42.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:1.1.0') - .addModule('test.nebula:c:0.42.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:1.1.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "includes": [ "a", "b" ], - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:1.1.0' - compile 'test.nebula:c:0.42.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 1.1.0' - result.standardOutput.contains '+--- test.nebula:b:1.1.0' - result.standardOutput.contains '\\--- test.nebula:c:0.42.0' - } - - def 'skip aligning some dependencies in a group'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:0.42.0') - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:1.1.0') - .addModule('test.nebula:b:0.42.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:1.1.0') - .addModule('test.nebula:c:0.42.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:1.1.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "excludes": [ "a" ], - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:1.1.0' - compile 'test.nebula:c:0.42.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '+--- test.nebula:b:1.1.0\n' - result.standardOutput.contains '\\--- test.nebula:c:0.42.0 -> 1.1.0' - } - - def 'dependencies with cycles do not lead to infinite loops'() { - def graph = new DependencyGraphBuilder() - .addModule(new ModuleBuilder('test.nebula:a:1.0.0').addDependency('test.other:b:1.0.0').build()) - .addModule(new ModuleBuilder('test.other:b:1.0.0').addDependency('test.nebula:b:1.0.0').build()) - .addModule(new ModuleBuilder('test.nebula:a:1.1.0').addDependency('test.other:b:1.0.0').build()) - .addModule('test.nebula:b:1.0.0') - .addModule(new ModuleBuilder('test.nebula:b:1.1.0').addDependency('test.other:b:1.0.0').build()) - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.1.0' - compile 'test.nebula:b:1.0.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.1.0\n' - result.standardOutput.contains '| \\--- test.other:b:1.0.0\n' - result.standardOutput.contains '| \\--- test.nebula:b:1.0.0 -> 1.1.0\n' - result.standardOutput.contains '| \\--- test.other:b:1.0.0 (*)\n' - result.standardOutput.contains '\\--- test.nebula:b:1.0.0 -> 1.1.0 (*)\n' - } - - def 'able to omit dependency versions to take what is given transitively'() { - def graph = new DependencyGraphBuilder() - .addModule(new ModuleBuilder('test.nebula:a:1.0.0').addDependency('test.nebula:b:1.0.0').build()) - .addModule('test.nebula:b:1.0.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '| \\--- test.nebula:b:1.0.0\n' - result.standardOutput.contains '\\--- test.nebula:b: -> 1.0.0\n' - } - - def 'a project can build in presence of align rules for jars it produces'() { - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "includes": ["aligntest", "b"], - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << '''\ - group = 'test.nebula' - version = '0.1.0' - - apply plugin: 'maven-publish' - - publishing { - publications { - test(MavenPublication) { - from components.java - } - } - repositories { - maven { - name 'repo' - url 'build/repo' - } - } - } - '''.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - noExceptionThrown() - } - - def 'multiple align rules'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:1.1.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:1.1.0') - .addModule('test.other:c:0.12.2') - .addModule('test.other:c:1.0.0') - .addModule('test.other:d:0.12.2') - .addModule('test.other:d:1.0.0') - .build() - def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen") - mavenrepo.generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - }, - { - "name": "testOther", - "group": "test.other", - "reason": "Aligning test", - "author": "Example Tester ", - "date": "2016-04-05T19:19:49.495Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - ${mavenrepo.mavenRepositoryBlock} - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:1.1.0' - compile 'test.other:c:1.0.0' - compile 'test.other:d:0.12.+' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains 'test.nebula:a:1.0.0 -> 1.1.0\n' - result.standardOutput.contains 'test.nebula:b:1.1.0\n' - result.standardOutput.contains 'test.other:c:1.0.0\n' - result.standardOutput.contains 'test.other:d:0.12.+ -> 1.0.0\n' - } - - def 'substitute and align work together'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .addModule('old.org:sub:0.1.0') - .addModule('new.org:sub:0.2.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], - "substitute": [ - { - "module" : "old.org:sub", - "with" : "new.org:sub:latest.release", - "reason" : "swap old.org to new.org", - "author" : "Example Person ", - "date" : "2016-03-18T20:21:20.368Z" - } - ], - "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:0.15.0' - compile 'old.org:sub:latest.release' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '+--- test.nebula:b:0.15.0 -> 1.0.0\n' - result.standardOutput.contains '\\--- old.org:sub:latest.release -> new.org:sub:0.2.0\n' - } - - def 'jcenter align'() { - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "com.google.guava", - "reason": "Align guava", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { jcenter() } - dependencies { - compile 'com.google.guava:guava:12.0' - } - """ - - when: - runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - noExceptionThrown() - } - - def 'unresolvable dependencies cause warnings to be output'() { - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "com.google.guava", - "reason": "Align guava", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { jcenter() } - dependencies { - compile 'org.slf4j:slf4j-api:1.7.21' - compile 'com.google.guava:guava:oops' - } - """ - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains("Resolution rules could not resolve all dependencies to align in configuration 'compile' should also fail to resolve") - } - - def 'can add additional resolution rules outside of plugin'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .addModule('test.example:c:0.1.0') - .addModule('test.example:c:0.2.0') - .addModule('test:x:1.0.0') - .addModule('test:x:1.0.1') - .addModule('test:y:1.0.0') - .addModule('test:y:1.0.1') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - configurations.all { - resolutionStrategy { - force 'test.example:c:0.1.0' - eachDependency { details -> - if (details.requested.group == 'test') { - details.useTarget group: details.requested.group, name: details.requested.name, version: '1.0.0' - } - } - } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:0.15.0' - compile 'test.example:c:latest.release' - compile 'test:x:1.+' - compile 'test:y:1.+' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '+--- test.nebula:b:0.15.0 -> 1.0.0\n' - result.standardOutput.contains '+--- test.example:c:latest.release -> 0.1.0\n' - result.standardOutput.contains '+--- test:x:1.+ -> 1.0.0\n' - result.standardOutput.contains '\\--- test:y:1.+ -> 1.0.0\n' - } - - def 'regular expressions supported in groups'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula.one:a:1.0.0') - .addModule('test.nebula.one:a:0.15.0') - .addModule('test.nebula.two:b:1.0.0') - .addModule('test.nebula.two:b:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula.*", - "reason": "Align test.nebula.* dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula.one:a:1.0.0' - compile 'test.nebula.two:b:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula.one:a:1.0.0\n' - result.standardOutput.contains '\\--- test.nebula.two:b:0.15.0 -> 1.0.0\n' - } - - def 'regular expressions supported in includes'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "includes": ["(a|b)"], - "reason": "Align test.nebula a and b dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:0.15.0' - compile 'test.nebula:c:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '+--- test.nebula:b:0.15.0 -> 1.0.0\n' - result.standardOutput.contains '\\--- test.nebula:c:0.15.0\n' - } - - def 'regular expressions supported in excludes'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "excludes": ["(b|c)"], - "reason": "Align test.nebula a and b dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:0.15.0' - compile 'test.nebula:c:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' - result.standardOutput.contains '+--- test.nebula:b:0.15.0\n' - result.standardOutput.contains '\\--- test.nebula:c:0.15.0\n' - } - - def 'alignment does not apply to dependencies that already have the expected version'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencyInsight', '--configuration', 'compile', '--dependency', 'test.nebula') - - then: - result.standardOutput.contains 'Resolution rules ruleset alignment-does-not-apply-to-dependencies-that-already-have-the-expected-version rule [group: test.nebula] aligning test.nebula:b to 1.0.0' - result.standardOutput.contains 'test.nebula:a:1.0.0\n' - result.standardOutput.contains 'test.nebula:b:0.15.0 -> 1.0.0\n' - } - - def 'alignment uses forced version, rather than highest version, when a force is present'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:0.15.0') - .addModule('test.nebula.other:a:1.0.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:1.0.0' - compile 'test.nebula:b:1.0.0' - compile 'test.nebula:c:0.15.0' - compile 'test.nebula.other:a:1.0.0' - } - configurations.compile.resolutionStrategy { - force 'test.nebula:a:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains 'Found force(s) [test.nebula:a:0.15.0] that supersede resolution ruleset alignment-uses-forced-version-rather-than-highest-version-when-a-force-is-present align rule [group: test.nebula]. Will use 0.15.0 instead of 1.0.0' - result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 0.15.0\n' - result.standardOutput.contains '+--- test.nebula:b:1.0.0 -> 0.15.0\n' - result.standardOutput.contains '+--- test.nebula:c:0.15.0\n' - result.standardOutput.contains '\\--- test.nebula.other:a:1.0.0\n' - } - - def 'alignment uses lowest forced version, when multiple forces are present'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:2.0.0') - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:2.0.0') - .addModule('test.nebula:b:1.00.0') - .addModule('test.nebula:b:0.15.0') - .addModule('test.nebula:c:2.0.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:2.0.0' - compile 'test.nebula:b:2.0.0' - compile 'test.nebula:c:1.0.0' - } - configurations.compile.resolutionStrategy { - force 'test.nebula:a:2.0.0' - force 'test.nebula:b:1.0.0' - force 'test.nebula:c:0.15.0' - } - """.stripIndent() - - when: - def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') - - then: - result.standardOutput.contains '+--- test.nebula:a:2.0.0 -> 0.15.0\n' - result.standardOutput.contains '+--- test.nebula:b:2.0.0 -> 0.15.0\n' - result.standardOutput.contains '\\--- test.nebula:c:1.0.0 -> 0.15.0\n' - } - - def 'alignment outputs warnings and honors static force when dynamic forces are present'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:2.0.0') - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:2.0.0') - .addModule('test.nebula:b:1.00.0') - .addModule('test.nebula:b:0.15.0') - .addModule('test.nebula:c:2.0.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:2.0.0' - compile 'test.nebula:b:2.0.0' - compile 'test.nebula:c:1.0.0' - } - configurations.compile.resolutionStrategy { - force 'test.nebula:a:latest.release' - force 'test.nebula:b:1.+' - force 'test.nebula:c:0.15.0' - } - """.stripIndent() - - when: - def standardOutput = runTasksSuccessfully('dependencies', '--configuration', 'compile').standardOutput - - then: - standardOutput.contains('Resolution rules ruleset alignment-outputs-warnings-and-honors-static-force-when-dynamic-forces-are-present align rule [group: test.nebula] is unable to honor forced versions [latest.release, 1.+]. For a force to take precedence on an align rule, it must use a static version') - standardOutput.contains '+--- test.nebula:a:2.0.0 -> 0.15.0\n' - standardOutput.contains '+--- test.nebula:b:2.0.0 -> 0.15.0\n' - standardOutput.contains '\\--- test.nebula:c:1.0.0 -> 0.15.0\n' - } - - def 'alignment outputs warnings and falls back to default logic, when only dynamic forces are present'() { - def graph = new DependencyGraphBuilder() - .addModule('test.nebula:a:2.0.0') - .addModule('test.nebula:a:1.0.0') - .addModule('test.nebula:a:0.15.0') - .addModule('test.nebula:b:2.0.0') - .addModule('test.nebula:b:1.0.0') - .addModule('test.nebula:b:0.15.0') - .addModule('test.nebula:c:2.0.0') - .addModule('test.nebula:c:1.0.0') - .addModule('test.nebula:c:0.15.0') - .build() - File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() - - rulesJsonFile << '''\ - { - "deny": [], "reject": [], "substitute": [], "replace": [], - "align": [ - { - "name": "testNebula", - "group": "test.nebula", - "reason": "Align test.nebula dependencies", - "author": "Example Person ", - "date": "2016-03-17T20:21:20.368Z" - } - ] - } - '''.stripIndent() - - buildFile << """\ - repositories { - maven { url '${mavenrepo.absolutePath}' } - } - dependencies { - compile 'test.nebula:a:2.0.0' - compile 'test.nebula:b:1.0.0' - compile 'test.nebula:c:0.15.0' - } - configurations.compile.resolutionStrategy { - force 'test.nebula:a:latest.release' - force 'test.nebula:b:1.+' - force 'test.nebula:c:2.+' - } - """.stripIndent() - - when: - def standardOutput = runTasksSuccessfully('dependencies', '--configuration', 'compile').standardOutput - - then: - standardOutput.contains('Resolution rules ruleset alignment-outputs-warnings-and-falls-back-to-default-logic-when-only-dynamic-forces-are-present align rule [group: test.nebula] is unable to honor forced versions [latest.release, 1.+, 2.+]. For a force to take precedence on an align rule, it must use a static version') - standardOutput.contains('No static forces found for ruleset alignment-outputs-warnings-and-falls-back-to-default-logic-when-only-dynamic-forces-are-present align rule [group: test.nebula]. Falling back to default alignment logic') - standardOutput.contains '+--- test.nebula:a:2.0.0\n' - standardOutput.contains '+--- test.nebula:b:1.0.0 -> 2.0.0\n' - standardOutput.contains '\\--- test.nebula:c:0.15.0 -> 2.0.0\n' - } -} diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesTransitiveDependenciesSpec.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesTransitiveDependenciesSpec.groovy new file mode 100644 index 0000000..819acd2 --- /dev/null +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesTransitiveDependenciesSpec.groovy @@ -0,0 +1,184 @@ +package nebula.plugin.resolutionrules + +import nebula.test.dependencies.DependencyGraphBuilder +import nebula.test.dependencies.GradleDependencyGenerator +import nebula.test.dependencies.ModuleBuilder + +class AlignRulesTransitiveDependenciesSpec extends AbstractAlignRulesSpec { + def 'can align transitive dependencies'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:1.1.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:1.1.0') + .addModule(new ModuleBuilder('test.other:c:1.0.0').addDependency('test.nebula:b:1.1.0').build()) + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.other:c:1.0.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 1.1.0\n' + result.standardOutput.contains '\\--- test.other:c:1.0.0\n' + result.standardOutput.contains ' \\--- test.nebula:b:1.1.0\n' + } + + def 'can align deeper transitive dependencies'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule('test.nebula:a:1.1.0') + .addModule('test.nebula:b:1.0.0') + .addModule('test.nebula:b:1.1.0') + .addModule(new ModuleBuilder('test.other:c:1.0.0').addDependency('test.nebula:b:1.1.0').build()) + .addModule(new ModuleBuilder('test.other:d:1.0.0').addDependency('test.other:c:1.0.0').build()) + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.other:d:1.0.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0 -> 1.1.0\n' + result.standardOutput.contains '\\--- test.other:d:1.0.0\n' + result.standardOutput.contains ' \\--- test.other:c:1.0.0\n' + result.standardOutput.contains ' \\--- test.nebula:b:1.1.0\n' + } + + def 'dependencies with cycles do not lead to infinite loops'() { + def graph = new DependencyGraphBuilder() + .addModule(new ModuleBuilder('test.nebula:a:1.0.0').addDependency('test.other:b:1.0.0').build()) + .addModule(new ModuleBuilder('test.other:b:1.0.0').addDependency('test.nebula:b:1.0.0').build()) + .addModule(new ModuleBuilder('test.nebula:a:1.1.0').addDependency('test.other:b:1.0.0').build()) + .addModule('test.nebula:b:1.0.0') + .addModule(new ModuleBuilder('test.nebula:b:1.1.0').addDependency('test.other:b:1.0.0').build()) + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.1.0' + compile 'test.nebula:b:1.0.0' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.1.0\n' + result.standardOutput.contains '| \\--- test.other:b:1.0.0\n' + result.standardOutput.contains '| \\--- test.nebula:b:1.0.0 -> 1.1.0\n' + result.standardOutput.contains '| \\--- test.other:b:1.0.0 (*)\n' + result.standardOutput.contains '\\--- test.nebula:b:1.0.0 -> 1.1.0 (*)\n' + } + + def 'able to omit dependency versions to take what is given transitively'() { + def graph = new DependencyGraphBuilder() + .addModule(new ModuleBuilder('test.nebula:a:1.0.0').addDependency('test.nebula:b:1.0.0').build()) + .addModule('test.nebula:b:1.0.0') + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:a:1.0.0' + compile 'test.nebula:b' + } + """.stripIndent() + + when: + def result = runTasksSuccessfully('dependencies', '--configuration', 'compile') + + then: + result.standardOutput.contains '+--- test.nebula:a:1.0.0\n' + result.standardOutput.contains '| \\--- test.nebula:b:1.0.0\n' + result.standardOutput.contains '\\--- test.nebula:b: -> 1.0.0\n' + } +} diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesVersionMatchSpec.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesVersionMatchSpec.groovy new file mode 100644 index 0000000..5e52701 --- /dev/null +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesVersionMatchSpec.groovy @@ -0,0 +1,87 @@ +package nebula.plugin.resolutionrules + +import nebula.test.dependencies.DependencyGraphBuilder +import nebula.test.dependencies.GradleDependencyGenerator +import nebula.test.dependencies.ModuleBuilder + +class AlignRulesVersionMatchSpec extends AbstractAlignRulesSpec { + def 'match excluding differences in version results in no alignment'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build()) + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "match": "EXCLUDE_SUFFIXES", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:b:1.0.0-1' + } + """.stripIndent() + + when: + def standardOutput = runTasksSuccessfully('dependencies', '--configuration', 'compile').standardOutput + + then: + standardOutput.contains '\\--- test.nebula:b:1.0.0-1\n' + standardOutput.contains '\\--- test.nebula:a:1.0.0\n' + } + + def 'match regex version alignment'() { + def graph = new DependencyGraphBuilder() + .addModule('test.nebula:a:1.0.0') + .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build()) + .build() + File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo() + + rulesJsonFile << '''\ + { + "deny": [], "reject": [], "substitute": [], "replace": [], + "align": [ + { + "name": "testNebula", + "group": "test.nebula", + "match": "^(\\\\d+\\\\.)?(\\\\d+\\\\.)?(\\\\*|\\\\d+)", + "reason": "Align test.nebula dependencies", + "author": "Example Person ", + "date": "2016-03-17T20:21:20.368Z" + } + ] + } + '''.stripIndent() + + buildFile << """\ + repositories { + maven { url '${mavenrepo.absolutePath}' } + } + dependencies { + compile 'test.nebula:b:1.0.0-1' + } + """.stripIndent() + + when: + def standardOutput = runTasksSuccessfully('dependencies', '--configuration', 'compile').standardOutput + + then: + standardOutput.contains '\\--- test.nebula:b:1.0.0-1\n' + standardOutput.contains '\\--- test.nebula:a:1.0.0\n' + } +} diff --git a/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy b/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy index 702cddf..3d67dd8 100644 --- a/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy +++ b/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy @@ -15,6 +15,7 @@ */ package nebula.plugin.resolutionrules +import jdk.nashorn.internal.runtime.Version import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.artifacts.* @@ -30,6 +31,8 @@ import org.gradle.api.specs.Specs import org.joda.time.DateTime import org.joda.time.DateTimeZone +import java.util.regex.Pattern + public class Rules { List replace List substitute @@ -181,12 +184,14 @@ class AlignRule extends BaseRule { String group Collection includes Collection excludes + String match AlignRule(String ruleSet, Map map) { super(ruleSet, map) group = map.group includes = map.includes ?: [] excludes = map.excludes ?: [] + match = map.match } boolean resolvedMatches(ResolvedModuleVersion dep) { @@ -207,15 +212,18 @@ class AlignRule extends BaseRule { @Override String toString() { - if (includes.isEmpty() && excludes.isEmpty()) { - return "[group: $group]" - } else if (includes.isEmpty()) { - return "[group: $group, excludes: $excludes]" - } else if (excludes.isEmpty()) { - return "[group: $group, includes: $includes]" - } else { - return "[group: $group, includes: $includes, excludes: $excludes]" + StringBuilder sb = new StringBuilder("[group: $group") + if (!includes.isEmpty()) { + sb.append ", includes: $includes]" + } + if (!excludes.isEmpty()) { + sb.append ", excludes: $excludes" } + if (match) { + sb.append(", match: $match") + } + sb.append("]") + return sb.toString() } } @@ -266,7 +274,7 @@ class AlignRules implements ProjectConfigurationRule { if (foundMatch) { def rule = foundMatch.key def version = foundMatch.value - if (version != details.requested.version) { + if (version != matchedVersion(rule, details.requested.version)) { LOGGER.info("Resolution rules ruleset ${rule.ruleSet} rule $rule aligning ${details.requested.group}:${details.requested.name} to $version") details.useVersion version } @@ -278,14 +286,15 @@ class AlignRules implements ProjectConfigurationRule { private static String alignedVersion(AlignRule rule, List moduleVersions, Configuration configuration, VersionSelectorScheme scheme, Comparator comparator) { + def versions = moduleVersions.collect { matchedVersion(rule, it.id.version) }.toUnique() + def highestVersion = versions.max { String a, String b -> comparator.compare(a, b) } + List forced = moduleVersions.findResults { moduleVersion -> configuration.resolutionStrategy.forcedModules.find { def id = moduleVersion.id it.group == id.group && it.name == id.name } } - def versions = moduleVersions.collect { ResolvedModuleVersion dep -> dep.id.version }.toUnique() - def highestVersion = versions.max { String a, String b -> comparator.compare(a, b) } if (forced) { def forcedVersions = forced.collect { it.version }.toUnique() def (dynamicVersions, staticVersions) = forcedVersions.split { version -> @@ -305,6 +314,36 @@ class AlignRules implements ProjectConfigurationRule { } return highestVersion } + + private static String matchedVersion(AlignRule rule, String version) { + def match = rule.match + if (match) { + def pattern = VersionMatcher.values().find { + it.name() == match + } ? VersionMatcher.valueOf(match).pattern() : match + def matcher = version =~ pattern + if (matcher) { + return matcher.group() + } else { + LOGGER.warn("Resolution rules ruleset ${rule.ruleSet} align rule $rule is unable to honor match. $match does not match $version. Will use $version") + } + } + return version + } + + static enum VersionMatcher { + EXCLUDE_SUFFIXES("^(\\d+\\.)?(\\d+\\.)?(\\*|\\d+)") + + private final pattern + + private VersionMatcher(String regex) { + pattern = Pattern.compile(regex) + } + + public Pattern pattern() { + return pattern + } + } } class ExcludeRule extends BaseRule implements ConfigurationRule { From 1aef059b6c9fcc6d34af10b7402ae8b36a267235 Mon Sep 17 00:00:00 2001 From: Danny Thomas Date: Wed, 18 May 2016 14:31:35 -0700 Subject: [PATCH 2/4] Remove dodgy import --- src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy b/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy index 3d67dd8..522436e 100644 --- a/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy +++ b/src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy @@ -15,7 +15,6 @@ */ package nebula.plugin.resolutionrules -import jdk.nashorn.internal.runtime.Version import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.artifacts.* From c3683d9614def733265867f51fd8ef682785863f Mon Sep 17 00:00:00 2001 From: Danny Thomas Date: Wed, 18 May 2016 15:14:54 -0700 Subject: [PATCH 3/4] Add validation rules for known fields. Fixes #28 --- ...roovy => ResolutionRulesPluginSpec.groovy} | 2 +- ...y => ResolutionRulesValidationSpec.groovy} | 13 +++++++--- .../ResolutionJsonValidator.groovy | 24 +++++++++++++++---- 3 files changed, 30 insertions(+), 9 deletions(-) rename src/functionalTest/groovy/nebula/plugin/resolutionrules/{PluginFunctionalTest.groovy => ResolutionRulesPluginSpec.groovy} (99%) rename src/functionalTest/groovy/nebula/plugin/resolutionrules/{PluginValidationTest.groovy => ResolutionRulesValidationSpec.groovy} (93%) diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/PluginFunctionalTest.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/ResolutionRulesPluginSpec.groovy similarity index 99% rename from src/functionalTest/groovy/nebula/plugin/resolutionrules/PluginFunctionalTest.groovy rename to src/functionalTest/groovy/nebula/plugin/resolutionrules/ResolutionRulesPluginSpec.groovy index ccf6648..be5ce83 100644 --- a/src/functionalTest/groovy/nebula/plugin/resolutionrules/PluginFunctionalTest.groovy +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/ResolutionRulesPluginSpec.groovy @@ -23,7 +23,7 @@ import org.codehaus.groovy.runtime.StackTraceUtils /** * Functional test for {@link ResolutionRulesPlugin}. */ -class PluginFunctionalTest extends IntegrationSpec { +class ResolutionRulesPluginSpec extends IntegrationSpec { File rulesJsonFile File optionalRulesJsonFile diff --git a/src/functionalTest/groovy/nebula/plugin/resolutionrules/PluginValidationTest.groovy b/src/functionalTest/groovy/nebula/plugin/resolutionrules/ResolutionRulesValidationSpec.groovy similarity index 93% rename from src/functionalTest/groovy/nebula/plugin/resolutionrules/PluginValidationTest.groovy rename to src/functionalTest/groovy/nebula/plugin/resolutionrules/ResolutionRulesValidationSpec.groovy index cbfbc14..d35dafd 100644 --- a/src/functionalTest/groovy/nebula/plugin/resolutionrules/PluginValidationTest.groovy +++ b/src/functionalTest/groovy/nebula/plugin/resolutionrules/ResolutionRulesValidationSpec.groovy @@ -6,8 +6,7 @@ import nebula.test.IntegrationSpec import static org.codehaus.groovy.runtime.StackTraceUtils.extractRootCause -class PluginValidationTest extends IntegrationSpec { - +class ResolutionRulesValidationSpec extends IntegrationSpec { def rulesJsonFile def rulesJson def rulesTemplate = """\ @@ -52,7 +51,15 @@ class PluginValidationTest extends IntegrationSpec { "date" : "2015-10-07T20:21:20.368Z" } ], - "align": [] + "align": [ + { + "group": "org.slf4j", + "reason": "Align SLF4J dependencies", + "author" : "Danny Thomas ", + "date" : "2015-10-07T20:21:20.368Z" + } + ] + } """ diff --git a/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy b/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy index 5656eee..6d6b91a 100644 --- a/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy +++ b/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy @@ -83,31 +83,45 @@ class ResolutionJsonValidator { return errors } + def validateValidFields = { entry, action, fields -> + def errors = [] + entry.keySet().findAll { !fields.contains(it) }.each { + errors << "* ${action}: ${entry} has an known property '${it}'" + } + return errors + } + def replaceErrors = json.replace.collect({ [ validateModuleName(it, 'replace'), validateWith(it, 'replace'), - validateNonEmptyFields(it, 'replace', ['reason', 'author']) + validateNonEmptyFields(it, 'replace', ['reason', 'author']), + validateValidFields(it, 'replace', ['module', 'with', 'reason', 'author', 'date']) ] }) def substErrors = json.substitute.collect({ [ validateModuleName(it, 'substitute'), validateWith(it, 'substitute'), - validateNonEmptyFields(it, 'substitute', ['reason', 'author']) + validateNonEmptyFields(it, 'substitute', ['reason', 'author']), + validateValidFields(it, 'substitute', ['module', 'with', 'reason', 'author', 'date']) ] }) def denyErrors = json.deny.collect({ [ - validateModuleName(it, 'deny'), validateNonEmptyFields(it, 'deny', ['reason', 'author']) + validateModuleName(it, 'deny'), + validateNonEmptyFields(it, 'deny', ['reason', 'author']), + validateValidFields(it, 'deny', ['module', 'reason', 'author', 'date']) ] }) def rejectErrors = json.reject.collect({ [ - validateModuleName(it, 'reject'), validateNonEmptyFields(it, 'reject', ['reason', 'author']) + validateModuleName(it, 'reject'), validateNonEmptyFields(it, 'reject', ['reason', 'author']), + validateValidFields(it, 'reject', ['module', 'reason', 'author', 'date']) ] }) def alignErrors = json.align.collect({ [ - validateNonEmptyFields(it, 'align', ['group', 'reason', 'author']) + validateNonEmptyFields(it, 'align', ['group', 'reason', 'author']), + validateValidFields(it, 'align', ['group', 'includes', 'excludes', 'match', 'reason', 'author', 'date']) ] }) From aa965b5d2539d93e0d238544e6fb93735fdf948b Mon Sep 17 00:00:00 2001 From: Danny Thomas Date: Wed, 18 May 2016 15:41:36 -0700 Subject: [PATCH 4/4] Add name property to align rules --- .../plugin/resolutionrules/ResolutionJsonValidator.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy b/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy index 6d6b91a..9185693 100644 --- a/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy +++ b/src/main/groovy/nebula/plugin/resolutionrules/ResolutionJsonValidator.groovy @@ -121,7 +121,7 @@ class ResolutionJsonValidator { def alignErrors = json.align.collect({ [ validateNonEmptyFields(it, 'align', ['group', 'reason', 'author']), - validateValidFields(it, 'align', ['group', 'includes', 'excludes', 'match', 'reason', 'author', 'date']) + validateValidFields(it, 'align', ['name', 'group', 'includes', 'excludes', 'match', 'reason', 'author', 'date']) ] })