diff --git a/composer_pub.go b/composer_pub.go index 1f8662b..3e9b291 100644 --- a/composer_pub.go +++ b/composer_pub.go @@ -13,6 +13,7 @@ var ( composerOrRegex = regexp.MustCompile(`\s*\|\|?\s*`) composerStabilityFlagRegex = regexp.MustCompile(`(?i)^(.*?)@(dev|alpha|beta|rc|stable)$`) composerVersionRegex = regexp.MustCompile(`(?i)^v?([0-9]+(?:\.[0-9]+){0,3})(?:[-._]?([a-z]+)(?:[.-]?([0-9]+(?:[.-][0-9]+)*))?)?(?:\+[^\s]+)?$`) + composerValidVersionRegex = regexp.MustCompile(`(?i)^v?[0-9]+(?:\.[0-9]+){0,3}(?:[-._]?(?:dev|a|alpha|b|beta|rc|(?-i:[sS])table|p|pl|patch)(?:[.-]?[0-9]+(?:[.-][0-9]+)*)?)?(?:\+[^\s]+)?$`) pubVersionPrefixRegex = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?`) ) @@ -528,7 +529,7 @@ func validComposerVersion(version string) bool { if version == "" || strings.ContainsAny(version, " \t\r\n") { return false } - if _, ok := parseComposerVersion(version); ok { + if composerValidVersionRegex.MatchString(version) { return true } return isComposerBranchVersion(version) || composerNumericBranchRegex.MatchString(version) diff --git a/normalization.go b/normalization.go index c075960..a2315df 100644 --- a/normalization.go +++ b/normalization.go @@ -24,8 +24,7 @@ func validVersionForScheme(version, scheme string) bool { //nolint:gocyclo switch scheme { case schemePyPI: - _, ok := parsePEP440(version) - return ok + return pep440Regex.MatchString(version) case schemeComposer: return validComposerVersion(version) case schemePub: diff --git a/validation_bench_test.go b/validation_bench_test.go new file mode 100644 index 0000000..c00838b --- /dev/null +++ b/validation_bench_test.go @@ -0,0 +1,58 @@ +package vers + +import ( + "strings" + "testing" +) + +func BenchmarkPackageVersionValidation(b *testing.B) { + for _, scheme := range []string{"pypi", "composer"} { + for _, version := range []string{"1.2.3", "1.2.3rc1", "v1.2.3", "dev-main", "1!2.0.post1+local"} { + b.Run(scheme+"/"+version, func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + ValidWithScheme(version, scheme) + } + }) + } + } +} + +func FuzzPackageVersionValidation(f *testing.F) { + for _, s := range []string{"1.2.3", "1.2.3rc1", "1!2.0.post1+local", "v1.2.3", "dev-main", "1.2.x-dev", "1.2.3-unknown", "1.2.3-ſtable", "1.2.3-ALPHA1", "1.2.3-patch1", "1.2.3+foo", "\t1.2.3\n", ""} { + f.Add(s) + } + f.Fuzz(func(t *testing.T, s string) { + v := strings.TrimSpace(s) + _, py := parsePEP440(v) + _, co := parseComposerVersion(v) + co = v != "" && !strings.ContainsAny(v, " \t\r\n") && (co || isComposerBranchVersion(v) || composerNumericBranchRegex.MatchString(v)) + for scheme, want := range map[string]bool{"pypi": py, "composer": co} { + if got := ValidWithScheme(s, scheme); got != want { + t.Fatalf("%s %q: got %v want %v", scheme, s, got, want) + } + } + }) +} + +func TestPackageVersionValidation(t *testing.T) { + for _, tc := range []struct { + scheme, version string + want bool + }{ + {"pypi", "1!2.0rc1.post2.dev3+local.4", true}, + {"pypi", "1.2.3junk", false}, + {"composer", "v1.2.3-ALPHA1", true}, + {"composer", "1.2.3-patch1", true}, + {"composer", "1.2.3-unknown", false}, + {"composer", "1.2.3-ſtable", false}, + {"composer", "dev-main", true}, + {"composer", "1.2.x-dev", true}, + {"composer", "1.2.3+build", true}, + {"composer", "1.2.3-DEV1", true}, + } { + if got := ValidWithScheme(tc.version, tc.scheme); got != tc.want { + t.Errorf("%s %q: got %v want %v", tc.scheme, tc.version, got, tc.want) + } + } +}