// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause package cmpver_test import ( "testing" "tailscale.com/util/cmpver" ) func TestCompare(t *testing.T) { tests := []struct { name string v1, v2 string want int }{ { name: "both empty", want: 0, }, { name: "v1 empty", v2: "1.2.3", want: -1, }, { name: "v2 empty", v1: "1.2.3", want: 1, }, { name: "semver major", v1: "2.0.0", v2: "1.9.9", want: 1, }, { name: "semver major", v1: "2.0.0", v2: "1.9.9", want: 1, }, { name: "semver minor", v1: "1.9.0", v2: "1.8.9", want: 1, }, { name: "semver patch", v1: "1.9.9", v2: "1.9.8", want: 1, }, { name: "semver equal", v1: "1.9.8", v2: "1.9.8", want: 0, }, { name: "tailscale major", v1: "1.0-0", v2: "0.97-105", want: 1, }, { name: "tailscale minor", v1: "0.98-0", v2: "0.97-105", want: 1, }, { name: "tailscale patch", v1: "0.97-120", v2: "0.97-105", want: 1, }, { name: "tailscale equal", v1: "0.97-105", v2: "0.97-105", want: 0, }, { name: "tailscale weird extra field", v1: "0.96.1-0", // more fields == larger v2: "0.96-105", want: 1, }, { // Though ۱ and ۲ both satisfy unicode.IsNumber, our previous use // of strconv.ParseUint with these characters would have lead us to // panic. We're now only looking at ascii numbers, so test these are // compared as text. name: "only ascii numbers", v1: "۱۱", // 2x EXTENDED ARABIC-INDIC DIGIT ONE v2: "۲", // 1x EXTENDED ARABIC-INDIC DIGIT TWO want: -1, }, // A few specific OS version tests below. { name: "windows version", v1: "10.0.19045.3324", v2: "10.0.18362", want: 1, }, { name: "windows 11 is everything above 10.0.22000", v1: "10.0.22631.2262", v2: "10.0.22000", want: 1, }, { name: "android short version", v1: "10", v2: "7", want: 1, }, { name: "android longer version", v1: "7.1.2", v2: "7", want: 1, }, { name: "iOS version", v1: "15.6.1", v2: "15.6", want: 1, }, { name: "Linux short kernel version", v1: "4.4.302+", v2: "4.0", want: 1, }, { name: "Linux long kernel version", v1: "4.14.255-311-248.529.amzn2.x86_64", v2: "4.0", want: 1, }, { name: "FreeBSD version", v1: "14.0-CURRENT", v2: "14", want: 1, }, { name: "Synology version", v1: "Synology 6.2.4; kernel=3.10.105", v2: "Synology 6", want: 1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := cmpver.Compare(test.v1, test.v2) if got != test.want { t.Errorf("Compare(%q, %q) = %v, want %v", test.v1, test.v2, got, test.want) } // Reversing the comparison should reverse the outcome. got2 := cmpver.Compare(test.v2, test.v1) if got2 != -test.want { t.Errorf("Compare(%q, %q) = %v, want %v", test.v2, test.v1, got2, -test.want) } if got, want := cmpver.Less(test.v1, test.v2), test.want < 0; got != want { t.Errorf("Less(%q, %q) = %v, want %v", test.v1, test.v2, got, want) } if got, want := cmpver.Less(test.v2, test.v1), test.want > 0; got != want { t.Errorf("Less(%q, %q) = %v, want %v", test.v2, test.v1, got, want) } if got, want := cmpver.LessEq(test.v1, test.v2), test.want <= 0; got != want { t.Errorf("LessEq(%q, %q) = %v, want %v", test.v1, test.v2, got, want) } if got, want := cmpver.LessEq(test.v2, test.v1), test.want >= 0; got != want { t.Errorf("LessEq(%q, %q) = %v, want %v", test.v2, test.v1, got, want) } // Check that version comparison does not allocate. if n := testing.AllocsPerRun(100, func() { cmpver.Compare(test.v1, test.v2) }); n > 0 { t.Errorf("Compare(%q, %q) got %v allocs per run", test.v1, test.v2, n) } }) } }