1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package kubernetes
16
17 import (
18 "bytes"
19 "context"
20 "flag"
21 "fmt"
22 "io"
23 "io/fs"
24 "io/ioutil"
25 "log"
26 "os"
27 "os/exec"
28 "path/filepath"
29 "regexp"
30 "strings"
31 "testing"
32
33 "github.com/google/go-cmp/cmp"
34
35 "cuelang.org/go/cmd/cue/cmd"
36 "cuelang.org/go/cue/load"
37 "cuelang.org/go/internal/copy"
38 "cuelang.org/go/internal/cuetest"
39 )
40
41 var (
42 cleanup = flag.Bool("cleanup", true, "clean up generated files")
43
44 testLong = os.Getenv("CUE_LONG") != ""
45 )
46
47 func TestTutorial(t *testing.T) {
48 if !testLong {
49 t.Skipf("the kubernetes tutorial can easily take half a minute")
50 }
51
52 cwd, err := os.Getwd()
53 if err != nil {
54 t.Fatal(err)
55 }
56
57
58 b, err := os.ReadFile("README.md")
59 if err != nil {
60 t.Fatal(err)
61 }
62
63
64 dir, err := ioutil.TempDir("", "tutorial")
65 if err != nil {
66 log.Fatal(err)
67 }
68 if *cleanup {
69 defer os.RemoveAll(dir)
70 } else {
71 defer logf(t, "Temporary dir: %v", dir)
72 }
73
74 wd := filepath.Join(dir, "services")
75 if err := copy.Dir(filepath.Join("original", "services"), wd); err != nil {
76 t.Fatal(err)
77 }
78
79 run(t, dir, "cue mod init", &config{
80
81 })
82
83 if cuetest.UpdateGoldenFiles {
84
85
86
87 out := execute(t, dir, "go", "mod", "init", "cuelang.org/dummy")
88 logf(t, "%s", out)
89 } else {
90
91 err := copy.Dir(load.GenPath("quick"), load.GenPath(dir))
92 if err != nil {
93 t.Fatal(err)
94 }
95 }
96
97 if err := os.Chdir(wd); err != nil {
98 t.Fatal(err)
99 }
100 defer os.Chdir(cwd)
101 logf(t, "Changed to directory: %s", wd)
102
103
104 for c := cuetest.NewChunker(t, b); c.Next("```", "```"); {
105 for c := cuetest.NewChunker(t, c.Bytes()); c.Next("$ ", "\n"); {
106 alt := c.Text()
107 cmd := strings.Replace(alt, "<<EOF", "", -1)
108
109 input := ""
110 if cmd != alt {
111 if !c.Next("", "EOF") {
112 t.Fatalf("non-terminated <<EOF")
113 }
114 input = c.Text()
115 }
116
117 redirect := ""
118 if p := strings.Index(cmd, " >"); p > 0 {
119 redirect = cmd[p+1:]
120 cmd = cmd[:p]
121 }
122
123 logf(t, "$ %s", cmd)
124 switch cmd = strings.TrimSpace(cmd); {
125 case strings.HasPrefix(cmd, "cat"):
126 if input == "" {
127 break
128 }
129 var r *os.File
130 var err error
131 if strings.HasPrefix(redirect, ">>") {
132
133 r, err = os.OpenFile(
134 strings.TrimSpace(redirect[2:]),
135 os.O_APPEND|os.O_CREATE|os.O_WRONLY,
136 0666)
137 } else {
138
139 r, err = os.Create(strings.TrimSpace(redirect[1:]))
140 }
141 if err != nil {
142 t.Fatal(err)
143 }
144 _, err = io.WriteString(r, input)
145 if err := r.Close(); err != nil {
146 t.Fatal(err)
147 }
148 if err != nil {
149 t.Fatal(err)
150 }
151
152 case strings.HasPrefix(cmd, "cue "):
153 if strings.HasPrefix(cmd, "cue create") {
154
155 break
156 }
157 if strings.HasPrefix(cmd, "cue mod init") {
158
159 break
160 }
161
162 if !cuetest.UpdateGoldenFiles && strings.HasPrefix(cmd, "cue get") {
163
164 break
165 }
166
167 run(t, wd, cmd, &config{
168 Stdin: strings.NewReader(input),
169 Stdout: os.Stdout,
170 })
171
172 case strings.HasPrefix(cmd, "sed "):
173 c := cuetest.NewChunker(t, []byte(cmd))
174 c.Next("s/", "/")
175 re := regexp.MustCompile(c.Text())
176 c.Next("", "/'")
177 repl := c.Bytes()
178 c.Next(" ", ".cue")
179 file := c.Text() + ".cue"
180 b, err := os.ReadFile(file)
181 if err != nil {
182 t.Fatal(err)
183 }
184 b = re.ReplaceAll(b, repl)
185 err = os.WriteFile(file, b, 0644)
186 if err != nil {
187 t.Fatal(err)
188 }
189
190 case strings.HasPrefix(cmd, "touch "):
191 logf(t, "$ %s", cmd)
192 file := strings.TrimSpace(cmd[len("touch "):])
193 err := os.WriteFile(file, []byte(""), 0644)
194 if err != nil {
195 t.Fatal(err)
196 }
197 case strings.HasPrefix(cmd, "go "):
198 if !cuetest.UpdateGoldenFiles && strings.HasPrefix(cmd, "go get") {
199
200 break
201 }
202
203 out := execute(t, wd, splitArgs(t, cmd)...)
204 logf(t, "%s", out)
205 }
206 }
207 }
208
209 if err := os.Chdir(filepath.Join(cwd, "quick")); err != nil {
210 t.Fatal(err)
211 }
212
213 if cuetest.UpdateGoldenFiles {
214
215 err := filepath.WalkDir(".", func(path string, entry fs.DirEntry, err error) error {
216 if isCUE(path) {
217 if err := os.Remove(path); err != nil {
218 t.Fatal(err)
219 }
220 }
221 return err
222 })
223 if err != nil {
224 t.Fatal(err)
225 }
226
227 err = filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error {
228 if filepath.Base(path) == "module.cue" {
229 return nil
230 }
231 if isCUE(path) {
232 dst := path[len(dir)+1:]
233 err := os.MkdirAll(filepath.Dir(dst), 0755)
234 if err != nil {
235 return err
236 }
237 return copy.File(path, dst)
238 }
239 return err
240 })
241 if err != nil {
242 t.Fatal(err)
243 }
244 return
245 }
246
247
248 err = filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error {
249 if err != nil {
250 t.Fatal(err)
251 }
252 if filepath.Base(path) == "module.cue" {
253 return nil
254 }
255 if filepath.Ext(path) != ".cue" {
256 return nil
257 }
258 b1, err := os.ReadFile(path)
259 if err != nil {
260 t.Fatal(err)
261 }
262 b2, err := os.ReadFile(path[len(dir)+1:])
263 if err != nil {
264 t.Fatal(err)
265 }
266 got, want := string(b1), string(b2)
267 if got != want {
268 t.Log(cmp.Diff(got, want))
269 return fmt.Errorf("file %q differs", path)
270 }
271 return nil
272 })
273 if err != nil {
274 t.Error(err)
275 }
276 }
277
278 func isCUE(filename string) bool {
279 return filepath.Ext(filename) == ".cue" && !strings.Contains(filename, "_tool")
280 }
281
282 func TestEval(t *testing.T) {
283 if !testLong {
284 t.Skipf("the kubernetes tutorial can easily take half a minute")
285 }
286
287 for _, dir := range []string{"quick", "manual"} {
288 t.Run(dir, func(t *testing.T) {
289 buf := &bytes.Buffer{}
290 run(t, dir, "cue eval ./...", &config{
291 Stdout: buf,
292 })
293
294 cwd, _ := os.Getwd()
295 pattern := fmt.Sprintf("//.*%s.*", regexp.QuoteMeta(filepath.Join(cwd, dir)))
296 re, err := regexp.Compile(pattern)
297 if err != nil {
298 t.Fatal(err)
299 }
300 got := re.ReplaceAll(buf.Bytes(), []byte{})
301 got = bytes.TrimSpace(got)
302
303 testfile := filepath.Join("testdata", dir+".out")
304
305 if cuetest.UpdateGoldenFiles {
306 err := os.WriteFile(testfile, got, 0644)
307 if err != nil {
308 t.Fatal(err)
309 }
310 return
311 }
312
313 b, err := os.ReadFile(testfile)
314 if err != nil {
315 t.Fatal(err)
316 }
317
318 if got, want := string(got), string(b); got != want {
319 t.Log(got)
320 t.Errorf("output differs for file %s in %s", testfile, cwd)
321 }
322 })
323 }
324 }
325
326 type config struct {
327 Stdin io.Reader
328 Stdout io.Writer
329 Golden string
330 }
331
332
333 func execute(t *testing.T, dir string, args ...string) string {
334 old, err := os.Getwd()
335 if err != nil {
336 t.Fatal(err)
337 }
338 if err = os.Chdir(dir); err != nil {
339 t.Fatal(err)
340 }
341 defer func() { os.Chdir(old) }()
342
343 logf(t, "Executing command: %s", strings.Join(args, " "))
344
345 cmd := exec.Command(args[0], args[1:]...)
346 out, err := cmd.CombinedOutput()
347 if err != nil {
348 t.Fatalf("failed to run [%v] in %s: %v\n%s", cmd, dir, err, out)
349 }
350 return string(out)
351 }
352
353
354
355 func run(t *testing.T, dir, command string, cfg *config) {
356 if cfg == nil {
357 cfg = &config{}
358 }
359
360 old, err := os.Getwd()
361 if err != nil {
362 t.Fatal(err)
363 }
364 if err = os.Chdir(dir); err != nil {
365 t.Fatal(err)
366 }
367 defer func() { os.Chdir(old) }()
368
369 logf(t, "Executing command: %s", command)
370
371 command = strings.TrimSpace(command[4:])
372 args := splitArgs(t, command)
373 logf(t, "Args: %q", args)
374
375 buf := &bytes.Buffer{}
376 if cfg.Golden != "" {
377 if cfg.Stdout != nil {
378 t.Fatal("cannot set Golden and Stdout")
379 }
380 cfg.Stdout = buf
381 }
382 cmd, _ := cmd.New(args)
383 if cfg.Stdout != nil {
384 cmd.SetOutput(cfg.Stdout)
385 } else {
386 cmd.SetOutput(buf)
387 }
388 if cfg.Stdin != nil {
389 cmd.SetInput(cfg.Stdin)
390 }
391 if err := cmd.Run(context.Background()); err != nil {
392 if cfg.Stdout == nil {
393 logf(t, "Output:\n%s", buf.String())
394 }
395 logf(t, "Execution failed: %v", err)
396 }
397
398 if cfg.Golden == "" {
399 return
400 }
401
402 pattern := fmt.Sprintf("//.*%s.*", regexp.QuoteMeta(dir))
403 re, err := regexp.Compile(pattern)
404 if err != nil {
405 t.Fatal(err)
406 }
407 got := re.ReplaceAllString(buf.String(), "")
408 got = strings.TrimSpace(got)
409
410 want := strings.TrimSpace(cfg.Golden)
411 if got != want {
412 t.Errorf("files differ:\n%s", cmp.Diff(got, want))
413 }
414 }
415
416 func logf(t *testing.T, format string, args ...interface{}) {
417 t.Helper()
418 t.Logf(format, args...)
419 }
420
421 func splitArgs(t *testing.T, s string) (args []string) {
422 c := cuetest.NewChunker(t, []byte(s))
423 for {
424 found := c.Find(" '")
425 args = append(args, strings.Split(c.Text(), " ")...)
426 if !found {
427 break
428 }
429 c.Next("", "' ")
430 args = append(args, c.Text())
431 }
432 return args
433 }
434
View as plain text