1// Copyright 2017 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 pipeline
6
7import (
8	"bufio"
9	"bytes"
10	"encoding/json"
11	"flag"
12	"fmt"
13	"go/build"
14	"io/ioutil"
15	"os"
16	"os/exec"
17	"path"
18	"path/filepath"
19	"reflect"
20	"runtime"
21	"strings"
22	"testing"
23
24	"golang.org/x/text/language"
25)
26
27var genFiles = flag.Bool("gen", false, "generate output files instead of comparing")
28
29// setHelper is testing.T.Helper on Go 1.9+, overridden by go19_test.go.
30var setHelper = func(t *testing.T) {}
31
32func TestFullCycle(t *testing.T) {
33	if runtime.GOOS == "android" {
34		t.Skip("cannot load outside packages on android")
35	}
36	if _, err := exec.LookPath("go"); err != nil {
37		t.Skipf("skipping because 'go' command is unavailable: %v", err)
38	}
39
40	GOPATH, err := ioutil.TempDir("", "pipeline_test")
41	if err != nil {
42		t.Fatal(err)
43	}
44	defer os.RemoveAll(GOPATH)
45	testdata := filepath.Join(GOPATH, "src", "testdata")
46
47	// Copy the testdata contents into a new module.
48	copyTestdata(t, testdata)
49	initTestdataModule(t, testdata)
50
51	// Several places hard-code the use of build.Default.
52	// Adjust it to match the test's temporary GOPATH.
53	defer func(prev string) { build.Default.GOPATH = prev }(build.Default.GOPATH)
54	build.Default.GOPATH = GOPATH + string(filepath.ListSeparator) + build.Default.GOPATH
55	if wd := reflect.ValueOf(&build.Default).Elem().FieldByName("WorkingDir"); wd.IsValid() {
56		defer func(prev string) { wd.SetString(prev) }(wd.String())
57		wd.SetString(testdata)
58	}
59
60	// To work around https://golang.org/issue/34860, execute the commands
61	// that (transitively) use go/build in the working directory of the
62	// corresponding module.
63	wd, _ := os.Getwd()
64	defer os.Chdir(wd)
65
66	dirs, err := ioutil.ReadDir(testdata)
67	if err != nil {
68		t.Fatal(err)
69	}
70	for _, f := range dirs {
71		if !f.IsDir() {
72			continue
73		}
74		t.Run(f.Name(), func(t *testing.T) {
75			chk := func(t *testing.T, err error) {
76				setHelper(t)
77				if err != nil {
78					t.Fatal(err)
79				}
80			}
81			dir := filepath.Join(testdata, f.Name())
82			pkgPath := "testdata/" + f.Name()
83			config := Config{
84				SourceLanguage: language.AmericanEnglish,
85				Packages:       []string{pkgPath},
86				Dir:            filepath.Join(dir, "locales"),
87				GenFile:        "catalog_gen.go",
88				GenPackage:     pkgPath,
89			}
90
91			os.Chdir(dir)
92
93			// TODO: load config if available.
94			s, err := Extract(&config)
95			chk(t, err)
96			chk(t, s.Import())
97			chk(t, s.Merge())
98			// TODO:
99			//  for range s.Config.Actions {
100			//  	//  TODO: do the actions.
101			//  }
102			chk(t, s.Export())
103			chk(t, s.Generate())
104
105			os.Chdir(wd)
106
107			writeJSON(t, filepath.Join(dir, "extracted.gotext.json"), s.Extracted)
108			checkOutput(t, dir, f.Name())
109		})
110	}
111}
112
113func copyTestdata(t *testing.T, dst string) {
114	err := filepath.Walk("testdata", func(p string, f os.FileInfo, err error) error {
115		if p == "testdata" || strings.HasSuffix(p, ".want") {
116			return nil
117		}
118
119		rel := strings.TrimPrefix(p, "testdata"+string(filepath.Separator))
120		if f.IsDir() {
121			return os.MkdirAll(filepath.Join(dst, rel), 0755)
122		}
123
124		data, err := ioutil.ReadFile(p)
125		if err != nil {
126			return err
127		}
128		return ioutil.WriteFile(filepath.Join(dst, rel), data, 0644)
129	})
130	if err != nil {
131		t.Fatal(err)
132	}
133}
134
135func initTestdataModule(t *testing.T, dst string) {
136	xTextDir, err := filepath.Abs("../..")
137	if err != nil {
138		t.Fatal(err)
139	}
140
141	goMod := fmt.Sprintf(`module testdata
142go 1.11
143require golang.org/x/text v0.0.0-00010101000000-000000000000
144replace golang.org/x/text v0.0.0-00010101000000-000000000000 => %s
145`, xTextDir)
146	if err := ioutil.WriteFile(filepath.Join(dst, "go.mod"), []byte(goMod), 0644); err != nil {
147		t.Fatal(err)
148	}
149
150	data, err := ioutil.ReadFile(filepath.Join(xTextDir, "go.sum"))
151	if err := ioutil.WriteFile(filepath.Join(dst, "go.sum"), data, 0644); err != nil {
152		t.Fatal(err)
153	}
154}
155
156func checkOutput(t *testing.T, gen string, testdataDir string) {
157	err := filepath.Walk(gen, func(gotFile string, f os.FileInfo, err error) error {
158		if f.IsDir() {
159			return nil
160		}
161		rel := strings.TrimPrefix(gotFile, gen+string(filepath.Separator))
162
163		wantFile := filepath.Join("testdata", testdataDir, rel+".want")
164		if _, err := os.Stat(wantFile); os.IsNotExist(err) {
165			return nil
166		}
167
168		got, err := ioutil.ReadFile(gotFile)
169		if err != nil {
170			t.Errorf("failed to read %q", gotFile)
171			return nil
172		}
173		if *genFiles {
174			if err := ioutil.WriteFile(wantFile, got, 0644); err != nil {
175				t.Fatal(err)
176			}
177		}
178		want, err := ioutil.ReadFile(wantFile)
179		if err != nil {
180			t.Errorf("failed to read %q", wantFile)
181		} else {
182			scanGot := bufio.NewScanner(bytes.NewReader(got))
183			scanWant := bufio.NewScanner(bytes.NewReader(want))
184			line := 0
185			clean := func(s string) string {
186				if i := strings.LastIndex(s, "//"); i != -1 {
187					s = s[:i]
188				}
189				return path.Clean(filepath.ToSlash(s))
190			}
191			for scanGot.Scan() && scanWant.Scan() {
192				got := clean(scanGot.Text())
193				want := clean(scanWant.Text())
194				if got != want {
195					t.Errorf("file %q differs from .want file at line %d:\n\t%s\n\t%s", gotFile, line, got, want)
196					break
197				}
198				line++
199			}
200			if scanGot.Scan() || scanWant.Scan() {
201				t.Errorf("file %q differs from .want file at line %d.", gotFile, line)
202			}
203		}
204		return nil
205	})
206	if err != nil {
207		t.Fatal(err)
208	}
209}
210
211func writeJSON(t *testing.T, path string, x interface{}) {
212	data, err := json.MarshalIndent(x, "", "    ")
213	if err != nil {
214		t.Fatal(err)
215	}
216	if err := ioutil.WriteFile(path, data, 0644); err != nil {
217		t.Fatal(err)
218	}
219}
220