-
Notifications
You must be signed in to change notification settings - Fork 273
feat: implement coarse versioning for future matching against #4590
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
base: master
Are you sure you want to change the base?
Changes from all commits
d8ec74d
cef9b23
99a202a
289a15f
cbc3c5d
82d2b78
31cf004
bdd996e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,3 +16,4 @@ hurl-scripts/ | |
| temp/* | ||
| **/tmp/** | ||
| gcp/api/v1/osv/** | ||
| .hypothesis | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,24 @@ def test_apk(self): | |
| self.assertLessEqual( | ||
| ecosystem.sort_key('1.2.0-r0'), ecosystem.sort_key('1.10.0-r0')) | ||
|
|
||
| def test_coarse_version(self): | ||
| """Test coarse version.""" | ||
| ecosystem = alpine.APK() | ||
| self.assertEqual('00:00000010.00000002.00000033', | ||
| ecosystem.coarse_version('10.2.33')) | ||
| self.assertEqual('00:00000004.00000005.00000000', | ||
| ecosystem.coarse_version('4.5_alpha')) | ||
| self.assertEqual('00:20200712.00000000.00000000', | ||
| ecosystem.coarse_version('20200712-r0')) | ||
| self.assertEqual('00:00000011.00000003.00000020', | ||
| ecosystem.coarse_version('11.3.20.1_p1-r0')) | ||
| self.assertEqual('00:00000002.00000003.00000000', | ||
| ecosystem.coarse_version('02.3')) | ||
| self.assertEqual('00:00000005.00000000.00000000', | ||
| ecosystem.coarse_version('5.06.7')) | ||
| self.assertEqual('00:00000005.00000000.00000000', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like it does trim 5.0.9, should this be happening?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah - 5.0.9 < 5.01.0 so everything after the first 0 must be truncated to 0 |
||
| ecosystem.coarse_version('5.0.9')) | ||
|
|
||
| def test_apk_ecosystems(self): | ||
| """Test apk-based ecosystems return an APK ecosystem.""" | ||
| ecos = [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # 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. | ||
| """Coarse version monotonicity tests.""" | ||
|
|
||
| import re | ||
| import unittest | ||
| from hypothesis import given, example, strategies as st | ||
| import packaging.version | ||
|
|
||
| from .. import ecosystems | ||
| from ..third_party.univers.gem import GemVersion | ||
|
|
||
| from . import alpine | ||
| from . import cran | ||
| from . import debian | ||
| from . import haskell | ||
| from . import maven | ||
| from . import nuget | ||
| from . import packagist | ||
| from . import pub | ||
| from . import pypi | ||
| from . import redhat | ||
| from . import rubygems | ||
| from . import semver_ecosystem_helper | ||
|
|
||
| # Strategies | ||
|
|
||
| # Matches standard SemVer: major.minor.patch, optional 'v', prerelease/build. | ||
| # Note: OSV's SemVer implementation coerces partial versions | ||
| # (e.g. '1.0' -> '1.0.0'). | ||
| semver_strategy = st.from_regex( | ||
| r'^v?[0-9]+(\.[0-9]+){0,2}(-[0-9a-zA-z.-]*)?\+?[0-9a-zA-z.-]*$') | ||
|
|
||
| # Matches standard Alpine versions like 1.2.3, optionally with suffixes | ||
| # like _rc1, _p2, and revision -r3. | ||
| apk_version_strategy = st.from_regex( | ||
| r'^[0-9]+(\.[0-9]+)*(_rc[0-9]*|_p[0-9]*)*(-r[0-9]+)?$') | ||
|
|
||
| # Matches R versions: sequence of numbers separated by dots or dashes | ||
| # (e.g. 1.2-3). | ||
| cran_version_strategy = st.from_regex(r'^[0-9]+([.-][0-9]+)+$') | ||
|
|
||
| # Matches Debian versions: optional epoch, upstream version | ||
| # (alphanumerics/separators), optional debian revision. | ||
| dpkg_version_strategy = st.from_regex( | ||
| r'^(\d+:)?\d([A-Za-z0-9\.\+\~\-]+|[A-Za-z0-9\.\+\~]+-[A-Za-z0-9\+\.\~]+)?$') | ||
|
|
||
| # Matches Haskell versions: dot-separated integers (e.g. 1.2.3). | ||
| hackage_version_strategy = st.from_regex(r'^[0-9]+(\.[0-9]+)*$') | ||
|
|
||
| # Matches Maven versions: flexible sequence of numbers or identifiers | ||
| # separated by dots or dashes. | ||
| maven_version_strategy = st.from_regex(r'^(([0-9]*|[A-Za-z+]*)[.-]?)*$') | ||
|
|
||
| # Matches NuGet versions: SemVer-like, optional 'v' prefix, 4th component, | ||
| # prerelease/build metadata. | ||
| nuget_version_strategy = st.from_regex( | ||
| r'^v?[0-9]+(\.[0-9]+){0,3}(-[0-9a-zA-z.-]*)?\+?[0-9a-zA-z.-]*$') | ||
|
|
||
| # Matches Packagist versions: 'v' prefix, flexible components separated by | ||
| # ., +, _, -. | ||
| packagist_version_strategy = st.from_regex(r'^v?(([0-9]*|[A-Za-z+]*)[.+_-]?)*$') | ||
|
|
||
| # Pub versions are the same format as SemVer. | ||
| pub_version_strategy = semver_strategy | ||
|
|
||
| # Uses standard packaging.version pattern. | ||
| pypi_strategy = st.one_of( | ||
| st.text(), # legacy version can be any string | ||
| st.from_regex( | ||
| re.compile(r'^' + packaging.version.VERSION_PATTERN + r'$', | ||
| re.IGNORECASE | re.VERBOSE | re.ASCII))) | ||
|
|
||
| # Matches RPM versions: optional epoch, alternating alphanumeric segments. | ||
| rpm_version_strategy = st.from_regex( | ||
| re.compile(r'^([0-9]+:)?(([0-9]+|[A-Za-z]+)((?![0-9A-Za-z])[ -~])*)+$', | ||
|
||
| re.ASCII)) | ||
|
|
||
| # Uses standard GemVersion pattern. | ||
| rubygems_version_strategy = st.from_regex(r'^' + GemVersion.VERSION_PATTERN + | ||
| r'$') | ||
|
|
||
|
|
||
| def check_coarse_version_monotonic(test_case: unittest.TestCase, | ||
| ecosystem: ecosystems.OrderedEcosystem, | ||
| v1_str: str, v2_str: str): | ||
| """Test coarse_version monotonicity.""" | ||
| v1 = ecosystem.sort_key(v1_str) | ||
| v2 = ecosystem.sort_key(v2_str) | ||
| if v2 < v1: | ||
| v1, v2 = v2, v1 | ||
| v1_str, v2_str = v2_str, v1_str | ||
|
|
||
| if v1.is_invalid: | ||
| test_case.assertRaises(ValueError, ecosystem.coarse_version, v1_str) | ||
| if v2.is_invalid: | ||
| test_case.assertRaises(ValueError, ecosystem.coarse_version, v2_str) | ||
|
|
||
| if not v1.is_invalid and not v2.is_invalid: | ||
| v1_coarse = ecosystem.coarse_version(v1_str) | ||
| v2_coarse = ecosystem.coarse_version(v2_str) | ||
| test_case.assertLessEqual(v1_coarse, v2_coarse) | ||
|
|
||
|
|
||
| class CoarseVersionMonotonicityTest(unittest.TestCase): | ||
| """Coarse version monotonicity tests.""" | ||
|
|
||
| @given(apk_version_strategy, apk_version_strategy) | ||
| @example('1.02', '1.1') | ||
| @example('5.0.9', '5.06.7') | ||
| def test_apk(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, alpine.APK(), v1_str, v2_str) | ||
|
|
||
| @given(cran_version_strategy, cran_version_strategy) | ||
| def test_cran(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, cran.CRAN(), v1_str, v2_str) | ||
|
|
||
| @given(dpkg_version_strategy, dpkg_version_strategy) | ||
| def test_dpkg(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, debian.DPKG(), v1_str, v2_str) | ||
|
|
||
| @given(hackage_version_strategy, hackage_version_strategy) | ||
| def test_hackage(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, haskell.Hackage(), v1_str, v2_str) | ||
|
|
||
| @given(maven_version_strategy, maven_version_strategy) | ||
| def test_maven(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, maven.Maven(), v1_str, v2_str) | ||
|
|
||
| @given(nuget_version_strategy, nuget_version_strategy) | ||
| def test_nuget(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, nuget.NuGet(), v1_str, v2_str) | ||
|
|
||
| @given(packagist_version_strategy, packagist_version_strategy) | ||
| def test_packagist(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, packagist.Packagist(), v1_str, v2_str) | ||
|
|
||
| @given(pub_version_strategy, pub_version_strategy) | ||
| def test_pub(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, pub.Pub(), v1_str, v2_str) | ||
|
|
||
| @given(pypi_strategy, pypi_strategy) | ||
| def test_pypi(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, pypi.PyPI(), v1_str, v2_str) | ||
|
|
||
| @given(rpm_version_strategy, rpm_version_strategy) | ||
| def test_rpm(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, redhat.RPM(), v1_str, v2_str) | ||
|
|
||
| @given(rubygems_version_strategy, rubygems_version_strategy) | ||
| def test_rubygems(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, rubygems.RubyGems(), v1_str, v2_str) | ||
|
|
||
| @given(semver_strategy, semver_strategy) | ||
| def test_semver(self, v1_str, v2_str): | ||
| check_coarse_version_monotonic(self, semver_ecosystem_helper.SemverLike(), | ||
| v1_str, v2_str) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does $ also match
\r\n? Might be worth it to call strip(), since I don't believe any versioning scheme have white spaces as part of the spec.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, at least not on Linux.
I would rather not call strip, just in case