1package semver
2
3import (
4	"reflect"
5	"strings"
6	"testing"
7)
8
9type wildcardTypeTest struct {
10	input        string
11	wildcardType wildcardType
12}
13
14type comparatorTest struct {
15	input      string
16	comparator func(comparator) bool
17}
18
19func TestParseComparator(t *testing.T) {
20	compatorTests := []comparatorTest{
21		{">", testGT},
22		{">=", testGE},
23		{"<", testLT},
24		{"<=", testLE},
25		{"", testEQ},
26		{"=", testEQ},
27		{"==", testEQ},
28		{"!=", testNE},
29		{"!", testNE},
30		{"-", nil},
31		{"<==", nil},
32		{"<<", nil},
33		{">>", nil},
34	}
35
36	for _, tc := range compatorTests {
37		if c := parseComparator(tc.input); c == nil {
38			if tc.comparator != nil {
39				t.Errorf("Comparator nil for case %q\n", tc.input)
40			}
41		} else if !tc.comparator(c) {
42			t.Errorf("Invalid comparator for case %q\n", tc.input)
43		}
44	}
45}
46
47var (
48	v1 = MustParse("1.2.2")
49	v2 = MustParse("1.2.3")
50	v3 = MustParse("1.2.4")
51)
52
53func testEQ(f comparator) bool {
54	return f(v1, v1) && !f(v1, v2)
55}
56
57func testNE(f comparator) bool {
58	return !f(v1, v1) && f(v1, v2)
59}
60
61func testGT(f comparator) bool {
62	return f(v2, v1) && f(v3, v2) && !f(v1, v2) && !f(v1, v1)
63}
64
65func testGE(f comparator) bool {
66	return f(v2, v1) && f(v3, v2) && !f(v1, v2)
67}
68
69func testLT(f comparator) bool {
70	return f(v1, v2) && f(v2, v3) && !f(v2, v1) && !f(v1, v1)
71}
72
73func testLE(f comparator) bool {
74	return f(v1, v2) && f(v2, v3) && !f(v2, v1)
75}
76
77func TestSplitAndTrim(t *testing.T) {
78	tests := []struct {
79		i string
80		s []string
81	}{
82		{"1.2.3 1.2.3", []string{"1.2.3", "1.2.3"}},
83		{"     1.2.3     1.2.3     ", []string{"1.2.3", "1.2.3"}},       // Spaces
84		{"  >=   1.2.3   <=  1.2.3   ", []string{">=1.2.3", "<=1.2.3"}}, // Spaces between operator and version
85		{"1.2.3 || >=1.2.3 <1.2.3", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
86		{"      1.2.3      ||     >=1.2.3     <1.2.3    ", []string{"1.2.3", "||", ">=1.2.3", "<1.2.3"}},
87	}
88
89	for _, tc := range tests {
90		p := splitAndTrim(tc.i)
91		if !reflect.DeepEqual(p, tc.s) {
92			t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
93		}
94	}
95}
96
97func TestSplitComparatorVersion(t *testing.T) {
98	tests := []struct {
99		i string
100		p []string
101	}{
102		{">1.2.3", []string{">", "1.2.3"}},
103		{">=1.2.3", []string{">=", "1.2.3"}},
104		{"<1.2.3", []string{"<", "1.2.3"}},
105		{"<=1.2.3", []string{"<=", "1.2.3"}},
106		{"1.2.3", []string{"", "1.2.3"}},
107		{"=1.2.3", []string{"=", "1.2.3"}},
108		{"==1.2.3", []string{"==", "1.2.3"}},
109		{"!=1.2.3", []string{"!=", "1.2.3"}},
110		{"!1.2.3", []string{"!", "1.2.3"}},
111		{"error", nil},
112	}
113	for _, tc := range tests {
114		if op, v, err := splitComparatorVersion(tc.i); err != nil {
115			if tc.p != nil {
116				t.Errorf("Invalid for case %q: Expected %q, got error %q", tc.i, tc.p, err)
117			}
118		} else if op != tc.p[0] {
119			t.Errorf("Invalid operator for case %q: Expected %q, got: %q", tc.i, tc.p[0], op)
120		} else if v != tc.p[1] {
121			t.Errorf("Invalid version for case %q: Expected %q, got: %q", tc.i, tc.p[1], v)
122		}
123
124	}
125}
126
127func TestBuildVersionRange(t *testing.T) {
128	tests := []struct {
129		opStr string
130		vStr  string
131		c     func(comparator) bool
132		v     string
133	}{
134		{">", "1.2.3", testGT, "1.2.3"},
135		{">=", "1.2.3", testGE, "1.2.3"},
136		{"<", "1.2.3", testLT, "1.2.3"},
137		{"<=", "1.2.3", testLE, "1.2.3"},
138		{"", "1.2.3", testEQ, "1.2.3"},
139		{"=", "1.2.3", testEQ, "1.2.3"},
140		{"==", "1.2.3", testEQ, "1.2.3"},
141		{"!=", "1.2.3", testNE, "1.2.3"},
142		{"!", "1.2.3", testNE, "1.2.3"},
143		{">>", "1.2.3", nil, ""},  // Invalid comparator
144		{"=", "invalid", nil, ""}, // Invalid version
145	}
146
147	for _, tc := range tests {
148		if r, err := buildVersionRange(tc.opStr, tc.vStr); err != nil {
149			if tc.c != nil {
150				t.Errorf("Invalid for case %q: Expected %q, got error %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tc.v, err)
151			}
152		} else if r == nil {
153			t.Errorf("Invalid for case %q: got nil", strings.Join([]string{tc.opStr, tc.vStr}, ""))
154		} else {
155			// test version
156			if tv := MustParse(tc.v); !r.v.EQ(tv) {
157				t.Errorf("Invalid for case %q: Expected version %q, got: %q", strings.Join([]string{tc.opStr, tc.vStr}, ""), tv, r.v)
158			}
159			// test comparator
160			if r.c == nil {
161				t.Errorf("Invalid for case %q: got nil comparator", strings.Join([]string{tc.opStr, tc.vStr}, ""))
162				continue
163			}
164			if !tc.c(r.c) {
165				t.Errorf("Invalid comparator for case %q\n", strings.Join([]string{tc.opStr, tc.vStr}, ""))
166			}
167		}
168	}
169
170}
171
172func TestSplitORParts(t *testing.T) {
173	tests := []struct {
174		i []string
175		o [][]string
176	}{
177		{[]string{">1.2.3", "||", "<1.2.3", "||", "=1.2.3"}, [][]string{
178			{">1.2.3"},
179			{"<1.2.3"},
180			{"=1.2.3"},
181		}},
182		{[]string{">1.2.3", "<1.2.3", "||", "=1.2.3"}, [][]string{
183			{">1.2.3", "<1.2.3"},
184			{"=1.2.3"},
185		}},
186		{[]string{">1.2.3", "||"}, nil},
187		{[]string{"||", ">1.2.3"}, nil},
188	}
189	for _, tc := range tests {
190		o, err := splitORParts(tc.i)
191		if err != nil && tc.o != nil {
192			t.Errorf("Unexpected error for case %q: %s", tc.i, err)
193		}
194		if !reflect.DeepEqual(tc.o, o) {
195			t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
196		}
197	}
198}
199
200func TestGetWildcardType(t *testing.T) {
201	wildcardTypeTests := []wildcardTypeTest{
202		{"x", majorWildcard},
203		{"1.x", minorWildcard},
204		{"1.2.x", patchWildcard},
205		{"fo.o.b.ar", noneWildcard},
206	}
207
208	for _, tc := range wildcardTypeTests {
209		o := getWildcardType(tc.input)
210		if o != tc.wildcardType {
211			t.Errorf("Invalid for case: %q: Expected %q, got: %q", tc.input, tc.wildcardType, o)
212		}
213	}
214}
215
216func TestCreateVersionFromWildcard(t *testing.T) {
217	tests := []struct {
218		i string
219		s string
220	}{
221		{"1.2.x", "1.2.0"},
222		{"1.x", "1.0.0"},
223	}
224
225	for _, tc := range tests {
226		p := createVersionFromWildcard(tc.i)
227		if p != tc.s {
228			t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
229		}
230	}
231}
232
233func TestIncrementMajorVersion(t *testing.T) {
234	tests := []struct {
235		i string
236		s string
237	}{
238		{"1.2.3", "2.2.3"},
239		{"1.2", "2.2"},
240		{"foo.bar", ""},
241	}
242
243	for _, tc := range tests {
244		p, _ := incrementMajorVersion(tc.i)
245		if p != tc.s {
246			t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
247		}
248	}
249}
250
251func TestIncrementMinorVersion(t *testing.T) {
252	tests := []struct {
253		i string
254		s string
255	}{
256		{"1.2.3", "1.3.3"},
257		{"1.2", "1.3"},
258		{"foo.bar", ""},
259	}
260
261	for _, tc := range tests {
262		p, _ := incrementMinorVersion(tc.i)
263		if p != tc.s {
264			t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.s, p)
265		}
266	}
267}
268
269func TestExpandWildcardVersion(t *testing.T) {
270	tests := []struct {
271		i [][]string
272		o [][]string
273	}{
274		{[][]string{{"foox"}}, nil},
275		{[][]string{{">=1.2.x"}}, [][]string{{">=1.2.0"}}},
276		{[][]string{{"<=1.2.x"}}, [][]string{{"<1.3.0"}}},
277		{[][]string{{">1.2.x"}}, [][]string{{">=1.3.0"}}},
278		{[][]string{{"<1.2.x"}}, [][]string{{"<1.2.0"}}},
279		{[][]string{{"!=1.2.x"}}, [][]string{{"<1.2.0", ">=1.3.0"}}},
280		{[][]string{{">=1.x"}}, [][]string{{">=1.0.0"}}},
281		{[][]string{{"<=1.x"}}, [][]string{{"<2.0.0"}}},
282		{[][]string{{">1.x"}}, [][]string{{">=2.0.0"}}},
283		{[][]string{{"<1.x"}}, [][]string{{"<1.0.0"}}},
284		{[][]string{{"!=1.x"}}, [][]string{{"<1.0.0", ">=2.0.0"}}},
285		{[][]string{{"1.2.x"}}, [][]string{{">=1.2.0", "<1.3.0"}}},
286		{[][]string{{"1.x"}}, [][]string{{">=1.0.0", "<2.0.0"}}},
287	}
288
289	for _, tc := range tests {
290		o, _ := expandWildcardVersion(tc.i)
291		if !reflect.DeepEqual(tc.o, o) {
292			t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
293		}
294	}
295}
296
297func TestVersionRangeToRange(t *testing.T) {
298	vr := versionRange{
299		v: MustParse("1.2.3"),
300		c: compLT,
301	}
302	rf := vr.rangeFunc()
303	if !rf(MustParse("1.2.2")) || rf(MustParse("1.2.3")) {
304		t.Errorf("Invalid conversion to range func")
305	}
306}
307
308func TestRangeAND(t *testing.T) {
309	v := MustParse("1.2.2")
310	v1 := MustParse("1.2.1")
311	v2 := MustParse("1.2.3")
312	rf1 := Range(func(v Version) bool {
313		return v.GT(v1)
314	})
315	rf2 := Range(func(v Version) bool {
316		return v.LT(v2)
317	})
318	rf := rf1.AND(rf2)
319	if rf(v1) {
320		t.Errorf("Invalid rangefunc, accepted: %s", v1)
321	}
322	if rf(v2) {
323		t.Errorf("Invalid rangefunc, accepted: %s", v2)
324	}
325	if !rf(v) {
326		t.Errorf("Invalid rangefunc, did not accept: %s", v)
327	}
328}
329
330func TestRangeOR(t *testing.T) {
331	tests := []struct {
332		v Version
333		b bool
334	}{
335		{MustParse("1.2.0"), true},
336		{MustParse("1.2.2"), false},
337		{MustParse("1.2.4"), true},
338	}
339	v1 := MustParse("1.2.1")
340	v2 := MustParse("1.2.3")
341	rf1 := Range(func(v Version) bool {
342		return v.LT(v1)
343	})
344	rf2 := Range(func(v Version) bool {
345		return v.GT(v2)
346	})
347	rf := rf1.OR(rf2)
348	for _, tc := range tests {
349		if r := rf(tc.v); r != tc.b {
350			t.Errorf("Invalid for case %q: Expected %t, got %t", tc.v, tc.b, r)
351		}
352	}
353}
354
355func TestParseRange(t *testing.T) {
356	type tv struct {
357		v string
358		b bool
359	}
360	tests := []struct {
361		i string
362		t []tv
363	}{
364		// Simple expressions
365		{">1.2.3", []tv{
366			{"1.2.2", false},
367			{"1.2.3", false},
368			{"1.2.4", true},
369		}},
370		{">=1.2.3", []tv{
371			{"1.2.3", true},
372			{"1.2.4", true},
373			{"1.2.2", false},
374		}},
375		{"<1.2.3", []tv{
376			{"1.2.2", true},
377			{"1.2.3", false},
378			{"1.2.4", false},
379		}},
380		{"<=1.2.3", []tv{
381			{"1.2.2", true},
382			{"1.2.3", true},
383			{"1.2.4", false},
384		}},
385		{"1.2.3", []tv{
386			{"1.2.2", false},
387			{"1.2.3", true},
388			{"1.2.4", false},
389		}},
390		{"=1.2.3", []tv{
391			{"1.2.2", false},
392			{"1.2.3", true},
393			{"1.2.4", false},
394		}},
395		{"==1.2.3", []tv{
396			{"1.2.2", false},
397			{"1.2.3", true},
398			{"1.2.4", false},
399		}},
400		{"!=1.2.3", []tv{
401			{"1.2.2", true},
402			{"1.2.3", false},
403			{"1.2.4", true},
404		}},
405		{"!1.2.3", []tv{
406			{"1.2.2", true},
407			{"1.2.3", false},
408			{"1.2.4", true},
409		}},
410		// Simple Expression errors
411		{">>1.2.3", nil},
412		{"!1.2.3", nil},
413		{"1.0", nil},
414		{"string", nil},
415		{"", nil},
416		{"fo.ob.ar.x", nil},
417		// AND Expressions
418		{">1.2.2 <1.2.4", []tv{
419			{"1.2.2", false},
420			{"1.2.3", true},
421			{"1.2.4", false},
422		}},
423		{"<1.2.2 <1.2.4", []tv{
424			{"1.2.1", true},
425			{"1.2.2", false},
426			{"1.2.3", false},
427			{"1.2.4", false},
428		}},
429		{">1.2.2 <1.2.5 !=1.2.4", []tv{
430			{"1.2.2", false},
431			{"1.2.3", true},
432			{"1.2.4", false},
433			{"1.2.5", false},
434		}},
435		{">1.2.2 <1.2.5 !1.2.4", []tv{
436			{"1.2.2", false},
437			{"1.2.3", true},
438			{"1.2.4", false},
439			{"1.2.5", false},
440		}},
441		// OR Expressions
442		{">1.2.2 || <1.2.4", []tv{
443			{"1.2.2", true},
444			{"1.2.3", true},
445			{"1.2.4", true},
446		}},
447		{"<1.2.2 || >1.2.4", []tv{
448			{"1.2.2", false},
449			{"1.2.3", false},
450			{"1.2.4", false},
451		}},
452		// Wildcard expressions
453		{">1.x", []tv{
454			{"0.1.9", false},
455			{"1.2.6", false},
456			{"1.9.0", false},
457			{"2.0.0", true},
458		}},
459		{">1.2.x", []tv{
460			{"1.1.9", false},
461			{"1.2.6", false},
462			{"1.3.0", true},
463		}},
464		// Combined Expressions
465		{">1.2.2 <1.2.4 || >=2.0.0", []tv{
466			{"1.2.2", false},
467			{"1.2.3", true},
468			{"1.2.4", false},
469			{"2.0.0", true},
470			{"2.0.1", true},
471		}},
472		{"1.x || >=2.0.x <2.2.x", []tv{
473			{"0.9.2", false},
474			{"1.2.2", true},
475			{"2.0.0", true},
476			{"2.1.8", true},
477			{"2.2.0", false},
478		}},
479		{">1.2.2 <1.2.4 || >=2.0.0 <3.0.0", []tv{
480			{"1.2.2", false},
481			{"1.2.3", true},
482			{"1.2.4", false},
483			{"2.0.0", true},
484			{"2.0.1", true},
485			{"2.9.9", true},
486			{"3.0.0", false},
487		}},
488	}
489
490	for _, tc := range tests {
491		r, err := ParseRange(tc.i)
492		if err != nil && tc.t != nil {
493			t.Errorf("Error parsing range %q: %s", tc.i, err)
494			continue
495		}
496		for _, tvc := range tc.t {
497			v := MustParse(tvc.v)
498			if res := r(v); res != tvc.b {
499				t.Errorf("Invalid for case %q matching %q: Expected %t, got: %t", tc.i, tvc.v, tvc.b, res)
500			}
501		}
502
503	}
504}
505
506func TestMustParseRange(t *testing.T) {
507	testCase := ">1.2.2 <1.2.4 || >=2.0.0 <3.0.0"
508	r := MustParseRange(testCase)
509	if !r(MustParse("1.2.3")) {
510		t.Errorf("Unexpected range behavior on MustParseRange")
511	}
512}
513
514func TestMustParseRange_panic(t *testing.T) {
515	defer func() {
516		if recover() == nil {
517			t.Errorf("Should have panicked")
518		}
519	}()
520	_ = MustParseRange("invalid version")
521}
522
523func BenchmarkRangeParseSimple(b *testing.B) {
524	const VERSION = ">1.0.0"
525	b.ReportAllocs()
526	b.ResetTimer()
527	for n := 0; n < b.N; n++ {
528		_, _ = ParseRange(VERSION)
529	}
530}
531
532func BenchmarkRangeParseAverage(b *testing.B) {
533	const VERSION = ">=1.0.0 <2.0.0"
534	b.ReportAllocs()
535	b.ResetTimer()
536	for n := 0; n < b.N; n++ {
537		_, _ = ParseRange(VERSION)
538	}
539}
540
541func BenchmarkRangeParseComplex(b *testing.B) {
542	const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
543	b.ReportAllocs()
544	b.ResetTimer()
545	for n := 0; n < b.N; n++ {
546		_, _ = ParseRange(VERSION)
547	}
548}
549
550func BenchmarkRangeMatchSimple(b *testing.B) {
551	const VERSION = ">1.0.0"
552	r, _ := ParseRange(VERSION)
553	v := MustParse("2.0.0")
554	b.ReportAllocs()
555	b.ResetTimer()
556	for n := 0; n < b.N; n++ {
557		r(v)
558	}
559}
560
561func BenchmarkRangeMatchAverage(b *testing.B) {
562	const VERSION = ">=1.0.0 <2.0.0"
563	r, _ := ParseRange(VERSION)
564	v := MustParse("1.2.3")
565	b.ReportAllocs()
566	b.ResetTimer()
567	for n := 0; n < b.N; n++ {
568		r(v)
569	}
570}
571
572func BenchmarkRangeMatchComplex(b *testing.B) {
573	const VERSION = ">=1.0.0 <2.0.0 || >=3.0.1 <4.0.0 !=3.0.3 || >=5.0.0"
574	r, _ := ParseRange(VERSION)
575	v := MustParse("5.0.1")
576	b.ReportAllocs()
577	b.ResetTimer()
578	for n := 0; n < b.N; n++ {
579		r(v)
580	}
581}
582