1// Copyright 2020 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
5// +build ignore
6
7// Test cases for mkmerge.go.
8// Usage:
9//     $ go test mkmerge.go mkmerge_test.go
10package main
11
12import (
13	"bytes"
14	"fmt"
15	"go/parser"
16	"go/token"
17	"html/template"
18	"strings"
19	"testing"
20)
21
22func TestImports(t *testing.T) {
23	t.Run("importName", func(t *testing.T) {
24		cases := []struct {
25			src   string
26			ident string
27		}{
28			{`"syscall"`, "syscall"},
29			{`. "foobar"`, "."},
30			{`"go/ast"`, "ast"},
31			{`moo "go/format"`, "moo"},
32			{`. "go/token"`, "."},
33			{`"golang.org/x/sys/unix"`, "unix"},
34			{`nix "golang.org/x/sys/unix"`, "nix"},
35			{`_ "golang.org/x/sys/unix"`, "_"},
36		}
37
38		for _, c := range cases {
39			pkgSrc := fmt.Sprintf("package main\nimport %s", c.src)
40
41			f, err := parser.ParseFile(token.NewFileSet(), "", pkgSrc, parser.ImportsOnly)
42			if err != nil {
43				t.Error(err)
44				continue
45			}
46			if len(f.Imports) != 1 {
47				t.Errorf("Got %d imports, expected 1", len(f.Imports))
48				continue
49			}
50
51			got, err := importName(f.Imports[0])
52			if err != nil {
53				t.Fatal(err)
54			}
55			if got != c.ident {
56				t.Errorf("Got %q, expected %q", got, c.ident)
57			}
58		}
59	})
60
61	t.Run("filterImports", func(t *testing.T) {
62		cases := []struct{ before, after string }{
63			{`package test
64
65			import (
66				"foo"
67				"bar"
68			)`,
69				"package test\n"},
70			{`package test
71
72			import (
73				"foo"
74				"bar"
75			)
76
77			func useFoo() { foo.Usage() }`,
78				`package test
79
80import (
81	"foo"
82)
83
84func useFoo() { foo.Usage() }
85`},
86		}
87		for _, c := range cases {
88			got, err := filterImports([]byte(c.before))
89			if err != nil {
90				t.Error(err)
91			}
92
93			if string(got) != c.after {
94				t.Errorf("Got:\n%s\nExpected:\n%s\n", got, c.after)
95			}
96		}
97	})
98}
99
100func TestMerge(t *testing.T) {
101	// Input architecture files
102	inTmpl := template.Must(template.New("input").Parse(`
103// Package comments
104
105// build directives for arch{{.}}
106
107// +build goos,arch{{.}}
108
109package main
110
111/*
112#include <stdint.h>
113#include <stddef.h>
114int utimes(uintptr_t, uintptr_t);
115int utimensat(int, uintptr_t, uintptr_t, int);
116*/
117import "C"
118
119// The imports
120import (
121	"commonDep"
122	"uniqueDep{{.}}"
123)
124
125// Vars
126var (
127	commonVar = commonDep.Use("common")
128
129	uniqueVar{{.}} = "unique{{.}}"
130)
131
132// Common free standing comment
133
134// Common comment
135const COMMON_INDEPENDENT = 1234
136const UNIQUE_INDEPENDENT_{{.}} = "UNIQUE_INDEPENDENT_{{.}}"
137
138// Group comment
139const (
140	COMMON_GROUP = "COMMON_GROUP"
141	UNIQUE_GROUP_{{.}} = "UNIQUE_GROUP_{{.}}"
142)
143
144// Group2 comment
145const (
146	UNIQUE_GROUP21_{{.}} = "UNIQUE_GROUP21_{{.}}"
147	UNIQUE_GROUP22_{{.}} = "UNIQUE_GROUP22_{{.}}"
148)
149
150// Group3 comment
151const (
152	sub1Common1 = 11
153	sub1Unique2{{.}} = 12
154	sub1Common3_LONG = 13
155
156	sub2Unique1{{.}} = 21
157	sub2Common2 = 22
158	sub2Common3 = 23
159	sub2Unique4{{.}} = 24
160)
161
162type commonInt int
163
164type uniqueInt{{.}} int
165
166func commonF() string {
167	return commonDep.Use("common")
168	}
169
170func uniqueF() string {
171	C.utimes(0, 0)
172	return uniqueDep{{.}}.Use("{{.}}")
173	}
174
175// Group4 comment
176const (
177	sub3Common1 = 31
178	sub3Unique2{{.}} = 32
179	sub3Unique3{{.}} = 33
180	sub3Common4 = 34
181
182	sub4Common1, sub4Unique2{{.}} = 41, 42
183	sub4Unique3{{.}}, sub4Common4 = 43, 44
184)
185`))
186
187	// Filtered architecture files
188	outTmpl := template.Must(template.New("output").Parse(`// Package comments
189
190// build directives for arch{{.}}
191
192// +build goos,arch{{.}}
193
194package main
195
196/*
197#include <stdint.h>
198#include <stddef.h>
199int utimes(uintptr_t, uintptr_t);
200int utimensat(int, uintptr_t, uintptr_t, int);
201*/
202import "C"
203
204// The imports
205import (
206	"commonDep"
207	"uniqueDep{{.}}"
208)
209
210// Vars
211var (
212	commonVar = commonDep.Use("common")
213
214	uniqueVar{{.}} = "unique{{.}}"
215)
216
217const UNIQUE_INDEPENDENT_{{.}} = "UNIQUE_INDEPENDENT_{{.}}"
218
219// Group comment
220const (
221	UNIQUE_GROUP_{{.}} = "UNIQUE_GROUP_{{.}}"
222)
223
224// Group2 comment
225const (
226	UNIQUE_GROUP21_{{.}} = "UNIQUE_GROUP21_{{.}}"
227	UNIQUE_GROUP22_{{.}} = "UNIQUE_GROUP22_{{.}}"
228)
229
230// Group3 comment
231const (
232	sub1Unique2{{.}} = 12
233
234	sub2Unique1{{.}} = 21
235	sub2Unique4{{.}} = 24
236)
237
238type uniqueInt{{.}} int
239
240func uniqueF() string {
241	C.utimes(0, 0)
242	return uniqueDep{{.}}.Use("{{.}}")
243}
244
245// Group4 comment
246const (
247	sub3Unique2{{.}} = 32
248	sub3Unique3{{.}} = 33
249
250	sub4Common1, sub4Unique2{{.}} = 41, 42
251	sub4Unique3{{.}}, sub4Common4 = 43, 44
252)
253`))
254
255	const mergedFile = `// Package comments
256
257package main
258
259// The imports
260import (
261	"commonDep"
262)
263
264// Common free standing comment
265
266// Common comment
267const COMMON_INDEPENDENT = 1234
268
269// Group comment
270const (
271	COMMON_GROUP = "COMMON_GROUP"
272)
273
274// Group3 comment
275const (
276	sub1Common1      = 11
277	sub1Common3_LONG = 13
278
279	sub2Common2 = 22
280	sub2Common3 = 23
281)
282
283type commonInt int
284
285func commonF() string {
286	return commonDep.Use("common")
287}
288
289// Group4 comment
290const (
291	sub3Common1 = 31
292	sub3Common4 = 34
293)
294`
295
296	// Generate source code for different "architectures"
297	var inFiles, outFiles []srcFile
298	for _, arch := range strings.Fields("A B C D") {
299		buf := new(bytes.Buffer)
300		err := inTmpl.Execute(buf, arch)
301		if err != nil {
302			t.Fatal(err)
303		}
304		inFiles = append(inFiles, srcFile{"file" + arch, buf.Bytes()})
305
306		buf = new(bytes.Buffer)
307		err = outTmpl.Execute(buf, arch)
308		if err != nil {
309			t.Fatal(err)
310		}
311		outFiles = append(outFiles, srcFile{"file" + arch, buf.Bytes()})
312	}
313
314	t.Run("getCodeSet", func(t *testing.T) {
315		got, err := getCodeSet(inFiles[0].src)
316		if err != nil {
317			t.Fatal(err)
318		}
319
320		expectedElems := []codeElem{
321			{token.COMMENT, "Package comments\n"},
322			{token.COMMENT, "build directives for archA\n"},
323			{token.COMMENT, "+build goos,archA\n"},
324			{token.CONST, `COMMON_INDEPENDENT = 1234`},
325			{token.CONST, `UNIQUE_INDEPENDENT_A = "UNIQUE_INDEPENDENT_A"`},
326			{token.CONST, `COMMON_GROUP = "COMMON_GROUP"`},
327			{token.CONST, `UNIQUE_GROUP_A = "UNIQUE_GROUP_A"`},
328			{token.CONST, `UNIQUE_GROUP21_A = "UNIQUE_GROUP21_A"`},
329			{token.CONST, `UNIQUE_GROUP22_A = "UNIQUE_GROUP22_A"`},
330			{token.CONST, `sub1Common1 = 11`},
331			{token.CONST, `sub1Unique2A = 12`},
332			{token.CONST, `sub1Common3_LONG = 13`},
333			{token.CONST, `sub2Unique1A = 21`},
334			{token.CONST, `sub2Common2 = 22`},
335			{token.CONST, `sub2Common3 = 23`},
336			{token.CONST, `sub2Unique4A = 24`},
337			{token.CONST, `sub3Common1 = 31`},
338			{token.CONST, `sub3Unique2A = 32`},
339			{token.CONST, `sub3Unique3A = 33`},
340			{token.CONST, `sub3Common4 = 34`},
341			{token.CONST, `sub4Common1, sub4Unique2A = 41, 42`},
342			{token.CONST, `sub4Unique3A, sub4Common4 = 43, 44`},
343			{token.TYPE, `commonInt int`},
344			{token.TYPE, `uniqueIntA int`},
345			{token.FUNC, `func commonF() string {
346	return commonDep.Use("common")
347}`},
348			{token.FUNC, `func uniqueF() string {
349	C.utimes(0, 0)
350	return uniqueDepA.Use("A")
351}`},
352		}
353		expected := newCodeSet()
354		for _, d := range expectedElems {
355			expected.add(d)
356		}
357
358		if len(got.set) != len(expected.set) {
359			t.Errorf("Got %d codeElems, expected %d", len(got.set), len(expected.set))
360		}
361		for expElem := range expected.set {
362			if !got.has(expElem) {
363				t.Errorf("Didn't get expected codeElem %#v", expElem)
364			}
365		}
366		for gotElem := range got.set {
367			if !expected.has(gotElem) {
368				t.Errorf("Got unexpected codeElem %#v", gotElem)
369			}
370		}
371	})
372
373	t.Run("getCommonSet", func(t *testing.T) {
374		got, err := getCommonSet(inFiles)
375		if err != nil {
376			t.Fatal(err)
377		}
378
379		expected := newCodeSet()
380		expected.add(codeElem{token.COMMENT, "Package comments\n"})
381		expected.add(codeElem{token.CONST, `COMMON_INDEPENDENT = 1234`})
382		expected.add(codeElem{token.CONST, `COMMON_GROUP = "COMMON_GROUP"`})
383		expected.add(codeElem{token.CONST, `sub1Common1 = 11`})
384		expected.add(codeElem{token.CONST, `sub1Common3_LONG = 13`})
385		expected.add(codeElem{token.CONST, `sub2Common2 = 22`})
386		expected.add(codeElem{token.CONST, `sub2Common3 = 23`})
387		expected.add(codeElem{token.CONST, `sub3Common1 = 31`})
388		expected.add(codeElem{token.CONST, `sub3Common4 = 34`})
389		expected.add(codeElem{token.TYPE, `commonInt int`})
390		expected.add(codeElem{token.FUNC, `func commonF() string {
391	return commonDep.Use("common")
392}`})
393
394		if len(got.set) != len(expected.set) {
395			t.Errorf("Got %d codeElems, expected %d", len(got.set), len(expected.set))
396		}
397		for expElem := range expected.set {
398			if !got.has(expElem) {
399				t.Errorf("Didn't get expected codeElem %#v", expElem)
400			}
401		}
402		for gotElem := range got.set {
403			if !expected.has(gotElem) {
404				t.Errorf("Got unexpected codeElem %#v", gotElem)
405			}
406		}
407	})
408
409	t.Run("filter(keepCommon)", func(t *testing.T) {
410		commonSet, err := getCommonSet(inFiles)
411		if err != nil {
412			t.Fatal(err)
413		}
414
415		got, err := filter(inFiles[0].src, commonSet.keepCommon)
416		expected := []byte(mergedFile)
417
418		if !bytes.Equal(got, expected) {
419			t.Errorf("Got:\n%s\nExpected:\n%s", addLineNr(got), addLineNr(expected))
420			diffLines(t, got, expected)
421		}
422	})
423
424	t.Run("filter(keepArchSpecific)", func(t *testing.T) {
425		commonSet, err := getCommonSet(inFiles)
426		if err != nil {
427			t.Fatal(err)
428		}
429
430		for i := range inFiles {
431			got, err := filter(inFiles[i].src, commonSet.keepArchSpecific)
432			if err != nil {
433				t.Fatal(err)
434			}
435
436			expected := outFiles[i].src
437
438			if !bytes.Equal(got, expected) {
439				t.Errorf("Got:\n%s\nExpected:\n%s", addLineNr(got), addLineNr(expected))
440				diffLines(t, got, expected)
441			}
442		}
443	})
444}
445
446func TestMergedName(t *testing.T) {
447	t.Run("getValidGOOS", func(t *testing.T) {
448		testcases := []struct {
449			filename, goos string
450			ok             bool
451		}{
452			{"zerrors_aix.go", "aix", true},
453			{"zerrors_darwin.go", "darwin", true},
454			{"zerrors_dragonfly.go", "dragonfly", true},
455			{"zerrors_freebsd.go", "freebsd", true},
456			{"zerrors_linux.go", "linux", true},
457			{"zerrors_netbsd.go", "netbsd", true},
458			{"zerrors_openbsd.go", "openbsd", true},
459			{"zerrors_solaris.go", "solaris", true},
460			{"zerrors_multics.go", "", false},
461		}
462		for _, tc := range testcases {
463			goos, ok := getValidGOOS(tc.filename)
464			if goos != tc.goos {
465				t.Errorf("got GOOS %q, expected %q", goos, tc.goos)
466			}
467			if ok != tc.ok {
468				t.Errorf("got ok %v, expected %v", ok, tc.ok)
469			}
470		}
471	})
472}
473
474// Helper functions to diff test sources
475
476func diffLines(t *testing.T, got, expected []byte) {
477	t.Helper()
478
479	gotLines := bytes.Split(got, []byte{'\n'})
480	expLines := bytes.Split(expected, []byte{'\n'})
481
482	i := 0
483	for i < len(gotLines) && i < len(expLines) {
484		if !bytes.Equal(gotLines[i], expLines[i]) {
485			t.Errorf("Line %d: Got:\n%q\nExpected:\n%q", i+1, gotLines[i], expLines[i])
486			return
487		}
488		i++
489	}
490
491	if i < len(gotLines) && i >= len(expLines) {
492		t.Errorf("Line %d: got %q, expected EOF", i+1, gotLines[i])
493	}
494	if i >= len(gotLines) && i < len(expLines) {
495		t.Errorf("Line %d: got EOF, expected %q", i+1, gotLines[i])
496	}
497}
498
499func addLineNr(src []byte) []byte {
500	lines := bytes.Split(src, []byte("\n"))
501	for i, line := range lines {
502		lines[i] = []byte(fmt.Sprintf("%d: %s", i+1, line))
503	}
504	return bytes.Join(lines, []byte("\n"))
505}
506