1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package modfile
6
7import (
8	"bytes"
9	"testing"
10
11	"golang.org/x/mod/module"
12)
13
14var addRequireTests = []struct {
15	desc string
16	in   string
17	path string
18	vers string
19	out  string
20}{
21	{
22		`existing`,
23		`
24		module m
25		require x.y/z v1.2.3
26		`,
27		"x.y/z", "v1.5.6",
28		`
29		module m
30		require x.y/z v1.5.6
31		`,
32	},
33	{
34		`new`,
35		`
36		module m
37		require x.y/z v1.2.3
38		`,
39		"x.y/w", "v1.5.6",
40		`
41		module m
42		require (
43			x.y/z v1.2.3
44			x.y/w v1.5.6
45		)
46		`,
47	},
48	{
49		`new2`,
50		`
51		module m
52		require x.y/z v1.2.3
53		require x.y/q/v2 v2.3.4
54		`,
55		"x.y/w", "v1.5.6",
56		`
57		module m
58		require x.y/z v1.2.3
59		require (
60			x.y/q/v2 v2.3.4
61			x.y/w v1.5.6
62		)
63		`,
64	},
65}
66
67var setRequireTests = []struct {
68	desc string
69	in   string
70	mods []struct {
71		path     string
72		vers     string
73		indirect bool
74	}
75	out string
76}{
77	{
78		`existing`,
79		`module m
80		require (
81			x.y/b v1.2.3
82
83			x.y/a v1.2.3
84			x.y/d v1.2.3
85		)
86		`,
87		[]struct {
88			path     string
89			vers     string
90			indirect bool
91		}{
92			{"x.y/a", "v1.2.3", false},
93			{"x.y/b", "v1.2.3", false},
94			{"x.y/c", "v1.2.3", false},
95		},
96		`module m
97		require (
98			x.y/a v1.2.3
99			x.y/b v1.2.3
100			x.y/c v1.2.3
101		)
102		`,
103	},
104	{
105		`existing_indirect`,
106		`module m
107		require (
108			x.y/a v1.2.3
109			x.y/b v1.2.3 //
110			x.y/c v1.2.3 //c
111			x.y/d v1.2.3 //   c
112			x.y/e v1.2.3 // indirect
113			x.y/f v1.2.3 //indirect
114			x.y/g v1.2.3 //	indirect
115		)
116		`,
117		[]struct {
118			path     string
119			vers     string
120			indirect bool
121		}{
122			{"x.y/a", "v1.2.3", true},
123			{"x.y/b", "v1.2.3", true},
124			{"x.y/c", "v1.2.3", true},
125			{"x.y/d", "v1.2.3", true},
126			{"x.y/e", "v1.2.3", true},
127			{"x.y/f", "v1.2.3", true},
128			{"x.y/g", "v1.2.3", true},
129		},
130		`module m
131		require (
132			x.y/a v1.2.3 // indirect
133			x.y/b v1.2.3 // indirect
134			x.y/c v1.2.3 // indirect; c
135			x.y/d v1.2.3 // indirect; c
136			x.y/e v1.2.3 // indirect
137			x.y/f v1.2.3 //indirect
138			x.y/g v1.2.3 //	indirect
139		)
140		`,
141	},
142}
143
144var addGoTests = []struct {
145	desc    string
146	in      string
147	version string
148	out     string
149}{
150	{
151		`module_only`,
152		`module m
153		`,
154		`1.14`,
155		`module m
156		go 1.14
157		`,
158	},
159	{
160		`module_before_require`,
161		`module m
162		require x.y/a v1.2.3
163		`,
164		`1.14`,
165		`module m
166		go 1.14
167		require x.y/a v1.2.3
168		`,
169	},
170	{
171		`require_before_module`,
172		`require x.y/a v1.2.3
173		module example.com/inverted
174		`,
175		`1.14`,
176		`require x.y/a v1.2.3
177		module example.com/inverted
178		go 1.14
179		`,
180	},
181	{
182		`require_only`,
183		`require x.y/a v1.2.3
184		`,
185		`1.14`,
186		`require x.y/a v1.2.3
187		go 1.14
188		`,
189	},
190}
191
192var addRetractTests = []struct {
193	desc      string
194	in        string
195	low       string
196	high      string
197	rationale string
198	out       string
199}{
200	{
201		`new_singleton`,
202		`module m
203		`,
204		`v1.2.3`,
205		`v1.2.3`,
206		``,
207		`module m
208		retract v1.2.3
209		`,
210	},
211	{
212		`new_interval`,
213		`module m
214		`,
215		`v1.0.0`,
216		`v1.1.0`,
217		``,
218		`module m
219		retract [v1.0.0, v1.1.0]`,
220	},
221	{
222		`duplicate_with_rationale`,
223		`module m
224		retract v1.2.3
225		`,
226		`v1.2.3`,
227		`v1.2.3`,
228		`bad`,
229		`module m
230		retract (
231			v1.2.3
232			// bad
233			v1.2.3
234		)
235		`,
236	},
237	{
238		`duplicate_multiline_rationale`,
239		`module m
240		retract [v1.2.3, v1.2.3]
241		`,
242		`v1.2.3`,
243		`v1.2.3`,
244		`multi
245line`,
246		`module m
247		retract	(
248			[v1.2.3, v1.2.3]
249			// multi
250			// line
251			v1.2.3
252		)
253		`,
254	},
255	{
256		`duplicate_interval`,
257		`module m
258		retract [v1.0.0, v1.1.0]
259		`,
260		`v1.0.0`,
261		`v1.1.0`,
262		``,
263		`module m
264		retract (
265			[v1.0.0, v1.1.0]
266			[v1.0.0, v1.1.0]
267		)
268		`,
269	},
270	{
271		`duplicate_singleton`,
272		`module m
273		retract v1.2.3
274		`,
275		`v1.2.3`,
276		`v1.2.3`,
277		``,
278		`module m
279		retract	(
280			v1.2.3
281			v1.2.3
282		)
283		`,
284	},
285}
286
287var dropRetractTests = []struct {
288	desc string
289	in   string
290	low  string
291	high string
292	out  string
293}{
294	{
295		`singleton_no_match`,
296		`module m
297		retract v1.2.3
298		`,
299		`v1.0.0`,
300		`v1.0.0`,
301		`module m
302		retract v1.2.3
303		`,
304	},
305	{
306		`singleton_match_one`,
307		`module m
308		retract v1.2.2
309		retract v1.2.3
310		retract v1.2.4
311		`,
312		`v1.2.3`,
313		`v1.2.3`,
314		`module m
315		retract v1.2.2
316		retract v1.2.4
317		`,
318	},
319	{
320		`singleton_match_all`,
321		`module m
322		retract v1.2.3 // first
323		retract v1.2.3 // second
324		`,
325		`v1.2.3`,
326		`v1.2.3`,
327		`module m
328		`,
329	},
330	{
331		`interval_match`,
332		`module m
333		retract [v1.2.3, v1.2.3]
334		`,
335		`v1.2.3`,
336		`v1.2.3`,
337		`module m
338		`,
339	},
340	{
341		`interval_superset_no_match`,
342		`module m
343		retract [v1.0.0, v1.1.0]
344		`,
345		`v1.0.0`,
346		`v1.2.0`,
347		`module m
348		retract [v1.0.0, v1.1.0]
349		`,
350	},
351	{
352		`singleton_match_middle`,
353		`module m
354		retract v1.2.3
355		`,
356		`v1.2.3`,
357		`v1.2.3`,
358		`module m
359		`,
360	},
361	{
362		`interval_match_middle_block`,
363		`module m
364		retract (
365			v1.0.0
366			[v1.1.0, v1.2.0]
367			v1.3.0
368		)
369		`,
370		`v1.1.0`,
371		`v1.2.0`,
372		`module m
373		retract (
374			v1.0.0
375			v1.3.0
376		)
377		`,
378	},
379	{
380		`interval_match_all`,
381		`module m
382		retract [v1.0.0, v1.1.0]
383		retract [v1.0.0, v1.1.0]
384		`,
385		`v1.0.0`,
386		`v1.1.0`,
387		`module m
388		`,
389	},
390}
391
392var retractRationaleTests = []struct {
393	desc, in, want string
394}{
395	{
396		`no_comment`,
397		`module m
398		retract v1.0.0`,
399		``,
400	},
401	{
402		`prefix_one`,
403		`module m
404		//   prefix
405		retract v1.0.0
406		`,
407		`prefix`,
408	},
409	{
410		`prefix_multiline`,
411		`module m
412		//  one
413		//
414		//     two
415		//
416		// three
417		retract v1.0.0`,
418		`one
419
420two
421
422three`,
423	},
424	{
425		`suffix`,
426		`module m
427		retract v1.0.0 // suffix
428		`,
429		`suffix`,
430	},
431	{
432		`prefix_suffix_after`,
433		`module m
434		// prefix
435		retract v1.0.0 // suffix
436		`,
437		`prefix
438suffix`,
439	},
440	{
441		`block_only`,
442		`// block
443		retract (
444			v1.0.0
445		)
446		`,
447		`block`,
448	},
449	{
450		`block_and_line`,
451		`// block
452		retract (
453			// line
454			v1.0.0
455		)
456		`,
457		`line`,
458	},
459}
460
461var sortBlocksTests = []struct {
462	desc, in, out string
463	strict        bool
464}{
465	{
466		`exclude_duplicates_removed`,
467		`module m
468		exclude x.y/z v1.0.0 // a
469		exclude x.y/z v1.0.0 // b
470		exclude (
471			x.y/w v1.1.0
472			x.y/z v1.0.0 // c
473		)
474		`,
475		`module m
476		exclude x.y/z v1.0.0 // a
477		exclude (
478			x.y/w v1.1.0
479		)`,
480		true,
481	},
482	{
483		`replace_duplicates_removed`,
484		`module m
485		replace x.y/z v1.0.0 => ./a
486		replace x.y/z v1.1.0 => ./b
487		replace (
488			x.y/z v1.0.0 => ./c
489		)
490		`,
491		`module m
492		replace x.y/z v1.1.0 => ./b
493		replace (
494			x.y/z v1.0.0 => ./c
495		)
496		`,
497		true,
498	},
499	{
500		`retract_duplicates_not_removed`,
501		`module m
502		// block
503		retract (
504			v1.0.0 // one
505			v1.0.0 // two
506		)`,
507		`module m
508		// block
509		retract (
510			v1.0.0 // one
511			v1.0.0 // two
512		)`,
513		true,
514	},
515	// Tests below this point just check sort order.
516	// Non-retract blocks are sorted lexicographically in ascending order.
517	// retract blocks are sorted using semver in descending order.
518	{
519		`sort_lexicographically`,
520		`module m
521		sort (
522			aa
523			cc
524			bb
525			zz
526			v1.2.0
527			v1.11.0
528		)`,
529		`module m
530		sort (
531			aa
532			bb
533			cc
534			v1.11.0
535			v1.2.0
536			zz
537		)
538		`,
539		false,
540	},
541	{
542		`sort_retract`,
543		`module m
544		retract (
545			[v1.2.0, v1.3.0]
546			[v1.1.0, v1.3.0]
547			[v1.1.0, v1.2.0]
548			v1.0.0
549			v1.1.0
550			v1.2.0
551			v1.3.0
552			v1.4.0
553		)
554		`,
555		`module m
556		retract (
557			v1.4.0
558			v1.3.0
559			[v1.2.0, v1.3.0]
560			v1.2.0
561			[v1.1.0, v1.3.0]
562			[v1.1.0, v1.2.0]
563			v1.1.0
564			v1.0.0
565		)
566		`,
567		false,
568	},
569}
570
571var addRetractValidateVersionTests = []struct {
572	dsc, low, high string
573}{
574	{
575		"blank_version",
576		"",
577		"",
578	},
579	{
580		"missing_prefix",
581		"1.0.0",
582		"1.0.0",
583	},
584	{
585		"non_canonical",
586		"v1.2",
587		"v1.2",
588	},
589	{
590		"invalid_range",
591		"v1.2.3",
592		"v1.3",
593	},
594}
595
596var addExcludeValidateVersionTests = []struct {
597	dsc, ver string
598}{
599	{
600		"blank_version",
601		"",
602	},
603	{
604		"missing_prefix",
605		"1.0.0",
606	},
607	{
608		"non_canonical",
609		"v1.2",
610	},
611}
612
613func TestAddRequire(t *testing.T) {
614	for _, tt := range addRequireTests {
615		t.Run(tt.desc, func(t *testing.T) {
616			testEdit(t, tt.in, tt.out, true, func(f *File) error {
617				return f.AddRequire(tt.path, tt.vers)
618			})
619		})
620	}
621}
622
623func TestSetRequire(t *testing.T) {
624	for _, tt := range setRequireTests {
625		t.Run(tt.desc, func(t *testing.T) {
626			var mods []*Require
627			for _, mod := range tt.mods {
628				mods = append(mods, &Require{
629					Mod: module.Version{
630						Path:    mod.path,
631						Version: mod.vers,
632					},
633					Indirect: mod.indirect,
634				})
635			}
636
637			f := testEdit(t, tt.in, tt.out, true, func(f *File) error {
638				f.SetRequire(mods)
639				return nil
640			})
641
642			f.Cleanup()
643			if len(f.Require) != len(mods) {
644				t.Errorf("after Cleanup, len(Require) = %v; want %v", len(f.Require), len(mods))
645			}
646		})
647	}
648}
649
650func TestAddGo(t *testing.T) {
651	for _, tt := range addGoTests {
652		t.Run(tt.desc, func(t *testing.T) {
653			testEdit(t, tt.in, tt.out, true, func(f *File) error {
654				return f.AddGoStmt(tt.version)
655			})
656		})
657	}
658}
659
660func TestAddRetract(t *testing.T) {
661	for _, tt := range addRetractTests {
662		t.Run(tt.desc, func(t *testing.T) {
663			testEdit(t, tt.in, tt.out, true, func(f *File) error {
664				return f.AddRetract(VersionInterval{Low: tt.low, High: tt.high}, tt.rationale)
665			})
666		})
667	}
668}
669
670func TestDropRetract(t *testing.T) {
671	for _, tt := range dropRetractTests {
672		t.Run(tt.desc, func(t *testing.T) {
673			testEdit(t, tt.in, tt.out, true, func(f *File) error {
674				if err := f.DropRetract(VersionInterval{Low: tt.low, High: tt.high}); err != nil {
675					return err
676				}
677				f.Cleanup()
678				return nil
679			})
680		})
681	}
682}
683
684func TestRetractRationale(t *testing.T) {
685	for _, tt := range retractRationaleTests {
686		t.Run(tt.desc, func(t *testing.T) {
687			f, err := Parse("in", []byte(tt.in), nil)
688			if err != nil {
689				t.Fatal(err)
690			}
691			if len(f.Retract) != 1 {
692				t.Fatalf("got %d retract directives; want 1", len(f.Retract))
693			}
694			if got := f.Retract[0].Rationale; got != tt.want {
695				t.Errorf("got %q; want %q", got, tt.want)
696			}
697		})
698	}
699}
700
701func TestSortBlocks(t *testing.T) {
702	for _, tt := range sortBlocksTests {
703		t.Run(tt.desc, func(t *testing.T) {
704			testEdit(t, tt.in, tt.out, tt.strict, func(f *File) error {
705				f.SortBlocks()
706				return nil
707			})
708		})
709	}
710}
711
712func testEdit(t *testing.T, in, want string, strict bool, transform func(f *File) error) *File {
713	t.Helper()
714	parse := Parse
715	if !strict {
716		parse = ParseLax
717	}
718	f, err := parse("in", []byte(in), nil)
719	if err != nil {
720		t.Fatal(err)
721	}
722	g, err := parse("out", []byte(want), nil)
723	if err != nil {
724		t.Fatal(err)
725	}
726	golden, err := g.Format()
727	if err != nil {
728		t.Fatal(err)
729	}
730
731	if err := transform(f); err != nil {
732		t.Fatal(err)
733	}
734	out, err := f.Format()
735	if err != nil {
736		t.Fatal(err)
737	}
738	if !bytes.Equal(out, golden) {
739		t.Errorf("have:\n%s\nwant:\n%s", out, golden)
740	}
741
742	return f
743}
744
745func TestAddRetractValidateVersion(t *testing.T) {
746	for _, tt := range addRetractValidateVersionTests {
747		t.Run(tt.dsc, func(t *testing.T) {
748			f, err := Parse("in", []byte("module m"), nil)
749			if err != nil {
750				t.Fatal(err)
751			}
752			if err = f.AddRetract(VersionInterval{Low: tt.low, High: tt.high}, ""); err == nil {
753				t.Fatal("expected AddRetract to complain about version format")
754			}
755		})
756	}
757}
758
759func TestAddExcludeValidateVersion(t *testing.T) {
760	for _, tt := range addExcludeValidateVersionTests {
761		t.Run(tt.dsc, func(t *testing.T) {
762			f, err := Parse("in", []byte("module m"), nil)
763			if err != nil {
764				t.Fatal(err)
765			}
766			if err = f.AddExclude("aa", tt.ver); err == nil {
767				t.Fatal("expected AddExclude to complain about version format")
768			}
769		})
770	}
771}
772