internal/lsp: interpolate variables in the sandbox

Change-Id: I818f0a6e77748720ba827ebc80719f344d95a436
Reviewed-on: https://go-review.googlesource.com/c/tools/+/256581
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/fake/sandbox.go b/internal/lsp/fake/sandbox.go
index e443fc3..549cb2c 100644
--- a/internal/lsp/fake/sandbox.go
+++ b/internal/lsp/fake/sandbox.go
@@ -35,6 +35,9 @@
 	RootDir string
 	// Files holds a txtar-encoded archive of files to populate the initial state
 	// of the working directory.
+	//
+	// For convenience, the special substring "$SANDBOX_WORKDIR" is replaced with
+	// the sandbox's resolved working directory before writing files.
 	Files string
 	// InGoPath specifies that the working directory should be within the
 	// temporary GOPATH.
@@ -121,7 +124,7 @@
 			return nil, err
 		}
 		sb.Workdir = NewWorkdir(workdir)
-		if err := sb.Workdir.WriteInitialFiles(config.Files); err != nil {
+		if err := sb.Workdir.writeInitialFiles(config.Files); err != nil {
 			return nil, err
 		}
 	}
diff --git a/internal/lsp/fake/workdir.go b/internal/lsp/fake/workdir.go
index cb24fde..6b7f734 100644
--- a/internal/lsp/fake/workdir.go
+++ b/internal/lsp/fake/workdir.go
@@ -5,6 +5,7 @@
 package fake
 
 import (
+	"bytes"
 	"context"
 	"io/ioutil"
 	"os"
@@ -44,10 +45,11 @@
 	return &Workdir{workdir: dir}
 }
 
-func (w *Workdir) WriteInitialFiles(txt string) error {
+func (w *Workdir) writeInitialFiles(txt string) error {
 	files := unpackTxt(txt)
 	for name, data := range files {
-		if err := w.writeFileData(name, string(data)); err != nil {
+		data = bytes.ReplaceAll(data, []byte("$SANDBOX_WORKDIR"), []byte(w.workdir))
+		if err := w.writeFileData(name, data); err != nil {
 			return errors.Errorf("writing to workdir: %w", err)
 		}
 	}
@@ -221,7 +223,7 @@
 	} else {
 		changeType = protocol.Changed
 	}
-	if err := w.writeFileData(path, content); err != nil {
+	if err := w.writeFileData(path, []byte(content)); err != nil {
 		return FileEvent{}, err
 	}
 	return FileEvent{
@@ -233,7 +235,7 @@
 	}, nil
 }
 
-func (w *Workdir) writeFileData(path string, content string) error {
+func (w *Workdir) writeFileData(path string, content []byte) error {
 	fp := w.filePath(path)
 	if err := os.MkdirAll(filepath.Dir(fp), 0755); err != nil {
 		return errors.Errorf("creating nested directory: %w", err)
diff --git a/internal/lsp/fake/workdir_test.go b/internal/lsp/fake/workdir_test.go
index 37d37fd..2438f35 100644
--- a/internal/lsp/fake/workdir_test.go
+++ b/internal/lsp/fake/workdir_test.go
@@ -30,7 +30,7 @@
 		t.Fatal(err)
 	}
 	wd := NewWorkdir(tmpdir)
-	if err := wd.WriteInitialFiles(data); err != nil {
+	if err := wd.writeInitialFiles(data); err != nil {
 		t.Fatal(err)
 	}
 	cleanup := func() {
@@ -153,11 +153,11 @@
 	}
 	// Sleep some positive amount of time to ensure a distinct mtime.
 	time.Sleep(100 * time.Millisecond)
-	if err := wd.writeFileData("go.mod", "module foo.test\n"); err != nil {
+	if err := wd.writeFileData("go.mod", []byte("module foo.test\n")); err != nil {
 		t.Fatal(err)
 	}
 	checkChange("go.mod", protocol.Changed)
-	if err := wd.writeFileData("newFile", "something"); err != nil {
+	if err := wd.writeFileData("newFile", []byte("something")); err != nil {
 		t.Fatal(err)
 	}
 	checkChange("newFile", protocol.Created)