Auto-updatable version ranges

This commit improves the version format so that the minor and patch
elements can hold a special 'x' character besides the version, i.e.
`1.x.x.RELEASE` or `2.4.x.BUILD-SNAPSHOT`. A `VersionParser` now takes
care to resolve those against a list of known Spring Boot versions.

This is particularly useful in version ranges that have to change when
the latest Spring Boot versions change. Spring Initializr already auto-
udpates itself based on the sagan metadata. When a range is using this
feature, it is also automatically updated.

It might be hard to track the actual range values on a given instance so
an `InfoContributor` is now automatically exposed to list them.

Closes gh-328
This commit is contained in:
Stephane Nicoll
2016-12-06 16:39:23 +01:00
parent 814a4ad260
commit 827b9d6e93
22 changed files with 673 additions and 148 deletions

View File

@@ -112,11 +112,11 @@ class CommandLineHelpGeneratorTests {
def second = new Dependency(id: 'second', description: 'second desc', versionRange: ' [1.2.0.RELEASE,1.3.0.M1) ')
def metadata = InitializrMetadataTestBuilder.withDefaults().addDependencyGroup("test", first, second).build()
String content = generator.generateSpringBootCliCapabilities(metadata, "https://fake-service")
assertThat content, containsString('| first | first desc | >= 1.2.0.RELEASE |')
assertThat content, containsString('| second | second desc | [1.2.0.RELEASE,1.3.0.M1) |')
assertThat content, containsString('| first | first desc | >=1.2.0.RELEASE |')
assertThat content, containsString('| second | second desc | >=1.2.0.RELEASE and <1.3.0.M1 |')
}
private assertCommandLineCapabilities(String content) {
private static assertCommandLineCapabilities(String content) {
assertThat content, containsString("| Rel")
assertThat content, containsString("| dependencies")
assertThat content, containsString("| applicationName")
@@ -124,11 +124,11 @@ class CommandLineHelpGeneratorTests {
assertThat content, not(containsString('| Tags'))
}
private static def createDependency(String id, String name) {
private static createDependency(String id, String name) {
createDependency(id, name, null)
}
private static def createDependency(String id, String name, String description) {
private static createDependency(String id, String name, String description) {
new Dependency(id: id, name: name, description: description)
}

View File

@@ -17,6 +17,7 @@
package io.spring.initializr.metadata
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionParser
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
@@ -108,4 +109,25 @@ class BillOfMaterialsTests {
bom.resolve(Version.parse('1.4.1.RELEASE'))
}
@Test
void resolveRangeWithVariablePatch() {
BillOfMaterials bom = new BillOfMaterials(groupId: 'com.example',
artifactId: 'bom', version: '1.0.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '[1.3.0.RELEASE,1.3.x.RELEASE]', version: '1.1.0')
bom.mappings << new BillOfMaterials.Mapping(
versionRange: '[1.3.x.BUILD-SNAPSHOT,1.4.0.RELEASE)', version: '1.1.1-SNAPSHOT')
bom.validate()
bom.updateVersionRange(new VersionParser(Arrays.asList(
Version.parse("1.3.8.RELEASE"), Version.parse("1.3.9.BUILD-SNAPSHOT"))))
assertThat(bom.resolve(Version.parse('1.3.8.RELEASE')).version, equalTo('1.1.0'))
assertThat(bom.resolve(Version.parse('1.3.9.RELEASE')).version, equalTo('1.1.1-SNAPSHOT'))
bom.updateVersionRange(new VersionParser(Arrays.asList(
Version.parse("1.3.9.RELEASE"), Version.parse("1.3.10.BUILD-SNAPSHOT"))))
assertThat(bom.resolve(Version.parse('1.3.8.RELEASE')).version, equalTo('1.1.0'))
assertThat(bom.resolve(Version.parse('1.3.9.RELEASE')).version, equalTo('1.1.0'))
}
}

View File

@@ -17,6 +17,7 @@
package io.spring.initializr.metadata
import io.spring.initializr.util.Version
import io.spring.initializr.util.VersionParser
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
@@ -201,6 +202,37 @@ class DependencyTests {
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
}
@Test
void resolveMatchingVersionWithVariablePatch() {
def dependency = new Dependency(id: 'web', description: 'A web dependency', version: '0.3.0.RELEASE',
keywords: ['foo', 'bar'], aliases: ['the-web'], facets: ['web'])
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.1.0.RELEASE, 1.1.x.RELEASE]', version: '0.1.0.RELEASE')
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.1.x.BUILD-SNAPSHOT, 1.2.0.RELEASE)', version: '0.2.0.RELEASE')
dependency.resolve()
dependency.updateVersionRanges(new VersionParser(Arrays.asList(
Version.parse("1.1.5.RELEASE"), Version.parse("1.1.6.BUILD-SNAPSHOT"))))
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')),
'org.springframework.boot', 'spring-boot-starter-web', '0.1.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.6.BUILD-SNAPSHOT')),
'org.springframework.boot', 'spring-boot-starter-web', '0.2.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')),
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
dependency.updateVersionRanges(new VersionParser(Arrays.asList(
Version.parse("1.1.6.RELEASE"), Version.parse("1.1.7.BUILD-SNAPSHOT"))))
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')),
'org.springframework.boot', 'spring-boot-starter-web', '0.1.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.6.RELEASE')),
'org.springframework.boot', 'spring-boot-starter-web', '0.1.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.7.BUILD-SNAPSHOT')),
'org.springframework.boot', 'spring-boot-starter-web', '0.2.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')),
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
}
static void validateResolvedWebDependency(
def dependency, def expectedGroupId, def expectedArtifactId, def expectedVersion) {
assertEquals expectedVersion, dependency.version

View File

@@ -17,10 +17,13 @@
package io.spring.initializr.metadata
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import io.spring.initializr.util.Version
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static org.assertj.core.api.Assertions.assertThat
/**
* @author Stephane Nicoll
*/
@@ -146,6 +149,34 @@ class InitializrMetadataTests {
builder.build()
}
@Test
void updateSpringBootVersions() {
def bom = new BillOfMaterials(groupId: 'org.acme', artifactId: 'foo-bom')
bom.mappings << new BillOfMaterials.Mapping(versionRange: '[1.3.0.RELEASE,1.3.x.RELEASE]', version: '1.0.0')
bom.mappings << new BillOfMaterials.Mapping(versionRange: '1.3.x.BUILD-SNAPSHOT', version: '1.1.0-BUILD-SNAPSHOT')
def dependency = new Dependency(id: 'bar')
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.3.0.RELEASE, 1.3.x.RELEASE]', version: '0.1.0.RELEASE')
dependency.mappings << new Dependency.Mapping(
versionRange: '1.3.x.BUILD-SNAPSHOT', version: '0.2.0.RELEASE')
InitializrMetadata metadata = InitializrMetadataTestBuilder
.withDefaults().addDependencyGroup("test", dependency)
.addBom('foo-bom', bom).build();
List<DefaultMetadataElement> bootVersions = Arrays.asList(
new DefaultMetadataElement(id: '1.3.6.RELEASE', name: '1.3.6'),
new DefaultMetadataElement(id: '1.3.7.BUILD-SNAPSHOT', name: '1.3.7'))
metadata.updateSpringBootVersions(bootVersions)
assertThat(metadata.configuration.env.boms['foo-bom']
.resolve(Version.parse('1.3.6.RELEASE')).version).isEqualTo('1.0.0')
assertThat(metadata.configuration.env.boms['foo-bom']
.resolve(Version.parse('1.3.7.BUILD-SNAPSHOT')).version).isEqualTo('1.1.0-BUILD-SNAPSHOT')
assertThat(metadata.dependencies.get('bar')
.resolve(Version.parse('1.3.6.RELEASE')).version).isEqualTo('0.1.0.RELEASE')
assertThat(metadata.dependencies.get('bar')
.resolve(Version.parse('1.3.7.BUILD-SNAPSHOT')).version).isEqualTo('0.2.0.RELEASE')
}
@Test
void invalidParentMissingVersion() {
InitializrMetadataTestBuilder builder = InitializrMetadataTestBuilder

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* 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 io.spring.initializr.util
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.Matchers.equalTo
import static org.hamcrest.Matchers.lessThan
import static org.junit.Assert.assertNull
/**
* Tests for {@link VersionParser}.
*
* @author Stephane Nicoll
*/
class VersionParserTests {
@Rule
public final ExpectedException thrown = ExpectedException.none()
private VersionParser parser = new VersionParser(Collections.EMPTY_LIST)
@Test
void noQualifierString() {
def version = parser.parse('1.2.0')
assertThat(version.toString(), equalTo('1.2.0'))
}
@Test
void withQualifierString() {
def version = parser.parse('1.2.0.RELEASE')
assertThat(version.toString(), equalTo('1.2.0.RELEASE'))
}
@Test
void withQualifierAndVersionString() {
def version = parser.parse('1.2.0.RC2')
assertThat(version.toString(), equalTo('1.2.0.RC2'))
}
@Test
void parseInvalidVersion() {
thrown.expect(InvalidVersionException)
parser.parse('foo')
}
@Test
void safeParseInvalidVersion() {
assertNull parser.safeParse('foo')
}
@Test
void parseVersionWithSpaces() {
assertThat(parser.parse(' 1.2.0.RC3 '),
lessThan(parser.parse('1.3.0.RELEASE')))
}
@Test
void parseVariableVersionMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.3.x.BUILD-SNAPSHOT').toString(),
equalTo('1.3.9.BUILD-SNAPSHOT'))
}
@Test
void parseVariableVersionNoPatchMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.x.x.RELEASE').toString(),
equalTo('1.3.8.RELEASE'))
}
@Test
void parseVariableVersionNoQualifierMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.4.0.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.4.x').toString(),
equalTo('1.4.0.BUILD-SNAPSHOT'))
}
@Test
void parseVariableVersionNoMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.4.x.BUILD-SNAPSHOT').toString(),
equalTo("1.4.999.BUILD-SNAPSHOT"))
}
@Test
void parseVariableVersionNoPatchNoMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.3.9.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('2.x.x.RELEASE').toString(),
equalTo("2.999.999.RELEASE"))
}
@Test
void parseVariableVersionNoQualifierNoMatch() {
List<Version> currentVersions = Arrays.asList(parser.parse('1.3.8.RELEASE'),
parser.parse('1.4.0.BUILD-SNAPSHOT'))
parser = new VersionParser(currentVersions)
assertThat(parser.parse('1.2.x').toString(), equalTo("1.2.999"))
}
@Test
void invalidRange() {
thrown.expect(InvalidVersionException)
parser.parseRange("foo-bar")
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,29 +83,54 @@ class VersionRangeTests {
assertThat('1.1.9.RELEASE', not(match('1.2.0.RELEASE')))
}
@Test
void invalidRange() {
thrown.expect(InvalidVersionException)
VersionRange.parse("foo-bar")
}
@Test
void rangeWithSpaces() {
assertThat('1.2.0.RC3', match('[ 1.2.0.RC1 , 1.2.0.RC5]'))
}
@Test
void matchLatestVersion() {
assertThat('1.2.8.RELEASE', match('[1.2.0.RELEASE,1.2.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.2.9.BUILD-SNAPSHOT')))))
}
@Test
void matchOverLatestVersion() {
assertThat('1.2.10.RELEASE', not(match('[1.2.0.RELEASE,1.2.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.2.9.BUILD-SNAPSHOT'))))))
}
@Test
void matchAsOfCurrentVersion() {
assertThat('1.3.5.RELEASE', match('[1.3.x.RELEASE,1.3.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.3.4.RELEASE'),
Version.parse('1.3.6.BUILD-SNAPSHOT')))))
}
@Test
void matchOverAsOfCurrentVersion() {
assertThat('1.3.5.RELEASE', not(match('[1.3.x.RELEASE,1.3.x.BUILD-SNAPSHOT]',
new VersionParser(Arrays.asList(Version.parse('1.3.7.RELEASE'),
Version.parse('1.3.6.BUILD-SNAPSHOT'))))))
}
private static VersionRangeMatcher match(String range) {
new VersionRangeMatcher(range)
new VersionRangeMatcher(range, new VersionParser(Collections.EMPTY_LIST))
}
private static VersionRangeMatcher match(String range, VersionParser parser) {
new VersionRangeMatcher(range, parser)
}
static class VersionRangeMatcher extends BaseMatcher<String> {
private final VersionRange range;
private final VersionParser parser;
VersionRangeMatcher(String text) {
this.range = VersionRange.parse(text)
VersionRangeMatcher(String text, VersionParser parser) {
this.parser = parser
this.range = parser.parseRange(text)
}
@Override
@@ -113,12 +138,13 @@ class VersionRangeTests {
if (!item instanceof String) {
return false;
}
return this.range.match(Version.parse(item))
return this.range.match(this.parser.parse((String) item))
}
@Override
void describeTo(Description description) {
description.appendText(range)
description.appendText(range.toString())
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,41 +16,20 @@
package io.spring.initializr.util
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import static io.spring.initializr.util.Version.parse
import static io.spring.initializr.util.Version.safeParse
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertNull
import static org.hamcrest.Matchers.comparesEqualTo
import static org.hamcrest.Matchers.equalTo
import static org.hamcrest.Matchers.greaterThan
import static org.hamcrest.Matchers.lessThan
/**
* @author Stephane Nicoll
*/
class VersionTests {
@Rule
public final ExpectedException thrown = ExpectedException.none()
@Test
void noQualifierString() {
def version = parse('1.2.0')
assertThat(version.toString(), equalTo('1.2.0'))
}
@Test
void withQualifierString() {
def version = parse('1.2.0.RELEASE')
assertThat(version.toString(), equalTo('1.2.0.RELEASE'))
}
@Test
void withQualifierAndVersionString() {
def version = parse('1.2.0.RC2')
assertThat(version.toString(), equalTo('1.2.0.RC2'))
}
private static final VersionParser parser = new VersionParser(Collections.EMPTY_LIST)
@Test
void equalNoQualifier() {
@@ -146,20 +125,9 @@ class VersionTests {
assertThat(parse('1.2.0.BUILD-SNAPSHOT'), lessThan(parse('1.2.0.RELEASE')))
}
@Test
void parseInvalidVersion() {
thrown.expect(InvalidVersionException)
parse('foo')
}
@Test
void safeParseInvalidVersion() {
assertNull safeParse('foo')
}
@Test
void parseVersionWithSpaces() {
assertThat(parse(' 1.2.0.RC3 '), lessThan(parse('1.3.0.RELEASE')))
private static Version parse(String text) {
def version = parser.parse(text)
version
}
}