| // Copyright 2019 CUE Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package kubernetes |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "regexp" |
| "strings" |
| "testing" |
| |
| "github.com/kylelemons/godebug/diff" |
| |
| "cuelang.org/go/cue/load" |
| "cuelang.org/go/internal/copy" |
| "cuelang.org/go/internal/cuetest" |
| ) |
| |
| var ( |
| update = flag.Bool("update", false, "update test data") |
| cleanup = flag.Bool("cleanup", true, "clean up generated files") |
| ) |
| |
| func TestTutorial(t *testing.T) { |
| if testing.Short() { |
| t.Skip() |
| } |
| |
| cwd, err := os.Getwd() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Read the tutorial. |
| b, err := ioutil.ReadFile("README.md") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Copy test data and change the cwd to this directory. |
| dir, err := ioutil.TempDir("", "tutorial") |
| if err != nil { |
| log.Fatal(err) |
| } |
| if *cleanup { |
| defer os.RemoveAll(dir) |
| } else { |
| defer logf(t, "Temporary dir: %v", dir) |
| } |
| |
| wd := filepath.Join(dir, "services") |
| if err := copy.Dir(filepath.Join("original", "services"), wd); err != nil { |
| t.Fatal(err) |
| } |
| |
| cuetest.Run(t, dir, "cue mod init", &cuetest.Config{ |
| // Stdin: strings.NewReader(input), |
| }) |
| |
| if *update { |
| // The test environment won't work in all environments. We create |
| // a fake go.mod so that Go will find the module root. By default |
| // we won't set it. |
| if err := os.Chdir(dir); err != nil { |
| t.Fatal(err) |
| } |
| cmd := exec.Command("go", "mod", "init", "cuelang.org/dummy") |
| b, err := cmd.CombinedOutput() |
| logf(t, string(b)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } else { |
| // We only fetch new kubernetes files with when updating. |
| err := copy.Dir(load.GenPath("quick"), load.GenPath(dir)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| if err := os.Chdir(wd); err != nil { |
| t.Fatal(err) |
| } |
| defer os.Chdir(cwd) |
| logf(t, "Changed to directory: %s", wd) |
| |
| // Execute the tutorial. |
| for c := cuetest.NewChunker(t, b); c.Next("```", "```"); { |
| for c := cuetest.NewChunker(t, c.Bytes()); c.Next("$ ", "\n"); { |
| alt := c.Text() |
| cmd := strings.Replace(alt, "<<EOF", "", -1) |
| |
| input := "" |
| if cmd != alt { |
| if !c.Next("", "EOF") { |
| t.Fatalf("non-terminated <<EOF") |
| } |
| input = c.Text() |
| } |
| |
| redirect := "" |
| if p := strings.Index(cmd, " >"); p > 0 { |
| redirect = cmd[p+1:] |
| cmd = cmd[:p] |
| } |
| |
| logf(t, "$ %s", cmd) |
| switch cmd = strings.TrimSpace(cmd); { |
| case strings.HasPrefix(cmd, "cat"): |
| if input == "" { |
| break |
| } |
| var r *os.File |
| var err error |
| if strings.HasPrefix(redirect, ">>") { |
| // Append input |
| r, err = os.OpenFile( |
| strings.TrimSpace(redirect[2:]), |
| os.O_APPEND|os.O_CREATE|os.O_WRONLY, |
| 0666) |
| } else { // strings.HasPrefix(redirect, ">") |
| // Create new file with input |
| r, err = os.Create(strings.TrimSpace(redirect[1:])) |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| _, err = io.WriteString(r, input) |
| if err := r.Close(); err != nil { |
| t.Fatal(err) |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| case strings.HasPrefix(cmd, "cue "): |
| if strings.HasPrefix(cmd, "cue create") { |
| // Don't execute the kubernetes dry run. |
| break |
| } |
| if strings.HasPrefix(cmd, "cue mod init") { |
| // Already ran this at setup. |
| break |
| } |
| |
| if !*update && strings.HasPrefix(cmd, "cue get") { |
| // Don't fetch stuff in normal mode. |
| break |
| } |
| |
| cuetest.Run(t, wd, cmd, &cuetest.Config{ |
| Stdin: strings.NewReader(input), |
| Stdout: os.Stdout, |
| }) |
| |
| case strings.HasPrefix(cmd, "sed "): |
| c := cuetest.NewChunker(t, []byte(cmd)) |
| c.Next("s/", "/") |
| re := regexp.MustCompile(c.Text()) |
| c.Next("", "/'") |
| repl := c.Bytes() |
| c.Next(" ", ".cue") |
| file := c.Text() + ".cue" |
| b, err := ioutil.ReadFile(file) |
| if err != nil { |
| t.Fatal(err) |
| } |
| b = re.ReplaceAll(b, repl) |
| err = ioutil.WriteFile(file, b, 0644) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| case strings.HasPrefix(cmd, "touch "): |
| logf(t, "$ %s", cmd) |
| file := strings.TrimSpace(cmd[len("touch "):]) |
| err := ioutil.WriteFile(file, []byte(""), 0644) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |
| } |
| |
| if err := os.Chdir(filepath.Join(cwd, "quick")); err != nil { |
| t.Fatal(err) |
| } |
| |
| if *update { |
| // Remove all old cue files. |
| err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { |
| if isCUE(path) { |
| if err := os.Remove(path); err != nil { |
| t.Fatal(err) |
| } |
| } |
| return err |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { |
| if isCUE(path) { |
| dst := path[len(dir)+1:] |
| err := os.MkdirAll(filepath.Dir(dst), 0755) |
| if err != nil { |
| return err |
| } |
| return copy.File(path, dst) |
| } |
| return err |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return |
| } |
| |
| // Compare the output in the temp directory with the quick output. |
| err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { |
| if filepath.Ext(path) != ".cue" { |
| return nil |
| } |
| b1, err := ioutil.ReadFile(path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| b2, err := ioutil.ReadFile(path[len(dir)+1:]) |
| if err != nil { |
| t.Fatal(err) |
| } |
| got, want := string(b1), string(b2) |
| if got != want { |
| t.Log(diff.Diff(got, want)) |
| return fmt.Errorf("file %q differs", path) |
| } |
| return nil |
| }) |
| if err != nil { |
| t.Error(err) |
| } |
| } |
| |
| func isCUE(filename string) bool { |
| return filepath.Ext(filename) == ".cue" && !strings.Contains(filename, "_tool") |
| } |
| |
| func logf(t *testing.T, format string, args ...interface{}) { |
| t.Logf(format, args...) |
| log.Printf(format, args...) |
| } |
| |
| func TestEval(t *testing.T) { |
| for _, dir := range []string{"quick", "manual"} { |
| t.Run(dir, func(t *testing.T) { |
| buf := &bytes.Buffer{} |
| cuetest.Run(t, dir, "cue eval ./...", &cuetest.Config{ |
| Stdout: buf, |
| }) |
| |
| cwd, _ := os.Getwd() |
| pattern := fmt.Sprintf("//.*%s.*", regexp.QuoteMeta(filepath.Join(cwd, dir))) |
| re, err := regexp.Compile(pattern) |
| if err != nil { |
| t.Fatal(err) |
| } |
| got := re.ReplaceAll(buf.Bytes(), []byte{}) |
| got = bytes.TrimSpace(got) |
| |
| testfile := filepath.Join("testdata", dir+".out") |
| |
| if *update { |
| err := ioutil.WriteFile(testfile, got, 0644) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return |
| } |
| |
| b, err := ioutil.ReadFile(testfile) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if got, want := string(got), string(b); got != want { |
| t.Log(got) |
| t.Errorf("output differs for file %s in %s", testfile, cwd) |
| } |
| }) |
| } |
| } |