ExactVersionMatcher uses non standard collation order


(Ronald Brindl) #1

ExactVersionMatcher, which is used by the VersionRangeMatcher (e.g. looking for a version like “[0.5.9, 0.5.9.20140812-211212]”) uses a collation order for comparing that does not match the standard collation sequences as defined by ASCII (http://tools.ietf.org/html/rfc20) or UTF (http://www.unicode.org/charts/PDF/U0000.pdf and http://www.unicode.org/charts/PDF/UFF00.pdf)

As opposed to any of the widely used Latin character sets, ExactVersionMatcher treats Digits to be greater than Characters.

import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ExactVersionMatcher
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionRangeMatcher
  import spock.lang.Specification
  public class VersionRangeMatcherTest extends Specification {
   def matcher = new VersionRangeMatcher(new ExactVersionMatcher())
   def test() {
  expect:
   matcher.accept(selector, candidate) == result
       where:
   selector
      | candidate
   | result
  // This is how it works now:
   "[0.5.9.0, 0.5.9.local]"
  | "0.5.9.local"
   | false
// local is not between "0" and "local"
   "[0.5.9.a, 0.5.9.local]"
  | "0.5.9.local"
   | true
 // but it is between "a" and "local"
   "[0.5.9.a, 0.5.9.20140812-121314]" | "0.5.9.local"
   | true
  // This is how I expect it to work from ASCII/UTF collation sequence:
   "[0.5.9.0, 0.5.9.local]"
  | "0.5.9.local"
   | true
 // local IS between "0" and "local" (incluseive)
   "[0.5.9.a, 0.5.9.local]"
  | "0.5.9.local"
   | true
 // AND it is between "a" and "local"
   "[0.5.9.a, 0.5.9.20140812-121314]" | "0.5.9.local"
   | false
// IS NOT between "a" and "2014..:" ("local" > "2014..."
 }
}

A very simple fix would be in ExactVersionMatcher, around lines 79-84 to switch the return values of:

if (is1Number && !is2Number) {
                return 1;
            }
            if (is2Number && !is1Number) {
                return -1;
            }

However, this might of course have big implications on code that relies on the current (wrong) interpretation. I also had a look at the original Ivy org.apache.ivy.plugins.version.VersionRangeMatcher and especially at the LatestRevisionStrategy that is used for comparing concrete version like ExactVersionMatcher does in gradle and saw that the compare logic looks quite similar and in fact my unit tests fail in the same way.

I guess this “works as designed”?


#2

Yes, Gradle considers letters to be before numbers in a version range: so “1.0.a” < “1.0”. This behaviour was inherited from Ivy: I think the rationale for the original behaviour is that letters are used for versions like “1.0-alpha”, which comes before “1.0”.

As you mention, we can’t really change this as it would break backward compatibility.