Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3 - Constraints don't appear to work as expected #150

Open
antonosmond opened this issue Mar 26, 2020 · 11 comments
Open

v3 - Constraints don't appear to work as expected #150

antonosmond opened this issue Mar 26, 2020 · 11 comments
Labels

Comments

@antonosmond
Copy link

antonosmond commented Mar 26, 2020

The semver spec states that prerelease versions always have a lower precedence than a normal version (https://semver.org/#spec-item-11).
Given:

1.0.0-rc1
1.0.0

1.0.0-rc1 should be < 1.0.0.
Given a constraint e.g. < 1.0.0-0 I'd expect 1.0.0-rc1 to be flagged as true in the constraint check because it IS less than 1.0.0 however this doesn't appear to be working.
Have a look at this: https://play.golang.org/p/boXFhusKDBw
In every case, the prerelease version "should" have a value of true but it doesn't.
Is this a bug or am I misunderstanding something?

@antonosmond
Copy link
Author

What's really strange is the final constraint check - <=1.0-0
In this check 1.0.0-rc1 passes the constraint as true. We know that 1.0.0-rc1 is NOT equal to 1.0.0 therefore it must be less than 1.0.0. If this is true then the constraint <1.0-0 should also flag it as true but it flags it as false.

@yanislavm
Copy link

Isn't this topic related to #21

@mattfarina
Copy link
Member

@antonosmond I think I can answer this. < is less than. So, < 1.0.0-0 is saying that it's looking for versions that are less than 1.0.0-0. The -0 is saying to use 0 as the prerelease. SemVer uses Ascii characters so sorting can be funny. See https://www.asciitable.com/ for ordering. 0 is the lowest value accepted by the spec.

I modified your playground a little to show this. See https://play.golang.org/p/w4UElRmiFQz.
In your original ranges I tested a version of 0.9.0 as it's less than 1. It shows true. I also created a range that's >= 1.0.0-0. This would look for versions that are greater than 1.0.0 and include all prereleases. It shows true for pre-releases.

I hope this helps.

@squaremo
Copy link

squaremo commented Sep 2, 2020

@mattfarina From the output of https://play.golang.org/p/w4UElRmiFQz:

Constraint: <1.0-0
  1.0.0-rc1: false
Constraint: <=1.0-0
  1.0.0-rc1: true

So 1.0.0-rc1 is not less than 1.0-0, but is less than or equal to 1.0-0, so ... it must be equal?

@mattfarina
Copy link
Member

@squaremo I can answer this. 1.0-0 translates into 1.0.x-0. The x is a wild card not a 0.

@mattolenik
Copy link

mattolenik commented Apr 28, 2021

This seems like a pretty severe bug. 1.0.0-beta1 is not considered less than 2.0.0!
https://play.golang.org/p/y8V58z5BGWx
Also appears to be the case in 1.5:
https://play.golang.org/p/AysuEhbnFJs

@afirth
Copy link
Contributor

afirth commented May 28, 2021

@mattolenik expanding to https://play.golang.org/p/t8fAR9oVDV8

Constraint: >=1.0.0
  1.0.0-beta: false
Constraint: <1.0.0
  1.0.0-beta: false
Constraint: <1.x-0
  1.0.0-beta: false
Constraint: =1.x-0
  1.0.0-beta: true
Constraint: >1.x-0
  1.0.0-beta: false

it's not compatible with ranges that don't specify they explicitly work with prereleases. Based on @mattfarina's explanation above, 1.x-0 translates to 1.x.x-x, all wildcards, and it's equal. See also #21 and https://semver.org/#spec-item-9

honestly i think this could be closed, working as intended in #23

@mattfarina
Copy link
Member

It's important to note that semver has a specific format for versions. The range syntax that is in use has no specification. In fact, different systems do ranges differently. Helm is currently patterned after what JS and Rust do. The JS libs (used in npm itself) may be the most widely used range usage.

@mattolenik
Copy link

I must be missing something...how does any of that have bearing on a version starting with major version 1 being less than a version starting with 2? Should I open a second issue for that? Surely my example cannot be expected behavior!

@mattfarina
Copy link
Member

To document, here's some details on what's going on with the examples...

=1.x-0 can be a little hard to parse. It's equal to 1.x.x-0. The .x.x is a range. So, this expands to >= 1.0.0-0 < 2.0.0-0. That means that 1.0.0-beta is within that range. The = at the front is misleading because it equals a range.

>1.x-0 expands to > 1.0.0-0. It's a range. 1.0.0-beta fits within that range.

When wildcards are expanded to ranges it helps to explain why the results are what they are.

@skyzyx
Copy link

skyzyx commented Oct 27, 2023

I know that this is old, but it's still open. Also, some of the Go Playgrounds differed from what was actually being asked, and according to the spec, @mattolenik and @antonosmond are correct that this is a bug.

Let's get rid of the -0 nonsense that befuddled this discussion. Let's just use straight-up, normal versions and ranges.

https://go.dev/play/p/NbjUjgQHpwL

This Go Playground says that:

  • (1.0.0-beta1 < 1.0.0) == false
  • (1.0.0-beta1 < 2.0.0) == false

…which any human knows is incorrect. Reviewing https://semver.org/#spec-item-11, §11.1,2 says:

  1. Precedence MUST be calculated by separating the version into major, minor, patch and pre-release identifiers in that order (Build metadata does not figure into precedence).

  2. Precedence is determined by the first difference when comparing each of these identifiers from left to right as follows: Major, minor, and patch versions are always compared numerically.

Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.

We definitely see this in the example above with numeric-only versions. The following comparisons match both the spec and also human intelligence.

  • (0.9.0 < 1.0.0) == true
  • (1.0.0 < 1.0.0) == false
  • (2.0.0 < 1.0.0) == false
  • (0.9.0 < 2.0.0) == true
  • (1.0.0 < 2.0.0) == true
  • (2.0.0 < 2.0.0) == false

When we get to §11.3, however, we begin to see issues.

  1. When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version:

Example: 1.0.0-alpha < 1.0.0.

If we just look at the example provided, this package fails the test.

  • (1.0.0-alpha < 1.0.0) == false (but should be true)

If we take the other obviously-prerelease versions, we see the following — all of which are incorrect behavior per §11.1-3.

  • (1.0.0-beta1 < 1.0.0) == false (but should be true)
  • (1.0.0-rc1 < 1.0.0) == false (but should be true)
  • (1.0.0-rc.1 < 1.0.0) == false (but should be true)

Important

I want to be absolutely clear that this is not comparing against 1.0.0-0 (prerelease). Instead, it's comparing against 1.0.0 (stable).

I understand that while versions like 1.0.0-1 are common in the real world (e.g., Linux packaging) and have their own specifications, these versions are not valid Semantic Versions (per its spec). If we look at §11.4, this is explained.

  1. Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by comparing each dot separated identifier from left to right until a difference is found as follows:

    1. Identifiers consisting of only digits are compared numerically.

    2. Identifiers with letters or hyphens are compared lexically in ASCII sort order.

    3. Numeric identifiers always have lower precedence than non-numeric identifiers.

    4. A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.

Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.

However, I want to explicitly call out this part — which implies that this rule also applies to versions with different major, minor, and patch values (although does not directly state it). There is a better explanation in §2, where it says:

  1. A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor version, and Z is the patch version. Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0.

Rules are applied in-order:

  1. We go left to right, comparing along the way.
  2. Identifiers consisting of only digits are compared numerically.
  3. Once we get to a prerelease, we compare lexicographically by US-ASCII bytecode.

So, when we use this package to compare certain versions, we can see that the package violates the rules of both §2 and also §11.4.

  • (1.0.0-alpha < 2.0.0) == false (should be true)
  • (1.0.0-beta1 < 2.0.0) == false (should be true)
  • (1.0.0-rc1 < 2.0.0) == false (should be true)
  • (1.0.0-rc.1 < 2.0.0) == false (should be true)

What the spec says, and also what humans expect, is the following:

  • (1.0.0-rc.1 < 2.0.0) == true (package says false)
  • (1.0.0-rc < 2.0.0) == true (package says false)
  • (1.0.0 < 2.0.0) == true (package says true)
  • (1.0 < 2.0) == true (package says true)
  • (1 < 2) == true (package says true)

Looking at a second example, https://go.dev/play/p/XOHvsLysvWE, I threw in a comparison to a prerelease. And when you do this, the comparison is suddenly correct.

  • (1.0.0-alpha < 2.0.0-rc.1) == true (should be true)
  • (1.0.0-beta1 < 2.0.0-rc.1) == true (should be true)
  • (1.0.0-rc1 < 2.0.0-rc.1) == true (should be true)
  • (1.0.0-rc.1 < 2.0.0-rc.1) == true (should be true)
  • (1.0.0-alpha < 2.0.0-0) == true (should be true)
  • (1.0.0-beta1 < 2.0.0-0) == true (should be true)
  • (1.0.0-rc1 < 2.0.0-0) == true (should be true)
  • (1.0.0-rc.1 < 2.0.0-0) == true (should be true)

There appears to be a bug when comparing prerelease vs non-prerelease versions where (at the very least), comparing major versions stops producing correct results.

Of final note is that sorting appears to work correctly. https://go.dev/play/p/hLAs7QTgVln. This bug seems to only affect comparators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants