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