1// Copyright 2020 The CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package cmd
16
17import (
18	"bufio"
19	"bytes"
20	"context"
21	"fmt"
22	"io/ioutil"
23	"os"
24	"path"
25	"path/filepath"
26	goruntime "runtime"
27	"strings"
28	"testing"
29
30	"github.com/rogpeppe/go-internal/goproxytest"
31	"github.com/rogpeppe/go-internal/gotooltest"
32	"github.com/rogpeppe/go-internal/testscript"
33	"github.com/rogpeppe/go-internal/txtar"
34
35	"cuelang.org/go/cue/errors"
36	"cuelang.org/go/cue/parser"
37	"cuelang.org/go/internal/cuetest"
38)
39
40const (
41	homeDirName = ".user-home"
42)
43
44// TestLatest checks that the examples match the latest language standard,
45// even if still valid in backwards compatibility mode.
46func TestLatest(t *testing.T) {
47	root := filepath.Join("testdata", "script")
48	filepath.Walk(root, func(fullpath string, info os.FileInfo, err error) error {
49		if !strings.HasSuffix(fullpath, ".txt") ||
50			strings.HasPrefix(filepath.Base(fullpath), "fix") {
51			return nil
52		}
53
54		a, err := txtar.ParseFile(fullpath)
55		if err != nil {
56			t.Error(err)
57			return nil
58		}
59		if bytes.HasPrefix(a.Comment, []byte("!")) {
60			return nil
61		}
62
63		for _, f := range a.Files {
64			t.Run(path.Join(fullpath, f.Name), func(t *testing.T) {
65				if !strings.HasSuffix(f.Name, ".cue") {
66					return
67				}
68				v := parser.FromVersion(parser.Latest)
69				_, err := parser.ParseFile(f.Name, f.Data, v)
70				if err != nil {
71					w := &bytes.Buffer{}
72					fmt.Fprintf(w, "\n%s:\n", fullpath)
73					errors.Print(w, err, nil)
74					t.Error(w.String())
75				}
76			})
77		}
78		return nil
79	})
80}
81
82func TestScript(t *testing.T) {
83	srv, err := goproxytest.NewServer(filepath.Join("testdata", "mod"), "")
84	if err != nil {
85		t.Fatalf("cannot start proxy: %v", err)
86	}
87	p := testscript.Params{
88		Dir:           filepath.Join("testdata", "script"),
89		UpdateScripts: cuetest.UpdateGoldenFiles,
90		Setup: func(e *testscript.Env) error {
91			// Set up a home dir within work dir with a . prefix so that the
92			// Go/CUE pattern ./... does not descend into it.
93			home := filepath.Join(e.WorkDir, homeDirName)
94			if err := os.Mkdir(home, 0777); err != nil {
95				return err
96			}
97
98			e.Vars = append(e.Vars,
99				"GOPROXY="+srv.URL,
100				"GONOSUMDB=*", // GOPROXY is a private proxy
101				homeEnvName()+"="+home,
102			)
103			return nil
104		},
105		Condition: cuetest.Condition,
106	}
107	if err := gotooltest.Setup(&p); err != nil {
108		t.Fatal(err)
109	}
110	testscript.Run(t, p)
111}
112
113// TestScriptDebug takes a single testscript file and then runs it within the
114// same process so it can be used for debugging. It runs the first cue command
115// it finds.
116//
117// Usage Comment out t.Skip() and set file to test.
118func TestX(t *testing.T) {
119	t.Skip()
120	const path = "./testdata/script/eval_e.txt"
121
122	check := func(err error) {
123		t.Helper()
124		if err != nil {
125			t.Fatal(err)
126		}
127	}
128
129	tmpdir, err := ioutil.TempDir("", "cue-script")
130	check(err)
131	defer os.Remove(tmpdir)
132
133	a, err := txtar.ParseFile(filepath.FromSlash(path))
134	check(err)
135
136	for _, f := range a.Files {
137		name := filepath.Join(tmpdir, f.Name)
138		check(os.MkdirAll(filepath.Dir(name), 0777))
139		check(ioutil.WriteFile(name, f.Data, 0666))
140	}
141
142	cwd, err := os.Getwd()
143	check(err)
144	defer func() { _ = os.Chdir(cwd) }()
145	_ = os.Chdir(tmpdir)
146
147	for s := bufio.NewScanner(bytes.NewReader(a.Comment)); s.Scan(); {
148		cmd := s.Text()
149		cmd = strings.TrimLeft(cmd, "! ")
150		if strings.HasPrefix(cmd, "exec ") {
151			cmd = cmd[len("exec "):]
152		}
153		if !strings.HasPrefix(cmd, "cue ") {
154			continue
155		}
156
157		// TODO: Ugly hack to get args. Do more principled parsing.
158		var args []string
159		for _, a := range strings.Split(cmd, " ")[1:] {
160			args = append(args, strings.Trim(a, "'"))
161		}
162
163		c, err := New(args)
164		check(err)
165		b := &bytes.Buffer{}
166		c.SetOutput(b)
167		err = c.Run(context.Background())
168		// Always create an error to show
169		t.Error(err, "\n", b.String())
170		return
171	}
172	t.Fatal("NO COMMAND FOUND")
173}
174
175func TestMain(m *testing.M) {
176	// Setting inTest causes filenames printed in error messages
177	// to be normalized so the output looks the same on Unix
178	// as Windows.
179	os.Exit(testscript.RunMain(m, map[string]func() int{
180		"cue": MainTest,
181	}))
182}
183
184// homeEnvName extracts the logic from os.UserHomeDir to get the
185// name of the environment variable that should be used when
186// seting the user's home directory
187func homeEnvName() string {
188	switch goruntime.GOOS {
189	case "windows":
190		return "USERPROFILE"
191	case "plan9":
192		return "home"
193	default:
194		return "HOME"
195	}
196}
197