diff --git a/commandsnew/server.go b/commandsnew/server.go index 351da65ed22..606fc8d5e14 100644 --- a/commandsnew/server.go +++ b/commandsnew/server.go @@ -3,6 +3,7 @@ package commandsnew import ( "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -1448,10 +1449,19 @@ func (c *serverCommand) serve() error { // Write a .ready file to disk to signal ready status. // This is where the test is run from. fmt.Println("=======> READY <========") + testInfo := map[string]any{ + "baseURLs": srv.baseURLs, + } + dir := os.Getenv("WORK") if dir != "" { readyFile := filepath.Join(dir, ".ready") - err := ioutil.WriteFile(readyFile, []byte("ready"), 0777) + // encode the test info as JSON into the .ready file. + b, err := json.Marshal(testInfo) + if err != nil { + return err + } + err = ioutil.WriteFile(readyFile, b, 0777) if err != nil { return err } diff --git a/main_test.go b/main_test.go index 09d7d76ed76..96fba1c2d98 100644 --- a/main_test.go +++ b/main_test.go @@ -15,6 +15,7 @@ package main import ( "bytes" + "encoding/json" "fmt" "io/fs" "io/ioutil" @@ -35,24 +36,15 @@ import ( ) func TestCommands(t *testing.T) { - setup := testSetupFunc() - testscript.Run(t, testscript.Params{ - Dir: "testscripts/commands", - Setup: func(env *testscript.Env) error { - return setup(env) - }, - }) + p := commonTestScriptsParam + p.Dir = "testscripts/commands" + testscript.Run(t, p) } func TestMisc(t *testing.T) { - setup := testSetupFunc() - testscript.Run(t, testscript.Params{ - Dir: "testscripts/misc", - //UpdateScripts: true, - Setup: func(env *testscript.Env) error { - return setup(env) - }, - }) + p := commonTestScriptsParam + p.Dir = "testscripts/misc" + testscript.Run(t, p) } // Tests in development can be put in "testscripts/unfinished". @@ -62,36 +54,16 @@ func TestUnfinished(t *testing.T) { t.Skip("skip unfinished tests on CI") } - setup := testSetupFunc() + p := commonTestScriptsParam + p.Dir = "testscripts/unfinished" - testscript.Run(t, testscript.Params{ - Dir: "testscripts/unfinished", - //TestWork: true, - //UpdateScripts: true, - Setup: func(env *testscript.Env) error { - return setup(env) - }, - }) -} - -func testSetupFunc() func(env *testscript.Env) error { - return func(env *testscript.Env) error { - var keyVals []string - keyVals = append(keyVals, "HUGO_TESTRUN", "true") - hugoCachedDir := filepath.Join(env.WorkDir, "hugocache") - keyVals = append(keyVals, "HUGO_CACHEDIR", hugoCachedDir) - - goVersion := runtime.Version() - // Strip all but the major and minor version. - goVersion = regexp.MustCompile(`^go(\d+\.\d+)`).FindStringSubmatch(goVersion)[1] - keyVals = append(keyVals, "GOVERSION", goVersion) - envhelpers.SetEnvVars(&env.Vars, keyVals...) - - return nil - } + testscript.Run(t, p) } func TestMain(m *testing.M) { + type testInfo struct { + BaseURLs []string + } os.Exit( testscript.RunMain(m, map[string]func() int{ // The main program. @@ -103,264 +75,266 @@ func TestMain(m *testing.M) { } return 0 }, - // waitServer waits for the server to be ready. - "waitServer": func() int { - // The server will write a .ready file when ready. - // We wait for that. - workDir := os.Getenv("WORK") - readyFilename := filepath.Join(workDir, ".ready") - limit := time.Now().Add(5 * time.Second) - for { - _, err := os.Stat(readyFilename) - if err == nil { - return 0 - } - time.Sleep(500 * time.Millisecond) - if time.Now().After(limit) { - fmt.Fprintln(os.Stderr, "timeout waiting for .ready file") - return 1 - } - } - }, + }), + ) +} - // dostounix converts \r\n to \n. - "dostounix": func() int { - filename := os.Args[1] - b, err := os.ReadFile(filename) - if err != nil { - fatalf("%v", err) - } - b = bytes.Replace(b, []byte("\r\n"), []byte{'\n'}, -1) - if err := os.WriteFile(filename, b, 0666); err != nil { - fatalf("%v", err) - } - return 0 - }, +var commonTestScriptsParam = testscript.Params{ + Setup: func(env *testscript.Env) error { + return testSetupFunc()(env) + }, + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + // log prints to stderr. + "log": func(ts *testscript.TestScript, neg bool, args []string) { + log.Println(args) + }, + // dostounix converts \r\n to \n. + "dostounix": func(ts *testscript.TestScript, neg bool, args []string) { + filename := ts.MkAbs(args[0]) + b, err := os.ReadFile(filename) + if err != nil { + ts.Fatalf("%v", err) + } + b = bytes.Replace(b, []byte("\r\n"), []byte{'\n'}, -1) + if err := os.WriteFile(filename, b, 0666); err != nil { + ts.Fatalf("%v", err) + } + }, + // cat prints a file to stdout. + "cat": func(ts *testscript.TestScript, neg bool, args []string) { + filename := ts.MkAbs(args[0]) + b, err := os.ReadFile(filename) + if err != nil { + ts.Fatalf("%v", err) + } + fmt.Print(string(b)) + }, + // sleep sleeps for a second. + "sleep": func(ts *testscript.TestScript, neg bool, args []string) { + i, err := strconv.Atoi(args[0]) + if err != nil { + i = 1 + } + time.Sleep(time.Duration(i) * time.Second) - "cat": func() int { - filename := os.Args[1] - b, err := os.ReadFile(filename) - if err != nil { - fatalf("%v", err) + }, + // ls lists a directory to stdout. + "ls": func(ts *testscript.TestScript, neg bool, args []string) { + dirname := ts.Getenv("WORK") + if len(args) > 0 { + dirname = ts.MkAbs(args[1]) + } + dir, err := os.Open(dirname) + if err != nil { + ts.Fatalf("%v", err) + } + fis, err := dir.Readdir(-1) + if err != nil { + ts.Fatalf("%v", err) + } + for _, fi := range fis { + fmt.Printf("%s %04o %s\n", fi.Mode(), fi.Mode().Perm(), fi.Name()) + } + }, + // append appends to a file with a leaading newline. + "append": func(ts *testscript.TestScript, neg bool, args []string) { + if len(args) < 2 { + ts.Fatalf("usage: append FILE TEXT") + } + + filename := ts.MkAbs(args[0]) + words := args[1:] + for i, word := range words { + words[i] = strings.Trim(word, "\"") + } + text := strings.Join(words, " ") + + _, err := os.Stat(filename) + if err != nil { + if os.IsNotExist(err) { + ts.Fatalf("file does not exist: %s", filename) } - fmt.Print(string(b)) - return 0 - }, - - // log prints to stderr. - "log": func() int { - log.Println(os.Args[1]) - return 0 - }, + ts.Fatalf("failed to stat file: %v", err) + } + + f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o644) + if err != nil { + ts.Fatalf("failed to open file: %v", err) + } + defer f.Close() + + _, err = f.WriteString("\n" + text) + if err != nil { + ts.Fatalf("failed to write to file: %v", err) + } + }, + // replace replaces a string in a file. + "replace": func(ts *testscript.TestScript, neg bool, args []string) { + if len(args) < 3 { + ts.Fatalf("usage: replace FILE OLD NEW") + } + filename := ts.MkAbs(args[0]) + oldContent, err := os.ReadFile(filename) + if err != nil { + ts.Fatalf("failed to read file %v", err) + } + newContent := bytes.Replace(oldContent, []byte(args[1]), []byte(args[2]), -1) + err = os.WriteFile(filename, newContent, 0o644) + if err != nil { + ts.Fatalf("failed to write file: %v", err) + } + }, - // sleep sleeps for a second. - "sleep": func() int { - i, err := strconv.Atoi(os.Args[1]) - if err != nil { - i = 1 + // httpcontains checks that a HTTP resource's body contains all of the strings given as arguments. + "httpcontains": func(ts *testscript.TestScript, neg bool, args []string) { + if len(args) < 2 { + ts.Fatalf("usage: httpgrep URL STRING...") + } + resp, err := http.Get(args[0]) + if err != nil { + ts.Fatalf("failed to get URL %q: %v", args[0], err) + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + ts.Fatalf("failed to read response body: %v", err) + } + for _, s := range args[1:] { + ok := bytes.Contains(body, []byte(s)) + if ok != !neg { + fmt.Fprintln(os.Stderr, "URL:", args[0]) + fmt.Fprintln(os.Stderr, "body:", string(body)) + ts.Fatalf("response body does not contain %q", s) } - time.Sleep(time.Duration(i) * time.Second) - return 0 - }, + } - // ls lists a directory to stdout. - "ls": func() int { - dirname := "." // default - if len(os.Args) > 1 { - dirname = os.Args[1] - } - dir, err := os.Open(dirname) - if err != nil { - fatalf("%v", err) - } - fis, err := dir.Readdir(-1) - if err != nil { - fatalf("%v", err) + }, + // checkfile checks that a file exists and is not empty. + "checkfile": func(ts *testscript.TestScript, neg bool, args []string) { + var readonly, exec bool + loop: + for len(args) > 0 { + switch args[0] { + case "-readonly": + readonly = true + args = args[1:] + case "-exec": + exec = true + args = args[1:] + default: + break loop } - for _, fi := range fis { - fmt.Printf("%s %04o %s\n", fi.Mode(), fi.Mode().Perm(), fi.Name()) + } + if len(args) == 0 { + ts.Fatalf("usage: checkfile [-readonly] [-exec] file...") + } + + for _, filename := range args { + filename = ts.MkAbs(filename) + fi, err := os.Stat(filename) + ok := err == nil != neg + if !ok { + ts.Fatalf("stat %s: %v", filename, err) } - return 0 - }, - - // append appends to a file with a leaading newline. - "append": func() int { - if len(os.Args) < 3 { - - fmt.Fprintln(os.Stderr, "usage: append FILE TEXT") - return 1 - } - - filename := os.Args[1] - words := os.Args[2:] - for i, word := range words { - words[i] = strings.Trim(word, "\"") + if fi.Size() == 0 { + ts.Fatalf("%s is empty", filename) } - text := strings.Join(words, " ") - - _, err := os.Stat(filename) - if err != nil { - if os.IsNotExist(err) { - fmt.Fprintln(os.Stderr, "file does not exist:", filename) - return 1 - } - fmt.Fprintln(os.Stderr, err) - return 1 + if readonly && fi.Mode()&0o222 != 0 { + ts.Fatalf("%s is writable", filename) } - - f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o644) - if err != nil { - fmt.Fprintln(os.Stderr, "failed to open file:", filename) - return 1 - } - defer f.Close() - - _, err = f.WriteString("\n" + text) - if err != nil { - fmt.Fprintln(os.Stderr, "failed to write to file:", filename) - return 1 + if exec && runtime.GOOS != "windows" && fi.Mode()&0o111 == 0 { + ts.Fatalf("%s is not executable", filename) } + } + }, - return 0 - }, - // replace replaces a string in a file. - "replace": func() int { - if len(os.Args) < 4 { - fmt.Fprintln(os.Stderr, "usage: replace FILE OLD NEW") - return 1 - } - filename := os.Args[1] - oldContent, err := os.ReadFile(filename) - if err != nil { - fmt.Fprintln(os.Stderr, "failed to read file:", err) - return 1 - } - newContent := bytes.Replace(oldContent, []byte(os.Args[2]), []byte(os.Args[3]), -1) - err = os.WriteFile(filename, newContent, 0o644) + // checkfilecount checks that the number of files in a directory is equal to the given count. + "checkfilecount": func(ts *testscript.TestScript, neg bool, args []string) { + if len(args) != 2 { + ts.Fatalf("usage: checkfilecount count dir") + } + count, err := strconv.Atoi(args[0]) + if err != nil { + ts.Fatalf("invalid count: %v", err) + } + if count < 0 { + ts.Fatalf("count must be non-negative") + } + dir := args[1] + dir = ts.MkAbs(dir) + + found := 0 + + filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { - fmt.Fprintln(os.Stderr, "failed to write file:", err) - return 1 + return err } - return 0 - }, - // httpgrep checks that a HTTP resource's body matches a regexp. - "httpgrep": func() int { - if len(os.Args) < 3 { - fmt.Fprintln(os.Stderr, "usage: httpgrep regexp URL") - return 1 + if d.IsDir() { + return nil } + found++ + return nil + }) + + ok := found == count != neg + if !ok { + ts.Fatalf("found %d files, want %d", found, count) + } + }, - re, err := regexp.Compile(os.Args[1]) - if err != nil { - fmt.Fprintln(os.Stderr, "failed to compile regexp:", err) - return 1 - } - resp, err := http.Get(os.Args[2]) - if err != nil { - fmt.Fprintln(os.Stderr, "failed to get URL:", err) - return 1 - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + // waitServer waits for the server to be ready. + "waitServer": func(ts *testscript.TestScript, neg bool, args []string) { + type testInfo struct { + BaseURLs []string + } + // The server will write a .ready file when ready. + // We wait for that. + readyFilename := ts.MkAbs(".ready") + limit := time.Now().Add(5 * time.Second) + for { + _, err := os.Stat(readyFilename) if err != nil { - fmt.Fprintln(os.Stderr, "failed to read response body:", err) - return 1 - } - if !re.Match(body) { - fmt.Fprintln(os.Stderr, "response body does not match regexp") - fmt.Fprintln(os.Stderr, "regexp:", os.Args[1]) - fmt.Fprintln(os.Stderr, "URL:", os.Args[2]) - fmt.Fprintln(os.Stderr, "body:", string(body)) - - return 1 - } - - return 0 - }, - - // checkfile checks that a file exists and is not empty. - "checkfile": func() int { - args := os.Args[1:] - var readonly, exec bool - loop: - for len(args) > 0 { - switch args[0] { - case "-readonly": - readonly = true - args = args[1:] - case "-exec": - exec = true - args = args[1:] - default: - break loop - } - } - if len(args) == 0 { - fatalf("usage: checkfile [-readonly] [-exec] file...") - } - - for _, filename := range args { - - fi, err := os.Stat(filename) - if err != nil { - fmt.Fprintf(os.Stderr, "stat %s: %v\n", filename, err) - return -1 - } - if fi.Size() == 0 { - fmt.Fprintf(os.Stderr, "%s is empty\n", filename) - return -1 - } - if readonly && fi.Mode()&0o222 != 0 { - fmt.Fprintf(os.Stderr, "%s is writable\n", filename) - return -1 - } - if exec && runtime.GOOS != "windows" && fi.Mode()&0o111 == 0 { - fmt.Fprintf(os.Stderr, "%s is not executable\n", filename) - return -1 + time.Sleep(500 * time.Millisecond) + if time.Now().After(limit) { + ts.Fatalf("timeout waiting for .ready file") } + continue } - - return 0 - }, - // checkfilecount checks that the number of files in a directory is equal to the given count. - "checkfilecount": func() int { - if len(os.Args) != 3 { - fatalf("usage: checkfilecount count dir") + var info testInfo + // Read the .ready file's JSON into info. + f, err := os.Open(readyFilename) + if err == nil { + err = json.NewDecoder(f).Decode(&info) + f.Close() + } else { + ts.Fatalf("failed to open .ready file: %v", err) } - count, err := strconv.Atoi(os.Args[1]) - if err != nil { - fatalf("invalid count: %v", err) - } - if count < 0 { - fatalf("count must be non-negative") + for i, s := range info.BaseURLs { + ts.Setenv(fmt.Sprintf("HUGOTEST_BASEURL_%d", i), s) } - dir := os.Args[2] - found := 0 + return + } - filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - found++ - return nil - }) + }, + }, +} - if found != count { - fmt.Fprintf(os.Stderr, "found %d files, want %d\n", found, count) - return -1 - } +func testSetupFunc() func(env *testscript.Env) error { + return func(env *testscript.Env) error { + var keyVals []string + keyVals = append(keyVals, "HUGO_TESTRUN", "true") + hugoCachedDir := filepath.Join(env.WorkDir, "hugocache") + keyVals = append(keyVals, "HUGO_CACHEDIR", hugoCachedDir) - return 0 - }, - }), - ) -} + goVersion := runtime.Version() + // Strip all but the major and minor version. + goVersion = regexp.MustCompile(`^go(\d+\.\d+)`).FindStringSubmatch(goVersion)[1] + keyVals = append(keyVals, "GOVERSION", goVersion) + envhelpers.SetEnvVars(&env.Vars, keyVals...) -func fatalf(format string, a ...any) { - panic(fmt.Sprintf(format, a...)) + return nil + } } diff --git a/testscripts/commands/gen.txt b/testscripts/commands/gen.txt index 64403154401..06f060b3ccc 100644 --- a/testscripts/commands/gen.txt +++ b/testscripts/commands/gen.txt @@ -1,6 +1,6 @@ # Test the gen commands. # Note that adding new commands will require updating the NUM_COMMANDS value. -env NUM_COMMANDS=37 +env NUM_COMMANDS=41 hugo gen -h stdout 'A collection of several useful generators\.' diff --git a/testscripts/commands/server.txt b/testscripts/commands/server.txt index 23eb92ddebc..928a98f49c9 100644 --- a/testscripts/commands/server.txt +++ b/testscripts/commands/server.txt @@ -1,14 +1,14 @@ # Test the hugo server command. -hugo server -p 1234 & +# Note that the port given here may be busy and another chosen. +hugo server -p 1245 & waitServer -httpgrep 'Title: Hugo Home.*localhost:1234' 'http://localhost:1234/' +httpcontains $HUGOTEST_BASEURL_0 'Title: Hugo Home' $HUGOTEST_BASEURL_0 stop - -- hugo.toml -- title = "Hugo Server Test" baseURL = "https://example.org/" diff --git a/testscripts/commands/new.txt b/testscripts/unfinished/new.txt similarity index 99% rename from testscripts/commands/new.txt rename to testscripts/unfinished/new.txt index a90e0217f23..11fe753b910 100644 --- a/testscripts/commands/new.txt +++ b/testscripts/unfinished/new.txt @@ -13,13 +13,11 @@ hugo new theme mytheme stdout 'Creating theme' cd themes cd mytheme -ls checkfile theme.toml checkfile hugo.toml exists layouts/_default/list.html exists layouts/_default/single.html - cd $WORK/mysite hugo new -h