zsvo/pkg/deps/deps_test.go
2026-03-13 15:18:31 +03:00

259 lines
6 KiB
Go

package deps
import (
"reflect"
"testing"
)
func TestParseRequirement(t *testing.T) {
t.Parallel()
req, err := ParseRequirement("lua5.1 (>= 5.1.5) | luajit")
if err != nil {
t.Fatalf("ParseRequirement() error = %v", err)
}
if len(req.Alternatives) != 2 {
t.Fatalf("expected 2 alternatives, got %d", len(req.Alternatives))
}
first := req.Alternatives[0]
if first.Name != "lua5.1" || first.Op != OpGreaterOrEqual || first.Version != "5.1.5" {
t.Fatalf("unexpected first alt: %#v", first)
}
second := req.Alternatives[1]
if second.Name != "luajit" || second.Op != OpAny {
t.Fatalf("unexpected second alt: %#v", second)
}
}
func TestParseRequirementsInvalid(t *testing.T) {
t.Parallel()
invalid := []string{
"",
" ",
"bad dep",
"foo (>> 1)",
"foo >=",
"foo |",
"| foo",
}
for _, raw := range invalid {
if _, err := ParseRequirement(raw); err == nil {
t.Fatalf("expected parse error for %q", raw)
}
}
}
func TestCompareVersions(t *testing.T) {
t.Parallel()
cases := []struct {
a, b string
want int
}{
{"1.0", "1.0", 0},
{"1.0", "1.0.1", -1},
{"2.0", "1.9", 1},
{"1.02", "1.2", 0},
{"1.0~rc1", "1.0", -1},
{"1:1.0-1", "1.9.9", 1},
{"2:1.0-1", "1:9.0-9", 1},
{"1.0-2", "1.0-10", -1},
{"1.0+dfsg1", "1.0+dfsg2", -1},
{"1.0a", "1.0", 1},
}
for _, tc := range cases {
got := CompareVersions(tc.a, tc.b)
if sign(got) != sign(tc.want) {
t.Fatalf("CompareVersions(%q, %q): want sign %d, got %d", tc.a, tc.b, sign(tc.want), sign(got))
}
}
}
func TestConstraintMatchesVersion(t *testing.T) {
t.Parallel()
cases := []struct {
rawDep string
version string
want bool
}{
{"foo", "1.0", true},
{"foo=1.0", "1.0", true},
{"foo=1.0", "1.1", false},
{"foo>=1.0", "1.2", true},
{"foo>=1.0", "0.9", false},
{"foo<2.0", "1.9", true},
{"foo<2.0", "2.0", false},
{"foo (<= 2.1)", "2.1", true},
}
for _, tc := range cases {
req, err := ParseRequirement(tc.rawDep)
if err != nil {
t.Fatalf("ParseRequirement(%q) error = %v", tc.rawDep, err)
}
got := req.Alternatives[0].MatchesVersion(tc.version)
if got != tc.want {
t.Fatalf("MatchesVersion(%q, %q): want %v, got %v", tc.rawDep, tc.version, tc.want, got)
}
}
}
func TestParseRequirements(t *testing.T) {
t.Parallel()
input := []string{"foo", "bar>=1.2", "baz | qux"}
got, err := ParseRequirements(input)
if err != nil {
t.Fatalf("ParseRequirements() error = %v", err)
}
if len(got) != 3 {
t.Fatalf("expected 3 requirements, got %d", len(got))
}
names := []string{got[0].Alternatives[0].Name, got[1].Alternatives[0].Name, got[2].Alternatives[0].Name}
if want := []string{"foo", "bar", "baz"}; !reflect.DeepEqual(names, want) {
t.Fatalf("unexpected parsed names:\nwant: %#v\ngot: %#v", want, names)
}
}
// Additional comprehensive tests for version comparison
func TestCompareVersionsEdgeCases(t *testing.T) {
t.Parallel()
cases := []struct {
a, b string
want int
desc string
}{
// Debian epoch tests
{"1:1.0", "1.0", 1, "epoch vs no epoch"},
{"2:1.0", "1:2.0", 1, "different epochs"},
{"0:1.0", "1.0", 0, "zero epoch explicit vs implicit"},
// Tilde precedence tests
{"1.0~rc1", "1.0~rc2", -1, "tilde rc versions"},
{"1.0~rc1", "1.0", -1, "tilde vs final"},
{"1.0~alpha", "1.0~dev", -1, "tilde alpha vs dev"},
// Complex version parts
{"1.2.3-4", "1.2.3-4ubuntu1", -1, "ubuntu suffix"},
{"1.2.3+dfsg1", "1.2.3+dfsg2", -1, "dfsg suffix"},
{"1.2.3-1+b1", "1.2.3-1", 1, "binNMU suffix"},
// Numeric vs alphanumeric
{"1.0a", "1.0", 1, "alpha suffix"},
{"1.0beta", "1.0", 1, "beta suffix"},
{"1.0rc1", "1.0", 1, "rc suffix"},
// Leading zeros
{"1.02", "1.002", 0, "leading zeros"},
{"1.010", "1.2", 1, "leading zeros comparison"},
// Empty parts
{"1.0-", "1.0", 0, "empty release"},
{"1.0-0", "1.0", 0, "zero release"},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got := CompareVersions(tc.a, tc.b)
if sign(got) != sign(tc.want) {
t.Errorf("CompareVersions(%q, %q): want sign %d, got %d", tc.a, tc.b, sign(tc.want), sign(got))
}
})
}
}
func TestParseRequirementComplex(t *testing.T) {
t.Parallel()
cases := []struct {
raw string
expect Requirement
desc string
}{
{
"pkg (>= 1.2.3) | otherpkg (= 2.0)",
Requirement{
Raw: "pkg (>= 1.2.3) | otherpkg (= 2.0)",
Alternatives: []Constraint{
{Name: "pkg", Op: OpGreaterOrEqual, Version: "1.2.3"},
{Name: "otherpkg", Op: OpEqual, Version: "2.0"},
},
},
"debian style with alternatives",
},
{
"complex-name+suffix_1.0",
Requirement{
Raw: "complex-name+suffix_1.0",
Alternatives: []Constraint{
{Name: "complex-name+suffix_1.0", Op: OpAny, Version: ""},
},
},
"complex package name",
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
got, err := ParseRequirement(tc.raw)
if err != nil {
t.Fatalf("ParseRequirement(%q) error = %v", tc.raw, err)
}
if !reflect.DeepEqual(got, tc.expect) {
t.Errorf("ParseRequirement(%q):\nwant: %#v\ngot: %#v", tc.raw, tc.expect, got)
}
})
}
}
// Property-based test for version comparison transitivity
func TestCompareVersionsTransitivity(t *testing.T) {
t.Parallel()
versions := []string{
"1.0", "1.0~rc1", "1.0~dev", "1.1", "2.0",
"1:1.0", "1:0.9", "1.0-1", "1.0-2",
}
for i, a := range versions {
for j, b := range versions {
for k, c := range versions {
if i == j || j == k || i == k {
continue
}
ab := CompareVersions(a, b)
bc := CompareVersions(b, c)
ac := CompareVersions(a, c)
// If a > b and b > c, then a > c
if ab > 0 && bc > 0 && ac <= 0 {
t.Errorf("Transitivity violation: %s > %s and %s > %s but %s <= %s", a, b, b, c, a, c)
}
// If a == b and b == c, then a == c
if ab == 0 && bc == 0 && ac != 0 {
t.Errorf("Transitivity violation: %s == %s and %s == %s but %s != %s", a, b, b, c, a, c)
}
}
}
}
}
func sign(v int) int {
switch {
case v < 0:
return -1
case v > 0:
return 1
default:
return 0
}
}