From 6883db4bb2062688c338aa94d2fa930943897da4 Mon Sep 17 00:00:00 2001 From: evanchooly Date: Tue, 15 Dec 2015 21:04:04 -0500 Subject: [PATCH] fixes #12. adds support for version ranges updates MavenId parsing to match gradle's parsing --- .../com/beust/kobalt/maven/DepFactory.kt | 10 +- .../kotlin/com/beust/kobalt/maven/MavenId.kt | 17 +- .../com/beust/kobalt/maven/RepoFinder.kt | 68 +++- .../com/beust/kobalt/maven/SimpleDep.kt | 4 +- .../kotlin/com/beust/kobalt/misc/Versions.kt | 360 ++++++++++++++++++ .../kotlin/com/beust/kobalt/KobaltTest.kt | 8 +- .../com/beust/kobalt/maven/DependencyTest.kt | 2 + .../com/beust/kobalt/maven/DownloadTest.kt | 21 +- .../com/beust/kobalt/maven/MavenIdTest.kt | 4 +- .../com/beust/kobalt/misc/VersionTest.kt | 32 ++ 10 files changed, 489 insertions(+), 37 deletions(-) create mode 100644 src/test/kotlin/com/beust/kobalt/misc/VersionTest.kt diff --git a/src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt b/src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt index 6f85bb8d..b26897e8 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/DepFactory.kt @@ -35,15 +35,15 @@ public class DepFactory @Inject constructor(val localRepo: LocalRepo, var packaging = mavenId.packaging var repoResult: RepoFinder.RepoResult? - if (! mavenId.hasVersion) { - if (localFirst) version = localRepo.findLocalVersion(mavenId.groupId, mavenId.artifactId, - mavenId.packaging) - if (! localFirst || version == null) { + if (mavenId.version != null) { + var localVersion: String? = mavenId.version + if (localFirst) localVersion = localRepo.findLocalVersion(mavenId.groupId, mavenId.artifactId, mavenId.packaging) + if (! localFirst || localVersion == null) { repoResult = repoFinder.findCorrectRepo(id) if (!repoResult.found) { throw KobaltException("Couldn't resolve $id") } else { - version = repoResult.version + version = repoResult.version?.version } } } diff --git a/src/main/kotlin/com/beust/kobalt/maven/MavenId.kt b/src/main/kotlin/com/beust/kobalt/maven/MavenId.kt index fb93ed9a..5aeff499 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/MavenId.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/MavenId.kt @@ -20,7 +20,13 @@ class MavenId private constructor(val groupId: String, val artifactId: String, v 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. @@ -38,12 +44,11 @@ class MavenId private constructor(val groupId: String, val artifactId: String, v groupId = c[0] artifactId = c[1] if (!c[2].isEmpty()) { - if (isVersion(c[2])) { - version = c[2] - } else { - packaging = c[2] - version = c[3] + val split = c[2].split('@') + if (isVersion(split[0])) { + version = split[0] } + packaging = if (split.size == 2) split[1] else null } return MavenId(groupId, artifactId, packaging, version) diff --git a/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt b/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt index f8aa30d4..296d8c9e 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt @@ -7,7 +7,9 @@ import com.beust.kobalt.misc.* import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader import com.google.common.cache.LoadingCache +import kotlinx.dom.asElementList import kotlinx.dom.parseXml +import org.w3c.dom.NodeList import java.io.File import java.util.concurrent.Callable import java.util.concurrent.ExecutorCompletionService @@ -25,8 +27,8 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) { return FOUND_REPOS.get(id) } - data class RepoResult(val hostConfig: HostConfig, val found: Boolean, val version: String, - val hasJar: Boolean = true, val snapshotVersion: String = "") + data class RepoResult(val hostConfig: HostConfig, val found: Boolean, val version: Version? = null, + val hasJar: Boolean = true, val snapshotVersion: Version? = null) private val FOUND_REPOS: LoadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader() { @@ -62,10 +64,11 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) { } 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] } 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 artifactId = mavenId.artifactId - if (! mavenId.hasVersion) { + if (mavenId.version == null) { val ud = UnversionedDep(groupId, artifactId) val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE) val foundVersion = findCorrectVersionRelease(ud.toMetadataXmlPath(false, isLocal), repoUrl) if (foundVersion != null) { - return RepoResult(repo, true, foundVersion) + return RepoResult(repo, true, Version.of(foundVersion)) } else { - return RepoResult(repo, false, "") + return RepoResult(repo, false) } } else { - val version = mavenId.version - if (version!!.contains("SNAPSHOT")) { + val version = Version.of(mavenId.version) + if (version.isSnapshot()) { val dep = SimpleDep(mavenId) val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE) - val snapshotVersion = if (isLocal) version!! - else findSnapshotVersion(dep.toMetadataXmlPath(false, isLocal, version), repoUrl) + val snapshotVersion = if (isLocal) version + else findSnapshotVersion(dep.toMetadataXmlPath(false, isLocal, version.version), repoUrl) if (snapshotVersion != null) { return RepoResult(repo, true, version, true /* hasJar, potential bug here */, snapshotVersion) } 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 { val dep = SimpleDep(mavenId) // Try to find the jar file @@ -118,7 +129,7 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) { true } 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 } - 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 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 @@ -158,11 +194,11 @@ public class RepoFinder @Inject constructor(val executors: KobaltExecutors) { val ts = timestamp.evaluate(doc, XPathConstants.STRING) val bn = buildNumber.evaluate(doc, XPathConstants.STRING) if (! Strings.isEmpty(ts.toString()) && ! Strings.isEmpty(bn.toString())) { - return ts.toString() + "-" + bn.toString() + return Version.of(ts.toString() + "-" + bn.toString()) } else { val lastUpdated = XPATH.compile("/metadata/versioning/lastUpdated") if (! lastUpdated.toString().isEmpty()) { - return lastUpdated.toString() + return Version.of(lastUpdated.toString()) } } diff --git a/src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt b/src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt index a041764d..c05ccf43 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/SimpleDep.kt @@ -20,11 +20,11 @@ open class SimpleDep(open val mavenId: MavenId) : UnversionedDep(mavenId.groupId 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(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" diff --git a/src/main/kotlin/com/beust/kobalt/misc/Versions.kt b/src/main/kotlin/com/beust/kobalt/misc/Versions.kt index f91ec3b6..d70ea3ea 100644 --- a/src/main/kotlin/com/beust/kobalt/misc/Versions.kt +++ b/src/main/kotlin/com/beust/kobalt/misc/Versions.kt @@ -1,6 +1,12 @@ package com.beust.kobalt.misc +import com.beust.kobalt.maven.MavenId 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 { companion object { @@ -32,3 +38,357 @@ public class Versions { } } } + +class Version(val version: String): Comparable { + + companion object { + private val comparator = VersionComparator() + fun of(string: String): Version { + return Version(string) + } + } + + internal val items: List + + private var hash: Int = -1 + + init { + items = parse(version) + } + + private fun parse(version: String): List { + val items = arrayListOf() + + val tokenizer = Tokenizer(version) + while (tokenizer.next()) { + items.add(tokenizer.toItem()) + } + + trimPadding(items) + + return items + } + + private fun trimPadding(items: MutableList) { + 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? { + 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 { + 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, 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.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 + } +} diff --git a/src/test/kotlin/com/beust/kobalt/KobaltTest.kt b/src/test/kotlin/com/beust/kobalt/KobaltTest.kt index 73ae4758..5c09efbf 100644 --- a/src/test/kotlin/com/beust/kobalt/KobaltTest.kt +++ b/src/test/kotlin/com/beust/kobalt/KobaltTest.kt @@ -6,8 +6,10 @@ import org.testng.annotations.Guice @Guice(modules = arrayOf(TestModule::class)) open class KobaltTest { - @BeforeSuite - public fun bs() { - Kobalt.INJECTOR = com.google.inject.Guice.createInjector(TestModule()) + companion object { + @BeforeSuite + public fun bs() { + Kobalt.INJECTOR = com.google.inject.Guice.createInjector(TestModule()) + } } } \ No newline at end of file diff --git a/src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt b/src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt index f5edcf97..afe9cbe5 100644 --- a/src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt +++ b/src/test/kotlin/com/beust/kobalt/maven/DependencyTest.kt @@ -16,6 +16,8 @@ public class DependencyTest @Inject constructor(val depFactory: DepFactory, @DataProvider fun dpVersions(): Array> { return arrayOf( + arrayOf("0.1", "0.1.1"), + arrayOf("0.1", "1.4"), arrayOf("6.9.4", "6.9.5"), arrayOf("1.7", "1.38"), arrayOf("1.70", "1.380"), diff --git a/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt b/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt index 3af41738..4748815c 100644 --- a/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt +++ b/src/test/kotlin/com/beust/kobalt/maven/DownloadTest.kt @@ -26,7 +26,7 @@ public class DownloadTest @Inject constructor( executor = executors.newExecutor("DependentTest", 5) } - private fun deleteDir() : Boolean { + private fun deleteDir(): Boolean { val dir = File(localRepo.toFullPath("$groupId")) val result = dir.deleteRecursively() return result @@ -52,7 +52,7 @@ public class DownloadTest @Inject constructor( val version = "2.9.1" val previousVersion = "2.9" val groupId = "joda-time" - val artifactId = "joda-time" + val artifactId = "joda-time" val jarFile = "$artifactId-$version.jar" 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 public fun shouldFindLocalJar() { MavenDependency.create("$idNoVersion$version") val dep = depFactory.create("$idNoVersion$version", executor) val future = dep.jarFile -// Assert.assertTrue(future is CompletedFuture) + // Assert.assertTrue(future is CompletedFuture) val file = future.get() Assert.assertTrue(file.exists()) } diff --git a/src/test/kotlin/com/beust/kobalt/maven/MavenIdTest.kt b/src/test/kotlin/com/beust/kobalt/maven/MavenIdTest.kt index 71b348f6..9a1a099c 100644 --- a/src/test/kotlin/com/beust/kobalt/maven/MavenIdTest.kt +++ b/src/test/kotlin/com/beust/kobalt/maven/MavenIdTest.kt @@ -15,7 +15,7 @@ class MavenIdTest { null, null), arrayOf("com.google.inject:guice:4.0: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) ) } @@ -30,4 +30,4 @@ class MavenIdTest { Assert.assertEquals(mi.packaging, packaging) // Assert.assertEquals(mi.qualifier, qualifier) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/beust/kobalt/misc/VersionTest.kt b/src/test/kotlin/com/beust/kobalt/misc/VersionTest.kt new file mode 100644 index 00000000..7599dfbc --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/misc/VersionTest.kt @@ -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")) + } +} \ No newline at end of file