1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 08:27:12 -07:00

fixes #12. adds support for version ranges

updates MavenId parsing to match gradle's parsing
This commit is contained in:
evanchooly 2015-12-15 21:04:04 -05:00
parent 6b1b141943
commit 6883db4bb2
10 changed files with 489 additions and 37 deletions

View file

@ -35,15 +35,15 @@ public class DepFactory @Inject constructor(val localRepo: LocalRepo,
var packaging = mavenId.packaging var packaging = mavenId.packaging
var repoResult: RepoFinder.RepoResult? var repoResult: RepoFinder.RepoResult?
if (! mavenId.hasVersion) { if (mavenId.version != null) {
if (localFirst) version = localRepo.findLocalVersion(mavenId.groupId, mavenId.artifactId, var localVersion: String? = mavenId.version
mavenId.packaging) if (localFirst) localVersion = localRepo.findLocalVersion(mavenId.groupId, mavenId.artifactId, mavenId.packaging)
if (! localFirst || version == null) { if (! localFirst || localVersion == null) {
repoResult = repoFinder.findCorrectRepo(id) repoResult = repoFinder.findCorrectRepo(id)
if (!repoResult.found) { if (!repoResult.found) {
throw KobaltException("Couldn't resolve $id") throw KobaltException("Couldn't resolve $id")
} else { } else {
version = repoResult.version version = repoResult.version?.version
} }
} }
} }

View file

@ -20,7 +20,13 @@ class MavenId private constructor(val groupId: String, val artifactId: String, v
size == 3 || size == 4 size == 3 || size == 4
} }
private fun isVersion(s: String) : Boolean = Character.isDigit(s[0]) private fun isVersion(s: String): Boolean {
return Character.isDigit(s[0]) || isRangedVersion(s)
}
fun isRangedVersion(s: String): Boolean {
return s.first() in listOf('[', '(') && s.last() in listOf(']', ')')
}
/** /**
* Similar to create(MavenId) but don't run IMavenIdInterceptors. * Similar to create(MavenId) but don't run IMavenIdInterceptors.
@ -38,12 +44,11 @@ class MavenId private constructor(val groupId: String, val artifactId: String, v
groupId = c[0] groupId = c[0]
artifactId = c[1] artifactId = c[1]
if (!c[2].isEmpty()) { if (!c[2].isEmpty()) {
if (isVersion(c[2])) { val split = c[2].split('@')
version = c[2] if (isVersion(split[0])) {
} else { version = split[0]
packaging = c[2]
version = c[3]
} }
packaging = if (split.size == 2) split[1] else null
} }
return MavenId(groupId, artifactId, packaging, version) return MavenId(groupId, artifactId, packaging, version)

View file

@ -7,7 +7,9 @@ import com.beust.kobalt.misc.*
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache import com.google.common.cache.LoadingCache
import kotlinx.dom.asElementList
import kotlinx.dom.parseXml import kotlinx.dom.parseXml
import org.w3c.dom.NodeList
import java.io.File import java.io.File
import java.util.concurrent.Callable import java.util.concurrent.Callable
import java.util.concurrent.ExecutorCompletionService import java.util.concurrent.ExecutorCompletionService
@ -25,8 +27,8 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) {
return FOUND_REPOS.get(id) return FOUND_REPOS.get(id)
} }
data class RepoResult(val hostConfig: HostConfig, val found: Boolean, val version: String, data class RepoResult(val hostConfig: HostConfig, val found: Boolean, val version: Version? = null,
val hasJar: Boolean = true, val snapshotVersion: String = "") val hasJar: Boolean = true, val snapshotVersion: Version? = null)
private val FOUND_REPOS: LoadingCache<String, RepoResult> = CacheBuilder.newBuilder() private val FOUND_REPOS: LoadingCache<String, RepoResult> = CacheBuilder.newBuilder()
.build(object : CacheLoader<String, RepoResult>() { .build(object : CacheLoader<String, RepoResult>() {
@ -62,10 +64,11 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) {
} }
if (results.size > 0) { if (results.size > 0) {
results.sortByDescending { Versions.toLongVersion(it.version) } // results.sortByDescending { Versions.toLongVersion(it.version) }
results.sort({ left, right -> left.version!!.compareTo(right.version!!) })
return results[0] return results[0]
} else { } else {
return RepoResult(HostConfig(""), false, id) return RepoResult(HostConfig(""), false, Version.of(id))
} }
} }
@ -81,28 +84,36 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) {
val groupId = mavenId.groupId val groupId = mavenId.groupId
val artifactId = mavenId.artifactId val artifactId = mavenId.artifactId
if (! mavenId.hasVersion) { if (mavenId.version == null) {
val ud = UnversionedDep(groupId, artifactId) val ud = UnversionedDep(groupId, artifactId)
val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE) val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE)
val foundVersion = findCorrectVersionRelease(ud.toMetadataXmlPath(false, isLocal), repoUrl) val foundVersion = findCorrectVersionRelease(ud.toMetadataXmlPath(false, isLocal), repoUrl)
if (foundVersion != null) { if (foundVersion != null) {
return RepoResult(repo, true, foundVersion) return RepoResult(repo, true, Version.of(foundVersion))
} else { } else {
return RepoResult(repo, false, "") return RepoResult(repo, false)
} }
} else { } else {
val version = mavenId.version val version = Version.of(mavenId.version)
if (version!!.contains("SNAPSHOT")) { if (version.isSnapshot()) {
val dep = SimpleDep(mavenId) val dep = SimpleDep(mavenId)
val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE) val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE)
val snapshotVersion = if (isLocal) version!! val snapshotVersion = if (isLocal) version
else findSnapshotVersion(dep.toMetadataXmlPath(false, isLocal, version), repoUrl) else findSnapshotVersion(dep.toMetadataXmlPath(false, isLocal, version.version), repoUrl)
if (snapshotVersion != null) { if (snapshotVersion != null) {
return RepoResult(repo, true, version, true /* hasJar, potential bug here */, return RepoResult(repo, true, version, true /* hasJar, potential bug here */,
snapshotVersion) snapshotVersion)
} else { } else {
return RepoResult(repo, false, "") return RepoResult(repo, false)
} }
} else if (version.isRangedVersion() ) {
val foundVersion = findRangedVersion(SimpleDep(mavenId), repoUrl)
if (foundVersion != null) {
return RepoResult(repo, true, foundVersion)
} else {
return RepoResult(repo, false)
}
} else { } else {
val dep = SimpleDep(mavenId) val dep = SimpleDep(mavenId)
// Try to find the jar file // Try to find the jar file
@ -118,7 +129,7 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) {
true true
} }
log(2, "Result for $repoUrl for $id: $found") log(2, "Result for $repoUrl for $id: $found")
return RepoResult(repo, found, dep.version, hasJar) return RepoResult(repo, found, Version.of(dep.version), hasJar)
} }
} }
} }
@ -148,7 +159,32 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) {
return null return null
} }
fun findSnapshotVersion(metadataPath: String, repoUrl: String): String? { fun findRangedVersion(dep: SimpleDep, repoUrl: String): Version? {
val l = listOf(dep.groupId.replace(".", "/"), dep.artifactId.replace(".", "/"), "maven-metadata.xml")
var metadataPath = l.joinToString("/")
val versionsXpath = XPATH.compile("/metadata/versioning/versions/version")
// No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists
val url = repoUrl + metadataPath
try {
val doc = parseXml(url)
val version = Version.of(dep.version)
if(version.isRangedVersion()) {
val versions = (versionsXpath.evaluate(doc, XPathConstants.NODESET) as NodeList)
.asElementList().map { Version.of(it.textContent) }
return version.select(versions)
} else {
return Version.of(XPATH.compile("/metadata/versioning/versions/version/$version")
.evaluate(doc, XPathConstants.STRING) as String)
}
} catch(ex: Exception) {
log(2, "Couldn't find metadata at ${url}")
}
return null
}
fun findSnapshotVersion(metadataPath: String, repoUrl: String): Version? {
val timestamp = XPATH.compile("/metadata/versioning/snapshot/timestamp") val timestamp = XPATH.compile("/metadata/versioning/snapshot/timestamp")
val buildNumber = XPATH.compile("/metadata/versioning/snapshot/buildNumber") val buildNumber = XPATH.compile("/metadata/versioning/snapshot/buildNumber")
// No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists // No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists
@ -158,11 +194,11 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) {
val ts = timestamp.evaluate(doc, XPathConstants.STRING) val ts = timestamp.evaluate(doc, XPathConstants.STRING)
val bn = buildNumber.evaluate(doc, XPathConstants.STRING) val bn = buildNumber.evaluate(doc, XPathConstants.STRING)
if (! Strings.isEmpty(ts.toString()) && ! Strings.isEmpty(bn.toString())) { if (! Strings.isEmpty(ts.toString()) && ! Strings.isEmpty(bn.toString())) {
return ts.toString() + "-" + bn.toString() return Version.of(ts.toString() + "-" + bn.toString())
} else { } else {
val lastUpdated = XPATH.compile("/metadata/versioning/lastUpdated") val lastUpdated = XPATH.compile("/metadata/versioning/lastUpdated")
if (! lastUpdated.toString().isEmpty()) { if (! lastUpdated.toString().isEmpty()) {
return lastUpdated.toString() return Version.of(lastUpdated.toString())
} }
} }

View file

@ -20,11 +20,11 @@ open class SimpleDep(open val mavenId: MavenId) : UnversionedDep(mavenId.groupId
fun toPomFile(v: String) = toFile(v, "", ".pom") fun toPomFile(v: String) = toFile(v, "", ".pom")
fun toPomFile(r: RepoFinder.RepoResult) = toFile(r.version, r.snapshotVersion, ".pom") fun toPomFile(r: RepoFinder.RepoResult) = toFile(r.version!!.version, r.snapshotVersion!!.version, ".pom")
fun toJarFile(v: String = version) = toFile(v, "", suffix) fun toJarFile(v: String = version) = toFile(v, "", suffix)
fun toJarFile(r: RepoFinder.RepoResult) = toFile(r.version, r.snapshotVersion, suffix) fun toJarFile(r: RepoFinder.RepoResult) = toFile(r.version!!.version, r.snapshotVersion!!.version, suffix)
fun toPomFileName() = "$artifactId-$version.pom" fun toPomFileName() = "$artifactId-$version.pom"

View file

@ -1,6 +1,12 @@
package com.beust.kobalt.misc package com.beust.kobalt.misc
import com.beust.kobalt.maven.MavenId
import com.google.common.base.CharMatcher import com.google.common.base.CharMatcher
import java.math.BigInteger
import java.util.Arrays
import java.util.Comparator
import java.util.Locale
import java.util.TreeMap
public class Versions { public class Versions {
companion object { companion object {
@ -32,3 +38,357 @@ public class Versions {
} }
} }
} }
class Version(val version: String): Comparable<Version> {
companion object {
private val comparator = VersionComparator()
fun of(string: String): Version {
return Version(string)
}
}
internal val items: List<Item>
private var hash: Int = -1
init {
items = parse(version)
}
private fun parse(version: String): List<Item> {
val items = arrayListOf<Item>()
val tokenizer = Tokenizer(version)
while (tokenizer.next()) {
items.add(tokenizer.toItem())
}
trimPadding(items)
return items
}
private fun trimPadding(items: MutableList<Item>) {
var number: Boolean? = null
var end = items.size - 1
for (i in end downTo 1) {
val item = items[i]
if (item.isNumber != number) {
end = i
number = item.isNumber
}
if (end == i && (i == items.size - 1 || items[i - 1].isNumber == item.isNumber) && item.compareTo(null) == 0) {
items.removeAt(i)
end--
}
}
}
override fun compareTo(other: Version): Int {
return comparator.compare(this, other)
}
override fun equals(other: Any?): Boolean {
return (other is Version) && comparator.compare(this, other) == 0
}
override fun hashCode(): Int {
if ( hash == -1 ) hash = Arrays.hashCode(items.toTypedArray())
return hash
}
override fun toString(): String {
return version
}
fun isSnapshot(): Boolean {
return items.firstOrNull { it.isSnapshot } != null
}
fun isRangedVersion(): Boolean {
return MavenId.isRangedVersion(version)
}
fun select(list: List<Version>): Version? {
if (!(version.first() in listOf('[', '(') && version.last() in listOf(']', ')'))) {
return this
}
var lowerExclusive = version.startsWith("(")
var upperExclusive = version.endsWith(")")
val split = version.drop(1).dropLast(1).split(",")
val lower = Version.of(split[0].substring(1))
val upper = if(split.size > 1) {
Version.of(if (split[1].isNotBlank()) split[1] else Int.MAX_VALUE.toString())
} else {
lower
}
var filtered = list.filter { comparator.compare(it, lower) >= 0 && comparator.compare(it, upper) <= 0 }
if (lowerExclusive && lower.equals(filtered.firstOrNull())) {
filtered = filtered.drop(1)
}
if (upperExclusive && upper.equals(filtered.lastOrNull())) {
filtered = filtered.dropLast(1)
}
return filtered.lastOrNull();
}
}
class VersionComparator: Comparator<Version> {
override fun compare(left: Version, right: Version): Int {
val these = left.items
val those = right.items
var number = true
var index = 0
while (true) {
if (index >= these.size && index >= those.size) {
return 0
} else if (index >= these.size) {
return -comparePadding(those, index, null)
} else if (index >= those.size) {
return comparePadding(these, index, null)
}
val thisItem = these[index]
val thatItem = those[index]
if (thisItem.isNumber != thatItem.isNumber) {
if (number == thisItem.isNumber) {
return comparePadding(these, index, number)
} else {
return -comparePadding(those, index, number)
}
} else {
val rel = thisItem.compareTo(thatItem)
if (rel != 0) {
return rel
}
number = thisItem.isNumber
}
index++
}
}
private fun comparePadding(items: List<Item>, index: Int, number: Boolean?): Int {
var rel = 0
for (i in index..items.size - 1) {
val item = items[i]
if (number != null && number !== item.isNumber) {
break
}
rel = item.compareTo(null)
if (rel != 0) {
break
}
}
return normalize(rel)
}
}
internal class Item(private val kind: Int, private val value: Any) {
// i.e. kind != string/qualifier
val isNumber: Boolean
get() = (kind and KIND_QUALIFIER) == 0
val isSnapshot: Boolean
get() = (kind and KIND_QUALIFIER) != 0 && value == Tokenizer.QUALIFIER_SNAPSHOT
operator fun compareTo(that: Item?): Int {
var rel: Int
if (that == null) {
// null in this context denotes the pad item (0 or "ga")
when (kind) {
KIND_MIN -> rel = -1
KIND_MAX, KIND_BIGINT, KIND_STRING -> rel = 1
KIND_INT, KIND_QUALIFIER -> rel = value as Int
else -> throw IllegalStateException("unknown version item kind " + kind)
}
} else {
rel = kind - that.kind
if (rel == 0) {
when (kind) {
KIND_MAX, KIND_MIN -> {
}
KIND_BIGINT -> rel = (value as BigInteger).compareTo(that.value as BigInteger)
KIND_INT, KIND_QUALIFIER -> rel = (value as Int).compareTo(that.value as Int)
KIND_STRING -> rel = (value as String).compareTo(that.value as String, ignoreCase = true)
else -> throw IllegalStateException("unknown version item kind " + kind)
}
}
}
return rel
}
override fun equals(other: Any?): Boolean {
return (other is Item) && compareTo(other as Item?) == 0
}
override fun hashCode(): Int {
return value.hashCode() + kind * 31
}
override fun toString(): String {
return value.toString()
}
companion object {
val KIND_MAX = 8
val KIND_BIGINT = 5
val KIND_INT = 4
val KIND_STRING = 3
val KIND_QUALIFIER = 2
val KIND_MIN = 0
val MAX = Item(KIND_MAX, "max")
val MIN = Item(KIND_MIN, "min")
}
}
internal class Tokenizer(version: String) {
private val version: String
private var index: Int = 0
private var token: String = ""
private var number: Boolean = false
private var terminatedByNumber: Boolean = false
init {
this.version = if (version.length > 0) version else "0"
}
operator fun next(): Boolean {
val n = version.length
if (index >= n) {
return false
}
var state = -2
var start = index
var end = n
terminatedByNumber = false
while (index < n) {
val c = version[index]
if (c == '.' || c == '-' || c == '_') {
end = index
index++
break
} else {
val digit = Character.digit(c, 10)
if (digit >= 0) {
if (state == -1) {
end = index
terminatedByNumber = true
break
}
if (state == 0) {
// normalize numbers and strip leading zeros (prereq for Integer/BigInteger handling)
start++
}
state = if ((state > 0 || digit > 0)) 1 else 0
} else {
if (state >= 0) {
end = index
break
}
state = -1
}
}
index++
}
if (end - start > 0) {
token = version.substring(start, end)
number = state >= 0
} else {
token = "0"
number = true
}
return true
}
override fun toString(): String {
return token.toString()
}
fun toItem(): Item {
if (number) {
try {
if (token.length < 10) {
return Item(Item.KIND_INT, Integer.parseInt(token))
} else {
return Item(Item.KIND_BIGINT, BigInteger(token))
}
} catch (e: NumberFormatException) {
throw IllegalStateException(e)
}
} else {
if (index >= version.length) {
if ("min".equals(token, ignoreCase = true)) {
return Item.MIN
} else if ("max".equals(token, ignoreCase = true)) {
return Item.MAX
}
}
if (terminatedByNumber && token.length == 1) {
when (token[0]) {
'a', 'A' -> return Item(Item.KIND_QUALIFIER, QUALIFIER_ALPHA)
'b', 'B' -> return Item(Item.KIND_QUALIFIER, QUALIFIER_BETA)
'm', 'M' -> return Item(Item.KIND_QUALIFIER, QUALIFIER_MILESTONE)
}
}
val qualifier = QUALIFIERS[token]
if (qualifier != null) {
return Item(Item.KIND_QUALIFIER, qualifier)
} else {
return Item(Item.KIND_STRING, token.toLowerCase(Locale.ENGLISH))
}
}
}
companion object {
internal val QUALIFIER_ALPHA = -5
internal val QUALIFIER_BETA = -4
internal val QUALIFIER_MILESTONE = -3
internal val QUALIFIER_SNAPSHOT = -1
private val QUALIFIERS = TreeMap<String, Int>(String.CASE_INSENSITIVE_ORDER)
init {
QUALIFIERS.put("alpha", QUALIFIER_ALPHA)
QUALIFIERS.put("beta", QUALIFIER_BETA)
QUALIFIERS.put("milestone", QUALIFIER_MILESTONE)
QUALIFIERS.put("snapshot", QUALIFIER_SNAPSHOT)
QUALIFIERS.put("cr", -2)
QUALIFIERS.put("rc", -2)
QUALIFIERS.put("ga", 0)
QUALIFIERS.put("final", 0)
QUALIFIERS.put("", 0)
QUALIFIERS.put("sp", 1)
}
}
}
private fun normalize(value: Int): Int {
return when {
value == 0 -> 0
value > 0 -> 1
else -> -1
}
}

View file

@ -6,8 +6,10 @@ import org.testng.annotations.Guice
@Guice(modules = arrayOf(TestModule::class)) @Guice(modules = arrayOf(TestModule::class))
open class KobaltTest { open class KobaltTest {
@BeforeSuite companion object {
public fun bs() { @BeforeSuite
Kobalt.INJECTOR = com.google.inject.Guice.createInjector(TestModule()) public fun bs() {
Kobalt.INJECTOR = com.google.inject.Guice.createInjector(TestModule())
}
} }
} }

View file

@ -16,6 +16,8 @@ public class DependencyTest @Inject constructor(val depFactory: DepFactory,
@DataProvider @DataProvider
fun dpVersions(): Array<Array<out Any>> { fun dpVersions(): Array<Array<out Any>> {
return arrayOf( return arrayOf(
arrayOf("0.1", "0.1.1"),
arrayOf("0.1", "1.4"),
arrayOf("6.9.4", "6.9.5"), arrayOf("6.9.4", "6.9.5"),
arrayOf("1.7", "1.38"), arrayOf("1.7", "1.38"),
arrayOf("1.70", "1.380"), arrayOf("1.70", "1.380"),

View file

@ -26,7 +26,7 @@ public class DownloadTest @Inject constructor(
executor = executors.newExecutor("DependentTest", 5) executor = executors.newExecutor("DependentTest", 5)
} }
private fun deleteDir() : Boolean { private fun deleteDir(): Boolean {
val dir = File(localRepo.toFullPath("$groupId")) val dir = File(localRepo.toFullPath("$groupId"))
val result = dir.deleteRecursively() val result = dir.deleteRecursively()
return result return result
@ -52,7 +52,7 @@ public class DownloadTest @Inject constructor(
val version = "2.9.1" val version = "2.9.1"
val previousVersion = "2.9" val previousVersion = "2.9"
val groupId = "joda-time" val groupId = "joda-time"
val artifactId = "joda-time" val artifactId = "joda-time"
val jarFile = "$artifactId-$version.jar" val jarFile = "$artifactId-$version.jar"
val idNoVersion = "$groupId:$artifactId:" val idNoVersion = "$groupId:$artifactId:"
@ -72,12 +72,27 @@ public class DownloadTest @Inject constructor(
} }
} }
@Test
public fun shouldDownloadRangedVersion() {
File(localRepo.toFullPath("javax/servlet/servlet-api")).deleteRecursively()
testRange("[2.5,)", "3.0-alpha-1")
}
private fun testRange(range: String, expected: String) {
val dep = depFactory.create("javax.servlet:servlet-api:${range}", executor)
val future = dep.jarFile
val file = future.get()
Assert.assertFalse(future is CompletedFuture)
Assert.assertEquals(file.getName(), "servlet-api-${expected}.jar")
Assert.assertTrue(file.exists())
}
@Test @Test
public fun shouldFindLocalJar() { public fun shouldFindLocalJar() {
MavenDependency.create("$idNoVersion$version") MavenDependency.create("$idNoVersion$version")
val dep = depFactory.create("$idNoVersion$version", executor) val dep = depFactory.create("$idNoVersion$version", executor)
val future = dep.jarFile val future = dep.jarFile
// Assert.assertTrue(future is CompletedFuture) // Assert.assertTrue(future is CompletedFuture)
val file = future.get() val file = future.get()
Assert.assertTrue(file.exists()) Assert.assertTrue(file.exists())
} }

View file

@ -15,7 +15,7 @@ class MavenIdTest {
null, null), null, null),
arrayOf("com.google.inject:guice:4.0:no_aop", arrayOf("com.google.inject:guice:4.0:no_aop",
"com.google.inject", "guice", "4.0", null, "no_aop"), "com.google.inject", "guice", "4.0", null, "no_aop"),
arrayOf("com.android.support:appcompat-v7:aar:22.2.1", arrayOf("com.android.support:appcompat-v7:22.2.1@aar",
"com.android.support", "appcompat-v7", "22.2.1", "aar", null) "com.android.support", "appcompat-v7", "22.2.1", "aar", null)
) )
} }
@ -30,4 +30,4 @@ class MavenIdTest {
Assert.assertEquals(mi.packaging, packaging) Assert.assertEquals(mi.packaging, packaging)
// Assert.assertEquals(mi.qualifier, qualifier) // Assert.assertEquals(mi.qualifier, qualifier)
} }
} }

View file

@ -0,0 +1,32 @@
package com.beust.kobalt.misc
import com.beust.kobalt.KobaltTest
import org.testng.Assert
import org.testng.annotations.Test
class VersionTest : KobaltTest() {
@Test
fun snapshot() {
val version = Version.of("1.2.0-SNAPSHOT")
Assert.assertTrue(version.isSnapshot())
}
@Test
fun rangedVersions() {
val ranged = Version.of("[2.5,)")
Assert.assertTrue(ranged.isRangedVersion())
}
@Test
fun selectVersion() {
var versions = listOf("2.4.public_draft", "2.2", "2.3", "2.4", "2.4-20040521", "2.5", "3.0-alpha-1").map { Version.of(it) }
Assert.assertEquals(Version.of("[2.5,)").select(versions), Version.of("3.0-alpha-1"))
Assert.assertEquals(Version.of("[2.5,3.0)").select(versions), Version.of("3.0-alpha-1"))
Assert.assertEquals(Version.of("[2.6-SNAPSHOT,)").select(versions), Version.of("3.0-alpha-1"))
versions = listOf("1.0", "1.1", "1.2", "1.2.3", "1.3", "1.4.2", "1.5-SNAPSHOT").map { Version.of(it) }
Assert.assertEquals(Version.of("[1.2,1.2.3)").select(versions), Version.of("1.2"))
Assert.assertEquals(Version.of("[1.2,1.2.3]").select(versions), Version.of("1.2.3"))
}
}