message/pipeline: avoid writing to the testdata directory

If this test is run as a dependency of some other module, the testdata
directory will be read-only. Moreover, if temporary files are written
to the working directory they are likely to interfere with concurrent
version-control operations, such as 'git commit -a'.

Updates golang/go#28387

Change-Id: I15cd9408c63f9b6aed50facbfefa26299392052f
Reviewed-on: https://go-review.googlesource.com/c/text/+/208123
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/message/pipeline/generate.go b/message/pipeline/generate.go
index 5d329b2..ada927a 100644
--- a/message/pipeline/generate.go
+++ b/message/pipeline/generate.go
@@ -49,10 +49,14 @@
 		return err
 	}
 	if !isDir {
-		gopath := build.Default.GOPATH
-		path = filepath.Join(gopath, filepath.FromSlash(pkgs[0].Pkg.Path()))
+		gopath := filepath.SplitList(build.Default.GOPATH)[0]
+		path = filepath.Join(gopath, "src", filepath.FromSlash(pkgs[0].Pkg.Path()))
 	}
-	path = filepath.Join(path, s.Config.GenFile)
+	if filepath.IsAbs(s.Config.GenFile) {
+		path = s.Config.GenFile
+	} else {
+		path = filepath.Join(path, s.Config.GenFile)
+	}
 	cw.WriteGoFile(path, pkg) // TODO: WriteGoFile should return error.
 	return err
 }
diff --git a/message/pipeline/pipeline_test.go b/message/pipeline/pipeline_test.go
index 1f5e44f..fe3b5da 100644
--- a/message/pipeline/pipeline_test.go
+++ b/message/pipeline/pipeline_test.go
@@ -10,11 +10,13 @@
 	"encoding/json"
 	"flag"
 	"fmt"
+	"go/build"
 	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
 	"path/filepath"
+	"reflect"
 	"runtime"
 	"strings"
 	"testing"
@@ -35,12 +37,40 @@
 		t.Skipf("skipping because 'go' command is unavailable: %v", err)
 	}
 
-	const path = "./testdata"
-	dirs, err := ioutil.ReadDir(path)
+	GOPATH, err := ioutil.TempDir("", "pipeline_test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(GOPATH)
+	testdata := filepath.Join(GOPATH, "src", "testdata")
+
+	// Copy the testdata contents into a new module.
+	copyTestdata(t, testdata)
+	initTestdataModule(t, testdata)
+
+	// Several places hard-code the use of build.Default.
+	// Adjust it to match the test's temporary GOPATH.
+	defer func(prev string) { build.Default.GOPATH = prev }(build.Default.GOPATH)
+	build.Default.GOPATH = GOPATH + string(filepath.ListSeparator) + build.Default.GOPATH
+	if wd := reflect.ValueOf(&build.Default).Elem().FieldByName("WorkingDir"); wd.IsValid() {
+		defer func(prev string) { wd.SetString(prev) }(wd.String())
+		wd.SetString(testdata)
+	}
+
+	// To work around https://golang.org/issue/34860, execute the commands
+	// that (transitively) use go/build in the working directory of the
+	// corresponding module.
+	wd, _ := os.Getwd()
+	defer os.Chdir(wd)
+
+	dirs, err := ioutil.ReadDir(testdata)
 	if err != nil {
 		t.Fatal(err)
 	}
 	for _, f := range dirs {
+		if !f.IsDir() {
+			continue
+		}
 		t.Run(f.Name(), func(t *testing.T) {
 			chk := func(t *testing.T, err error) {
 				setHelper(t)
@@ -48,8 +78,8 @@
 					t.Fatal(err)
 				}
 			}
-			dir := filepath.Join(path, f.Name())
-			pkgPath := fmt.Sprintf("%s/%s", path, f.Name())
+			dir := filepath.Join(testdata, f.Name())
+			pkgPath := "testdata/" + f.Name()
 			config := Config{
 				SourceLanguage: language.AmericanEnglish,
 				Packages:       []string{pkgPath},
@@ -57,6 +87,9 @@
 				GenFile:        "catalog_gen.go",
 				GenPackage:     pkgPath,
 			}
+
+			os.Chdir(dir)
+
 			// TODO: load config if available.
 			s, err := Extract(&config)
 			chk(t, err)
@@ -69,34 +102,82 @@
 			chk(t, s.Export())
 			chk(t, s.Generate())
 
+			os.Chdir(wd)
+
 			writeJSON(t, filepath.Join(dir, "extracted.gotext.json"), s.Extracted)
-			checkOutput(t, dir)
+			checkOutput(t, dir, f.Name())
 		})
 	}
 }
 
-func checkOutput(t *testing.T, p string) {
-	filepath.Walk(p, func(p string, f os.FileInfo, err error) error {
+func copyTestdata(t *testing.T, dst string) {
+	err := filepath.Walk("testdata", func(p string, f os.FileInfo, err error) error {
+		if p == "testdata" || strings.HasSuffix(p, ".want") {
+			return nil
+		}
+
+		rel := strings.TrimPrefix(p, "testdata"+string(filepath.Separator))
+		if f.IsDir() {
+			return os.MkdirAll(filepath.Join(dst, rel), 0755)
+		}
+
+		data, err := ioutil.ReadFile(p)
+		if err != nil {
+			return err
+		}
+		return ioutil.WriteFile(filepath.Join(dst, rel), data, 0644)
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func initTestdataModule(t *testing.T, dst string) {
+	xTextDir, err := filepath.Abs("../..")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	goMod := fmt.Sprintf(`module testdata
+go 1.11
+require golang.org/x/text v0.0.0-00010101000000-000000000000
+replace golang.org/x/text v0.0.0-00010101000000-000000000000 => %s
+`, xTextDir)
+	if err := ioutil.WriteFile(filepath.Join(dst, "go.mod"), []byte(goMod), 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	data, err := ioutil.ReadFile(filepath.Join(xTextDir, "go.sum"))
+	if err := ioutil.WriteFile(filepath.Join(dst, "go.sum"), data, 0644); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func checkOutput(t *testing.T, gen string, testdataDir string) {
+	err := filepath.Walk(gen, func(gotFile string, f os.FileInfo, err error) error {
 		if f.IsDir() {
 			return nil
 		}
-		if filepath.Ext(p) != ".want" {
+		rel := strings.TrimPrefix(gotFile, gen+string(filepath.Separator))
+
+		wantFile := filepath.Join("testdata", testdataDir, rel+".want")
+		if _, err := os.Stat(wantFile); os.IsNotExist(err) {
 			return nil
 		}
-		gotFile := p[:len(p)-len(".want")]
+
 		got, err := ioutil.ReadFile(gotFile)
 		if err != nil {
-			t.Errorf("failed to read %q", p)
+			t.Errorf("failed to read %q", gotFile)
 			return nil
 		}
 		if *genFiles {
-			if err := ioutil.WriteFile(p, got, 0644); err != nil {
+			if err := ioutil.WriteFile(wantFile, got, 0644); err != nil {
 				t.Fatal(err)
 			}
 		}
-		want, err := ioutil.ReadFile(p)
+		want, err := ioutil.ReadFile(wantFile)
 		if err != nil {
-			t.Errorf("failed to read %q", p)
+			t.Errorf("failed to read %q", wantFile)
 		} else {
 			scanGot := bufio.NewScanner(bytes.NewReader(got))
 			scanWant := bufio.NewScanner(bytes.NewReader(want))
@@ -122,6 +203,9 @@
 		}
 		return nil
 	})
+	if err != nil {
+		t.Fatal(err)
+	}
 }
 
 func writeJSON(t *testing.T, path string, x interface{}) {
diff --git a/message/pipeline/testdata/test1/catalog_gen.go b/message/pipeline/testdata/test1/catalog_gen.go
deleted file mode 100644
index 7d93f48..0000000
--- a/message/pipeline/testdata/test1/catalog_gen.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
-
-package main
-
-import (
-	"golang.org/x/text/language"
-	"golang.org/x/text/message"
-	"golang.org/x/text/message/catalog"
-)
-
-type dictionary struct {
-	index []uint32
-	data  string
-}
-
-func (d *dictionary) Lookup(key string) (data string, ok bool) {
-	p := messageKeyToIndex[key]
-	start, end := d.index[p], d.index[p+1]
-	if start == end {
-		return "", false
-	}
-	return d.data[start:end], true
-}
-
-func init() {
-	dict := map[string]catalog.Dictionary{
-		"de":    &dictionary{index: deIndex, data: deData},
-		"en_US": &dictionary{index: en_USIndex, data: en_USData},
-		"zh":    &dictionary{index: zhIndex, data: zhData},
-	}
-	fallback := language.MustParse("en-US")
-	cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
-	if err != nil {
-		panic(err)
-	}
-	message.DefaultCatalog = cat
-}
-
-var messageKeyToIndex = map[string]int{
-	"%.2[1]f miles traveled (%[1]f)":                 8,
-	"%[1]s is visiting %[3]s!\n":                     3,
-	"%d files remaining!":                            4,
-	"%d more files remaining!":                       5,
-	"%s is out of order!":                            7,
-	"%s is visiting %s!\n":                           2,
-	"Hello %s!\n":                                    1,
-	"Hello world!\n":                                 0,
-	"Use the following code for your discount: %d\n": 6,
-}
-
-var deIndex = []uint32{ // 10 elements
-	0x00000000, 0x00000011, 0x00000023, 0x0000003d,
-	0x00000057, 0x00000075, 0x00000094, 0x00000094,
-	0x00000094, 0x00000094,
-} // Size: 64 bytes
-
-const deData string = "" + // Size: 148 bytes
-	"\x04\x00\x01\x0a\x0c\x02Hallo Welt!\x04\x00\x01\x0a\x0d\x02Hallo %[1]s!" +
-	"\x04\x00\x01\x0a\x15\x02%[1]s besucht %[2]s!\x04\x00\x01\x0a\x15\x02%[1]" +
-	"s besucht %[3]s!\x02Noch zwei Bestände zu gehen!\x02Noch %[1]d Bestände " +
-	"zu gehen!"
-
-var en_USIndex = []uint32{ // 10 elements
-	0x00000000, 0x00000012, 0x00000024, 0x00000042,
-	0x00000060, 0x00000077, 0x000000ba, 0x000000ef,
-	0x00000106, 0x00000125,
-} // Size: 64 bytes
-
-const en_USData string = "" + // Size: 293 bytes
-	"\x04\x00\x01\x0a\x0d\x02Hello world!\x04\x00\x01\x0a\x0d\x02Hello %[1]s!" +
-	"\x04\x00\x01\x0a\x19\x02%[1]s is visiting %[2]s!\x04\x00\x01\x0a\x19\x02" +
-	"%[1]s is visiting %[3]s!\x02%[1]d files remaining!\x14\x01\x81\x01\x00" +
-	"\x02\x14\x02One file remaining!\x00&\x02There are %[1]d more files remai" +
-	"ning!\x04\x00\x01\x0a0\x02Use the following code for your discount: %[1]" +
-	"d\x02%[1]s is out of order!\x02%.2[1]f miles traveled (%[1]f)"
-
-var zhIndex = []uint32{ // 10 elements
-	0x00000000, 0x00000000, 0x00000000, 0x00000000,
-	0x00000000, 0x00000000, 0x00000000, 0x00000000,
-	0x00000000, 0x00000000,
-} // Size: 64 bytes
-
-const zhData string = ""
-
-// Total table size 633 bytes (0KiB); checksum: 74B32E70
diff --git a/message/pipeline/testdata/test1/extracted.gotext.json b/message/pipeline/testdata/test1/extracted.gotext.json
deleted file mode 100644
index 4d317af..0000000
--- a/message/pipeline/testdata/test1/extracted.gotext.json
+++ /dev/null
@@ -1,188 +0,0 @@
-{
-    "language": "en-US",
-    "messages": [
-        {
-            "id": "Hello world!",
-            "key": "Hello world!\n",
-            "message": "Hello world!",
-            "translation": "",
-            "position": "testdata/test1/test1.go:19:10"
-        },
-        {
-            "id": "Hello {City}!",
-            "key": "Hello %s!\n",
-            "message": "Hello {City}!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "City",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "city"
-                }
-            ],
-            "position": "testdata/test1/test1.go:24:10"
-        },
-        {
-            "id": "{Person} is visiting {Place}!",
-            "key": "%s is visiting %s!\n",
-            "message": "{Person} is visiting {Place}!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "Person",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "person",
-                    "comment": "The person of matter."
-                },
-                {
-                    "id": "Place",
-                    "string": "%[2]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 2,
-                    "expr": "place",
-                    "comment": "Place the person is visiting."
-                }
-            ],
-            "position": "testdata/test1/test1.go:30:10"
-        },
-        {
-            "id": "{Person} is visiting {Place}!",
-            "key": "%[1]s is visiting %[3]s!\n",
-            "message": "{Person} is visiting {Place}!",
-            "translation": "",
-            "comment": "Field names are placeholders.",
-            "placeholders": [
-                {
-                    "id": "Person",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "pp.Person"
-                },
-                {
-                    "id": "Place",
-                    "string": "%[3]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 3,
-                    "expr": "pp.Place",
-                    "comment": "Place the person is visiting."
-                },
-                {
-                    "id": "Extra",
-                    "string": "%[2]v",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 2,
-                    "expr": "pp.extra"
-                }
-            ],
-            "position": "testdata/test1/test1.go:44:10"
-        },
-        {
-            "id": "{2} files remaining!",
-            "key": "%d files remaining!",
-            "message": "{2} files remaining!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "2",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "2"
-                }
-            ],
-            "position": "testdata/test1/test1.go:51:10"
-        },
-        {
-            "id": "{N} more files remaining!",
-            "key": "%d more files remaining!",
-            "message": "{N} more files remaining!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "N",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "n"
-                }
-            ],
-            "position": "testdata/test1/test1.go:56:10"
-        },
-        {
-            "id": "Use the following code for your discount: {ReferralCode}",
-            "key": "Use the following code for your discount: %d\n",
-            "message": "Use the following code for your discount: {ReferralCode}",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "ReferralCode",
-                    "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "c"
-                }
-            ],
-            "position": "testdata/test1/test1.go:64:10"
-        },
-        {
-            "id": [
-                "msgOutOfOrder",
-                "{Device} is out of order!"
-            ],
-            "key": "%s is out of order!",
-            "message": "{Device} is out of order!",
-            "translation": "",
-            "comment": "This comment wins.\n",
-            "placeholders": [
-                {
-                    "id": "Device",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "device"
-                }
-            ],
-            "position": "testdata/test1/test1.go:70:10"
-        },
-        {
-            "id": "{Miles} miles traveled ({Miles_1})",
-            "key": "%.2[1]f miles traveled (%[1]f)",
-            "message": "{Miles} miles traveled ({Miles_1})",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "Miles",
-                    "string": "%.2[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                },
-                {
-                    "id": "Miles_1",
-                    "string": "%[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                }
-            ],
-            "position": "testdata/test1/test1.go:74:10"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/message/pipeline/testdata/test1/extracted.gotext.json.want b/message/pipeline/testdata/test1/extracted.gotext.json.want
index 4d317af..43302c1 100644
--- a/message/pipeline/testdata/test1/extracted.gotext.json.want
+++ b/message/pipeline/testdata/test1/extracted.gotext.json.want
@@ -130,7 +130,7 @@
                 {
                     "id": "ReferralCode",
                     "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
+                    "type": "testdata/test1.referralCode",
                     "underlyingType": "int",
                     "argNum": 1,
                     "expr": "c"
diff --git a/message/pipeline/testdata/test1/locales/de/out.gotext.json b/message/pipeline/testdata/test1/locales/de/out.gotext.json
deleted file mode 100755
index f19e21d..0000000
--- a/message/pipeline/testdata/test1/locales/de/out.gotext.json
+++ /dev/null
@@ -1,137 +0,0 @@
-{
-    "language": "de",
-    "messages": [
-        {
-            "id": "Hello world!",
-            "message": "Hello world!",
-            "translation": "Hallo Welt!"
-        },
-        {
-            "id": "Hello {City}!",
-            "message": "Hello {City}!",
-            "translation": "Hallo {City}!",
-            "placeholders": [
-                {
-                    "id": "City",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "city"
-                }
-            ]
-        },
-        {
-            "id": "{Person} is visiting {Place}!",
-            "message": "{Person} is visiting {Place}!",
-            "translation": "{Person} besucht {Place}!",
-            "placeholders": [
-                {
-                    "id": "Person",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "person",
-                    "comment": "The person of matter."
-                },
-                {
-                    "id": "Place",
-                    "string": "%[2]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 2,
-                    "expr": "place",
-                    "comment": "Place the person is visiting."
-                }
-            ]
-        },
-        {
-            "id": "{2} files remaining!",
-            "message": "{2} files remaining!",
-            "translation": "Noch zwei Bestände zu gehen!",
-            "placeholders": [
-                {
-                    "id": "2",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "2"
-                }
-            ]
-        },
-        {
-            "id": "{N} more files remaining!",
-            "message": "{N} more files remaining!",
-            "translation": "Noch {N} Bestände zu gehen!",
-            "placeholders": [
-                {
-                    "id": "N",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "n"
-                }
-            ]
-        },
-        {
-            "id": "Use the following code for your discount: {ReferralCode}",
-            "message": "Use the following code for your discount: {ReferralCode}",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "ReferralCode",
-                    "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "c"
-                }
-            ]
-        },
-        {
-            "id": [
-                "msgOutOfOrder",
-                "{Device} is out of order!"
-            ],
-            "message": "{Device} is out of order!",
-            "translation": "",
-            "comment": "This comment wins.\n",
-            "placeholders": [
-                {
-                    "id": "Device",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "device"
-                }
-            ]
-        },
-        {
-            "id": "{Miles} miles traveled ({Miles_1})",
-            "message": "{Miles} miles traveled ({Miles_1})",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "Miles",
-                    "string": "%.2[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                },
-                {
-                    "id": "Miles_1",
-                    "string": "%[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                }
-            ]
-        }
-    ]
-}
\ No newline at end of file
diff --git a/message/pipeline/testdata/test1/locales/de/out.gotext.json.want b/message/pipeline/testdata/test1/locales/de/out.gotext.json.want
index f19e21d..01c450a 100755
--- a/message/pipeline/testdata/test1/locales/de/out.gotext.json.want
+++ b/message/pipeline/testdata/test1/locales/de/out.gotext.json.want
@@ -84,7 +84,7 @@
                 {
                     "id": "ReferralCode",
                     "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
+                    "type": "testdata/test1.referralCode",
                     "underlyingType": "int",
                     "argNum": 1,
                     "expr": "c"
diff --git a/message/pipeline/testdata/test1/locales/en-US/out.gotext.json b/message/pipeline/testdata/test1/locales/en-US/out.gotext.json
deleted file mode 100755
index 59f92a5..0000000
--- a/message/pipeline/testdata/test1/locales/en-US/out.gotext.json
+++ /dev/null
@@ -1,154 +0,0 @@
-{
-    "language": "en-US",
-    "messages": [
-        {
-            "id": "Hello world!",
-            "message": "Hello world!",
-            "translation": "Hello world!"
-        },
-        {
-            "id": "Hello {City}!",
-            "message": "Hello {City}!",
-            "translation": "Hello {City}!",
-            "placeholders": [
-                {
-                    "id": "City",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "city"
-                }
-            ]
-        },
-        {
-            "id": "{Person} is visiting {Place}!",
-            "message": "{Person} is visiting {Place}!",
-            "translation": "{Person} is visiting {Place}!",
-            "placeholders": [
-                {
-                    "id": "Person",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "person",
-                    "comment": "The person of matter."
-                },
-                {
-                    "id": "Place",
-                    "string": "%[2]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 2,
-                    "expr": "place",
-                    "comment": "Place the person is visiting."
-                }
-            ]
-        },
-        {
-            "id": "{2} files remaining!",
-            "message": "{2} files remaining!",
-            "translation": "{2} files remaining!",
-            "translatorComment": "Copied from source.",
-            "placeholders": [
-                {
-                    "id": "2",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "2"
-                }
-            ],
-            "fuzzy": true
-        },
-        {
-            "id": "{N} more files remaining!",
-            "message": "{N} more files remaining!",
-            "translation": {
-                "select": {
-                    "feature": "plural",
-                    "arg": "N",
-                    "cases": {
-                        "one": {
-                            "msg": "One file remaining!"
-                        },
-                        "other": {
-                            "msg": "There are {N} more files remaining!"
-                        }
-                    }
-                }
-            },
-            "placeholders": [
-                {
-                    "id": "N",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "n"
-                }
-            ]
-        },
-        {
-            "id": "Use the following code for your discount: {ReferralCode}",
-            "message": "Use the following code for your discount: {ReferralCode}",
-            "translation": "Use the following code for your discount: {ReferralCode}",
-            "translatorComment": "Copied from source.",
-            "placeholders": [
-                {
-                    "id": "ReferralCode",
-                    "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "c"
-                }
-            ],
-            "fuzzy": true
-        },
-        {
-            "id": [
-                "msgOutOfOrder",
-                "{Device} is out of order!"
-            ],
-            "message": "{Device} is out of order!",
-            "translation": "{Device} is out of order!",
-            "comment": "This comment wins.\n",
-            "placeholders": [
-                {
-                    "id": "Device",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "device"
-                }
-            ]
-        },
-        {
-            "id": "{Miles} miles traveled ({Miles_1})",
-            "message": "{Miles} miles traveled ({Miles_1})",
-            "translation": "{Miles} miles traveled ({Miles_1})",
-            "placeholders": [
-                {
-                    "id": "Miles",
-                    "string": "%.2[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                },
-                {
-                    "id": "Miles_1",
-                    "string": "%[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                }
-            ]
-        }
-    ]
-}
\ No newline at end of file
diff --git a/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want b/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want
index 59f92a5..95ff5ad 100755
--- a/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want
+++ b/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want
@@ -100,7 +100,7 @@
                 {
                     "id": "ReferralCode",
                     "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
+                    "type": "testdata/test1.referralCode",
                     "underlyingType": "int",
                     "argNum": 1,
                     "expr": "c"
diff --git a/message/pipeline/testdata/test1/locales/zh/out.gotext.json b/message/pipeline/testdata/test1/locales/zh/out.gotext.json
deleted file mode 100755
index 9bede65..0000000
--- a/message/pipeline/testdata/test1/locales/zh/out.gotext.json
+++ /dev/null
@@ -1,137 +0,0 @@
-{
-    "language": "zh",
-    "messages": [
-        {
-            "id": "Hello world!",
-            "message": "Hello world!",
-            "translation": ""
-        },
-        {
-            "id": "Hello {City}!",
-            "message": "Hello {City}!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "City",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "city"
-                }
-            ]
-        },
-        {
-            "id": "{Person} is visiting {Place}!",
-            "message": "{Person} is visiting {Place}!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "Person",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "person",
-                    "comment": "The person of matter."
-                },
-                {
-                    "id": "Place",
-                    "string": "%[2]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 2,
-                    "expr": "place",
-                    "comment": "Place the person is visiting."
-                }
-            ]
-        },
-        {
-            "id": "{2} files remaining!",
-            "message": "{2} files remaining!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "2",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "2"
-                }
-            ]
-        },
-        {
-            "id": "{N} more files remaining!",
-            "message": "{N} more files remaining!",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "N",
-                    "string": "%[1]d",
-                    "type": "int",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "n"
-                }
-            ]
-        },
-        {
-            "id": "Use the following code for your discount: {ReferralCode}",
-            "message": "Use the following code for your discount: {ReferralCode}",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "ReferralCode",
-                    "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
-                    "underlyingType": "int",
-                    "argNum": 1,
-                    "expr": "c"
-                }
-            ]
-        },
-        {
-            "id": [
-                "msgOutOfOrder",
-                "{Device} is out of order!"
-            ],
-            "message": "{Device} is out of order!",
-            "translation": "",
-            "comment": "This comment wins.\n",
-            "placeholders": [
-                {
-                    "id": "Device",
-                    "string": "%[1]s",
-                    "type": "string",
-                    "underlyingType": "string",
-                    "argNum": 1,
-                    "expr": "device"
-                }
-            ]
-        },
-        {
-            "id": "{Miles} miles traveled ({Miles_1})",
-            "message": "{Miles} miles traveled ({Miles_1})",
-            "translation": "",
-            "placeholders": [
-                {
-                    "id": "Miles",
-                    "string": "%.2[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                },
-                {
-                    "id": "Miles_1",
-                    "string": "%[1]f",
-                    "type": "float64",
-                    "underlyingType": "float64",
-                    "argNum": 1,
-                    "expr": "miles"
-                }
-            ]
-        }
-    ]
-}
\ No newline at end of file
diff --git a/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want b/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want
index 9bede65..1b5c175 100755
--- a/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want
+++ b/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want
@@ -84,7 +84,7 @@
                 {
                     "id": "ReferralCode",
                     "string": "%[1]d",
-                    "type": "./testdata/test1.referralCode",
+                    "type": "testdata/test1.referralCode",
                     "underlyingType": "int",
                     "argNum": 1,
                     "expr": "c"