github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/main_test.go (about) 1 // Copyright 2023 The Hugo Authors. All rights reserved. 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 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package main 15 16 import ( 17 "bytes" 18 "encoding/json" 19 "fmt" 20 "io" 21 "io/fs" 22 "log" 23 "net/http" 24 "os" 25 "path/filepath" 26 "regexp" 27 "runtime" 28 "strconv" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/bep/helpers/envhelpers" 34 "github.com/gohugoio/hugo/commands" 35 "github.com/rogpeppe/go-internal/testscript" 36 ) 37 38 func TestCommands(t *testing.T) { 39 p := commonTestScriptsParam 40 p.Dir = "testscripts/commands" 41 testscript.Run(t, p) 42 } 43 44 // Tests in development can be put in "testscripts/unfinished". 45 // Also see the watch_testscripts.sh script. 46 func TestUnfinished(t *testing.T) { 47 if os.Getenv("CI") != "" { 48 t.Skip("skip unfinished tests on CI") 49 } 50 51 p := commonTestScriptsParam 52 p.Dir = "testscripts/unfinished" 53 //p.UpdateScripts = true 54 55 testscript.Run(t, p) 56 } 57 58 func TestMain(m *testing.M) { 59 type testInfo struct { 60 BaseURLs []string 61 } 62 os.Exit( 63 testscript.RunMain(m, map[string]func() int{ 64 // The main program. 65 "hugo": func() int { 66 err := commands.Execute(os.Args[1:]) 67 if err != nil { 68 fmt.Fprintln(os.Stderr, err) 69 return 1 70 } 71 return 0 72 }, 73 }), 74 ) 75 } 76 77 var commonTestScriptsParam = testscript.Params{ 78 Setup: func(env *testscript.Env) error { 79 return testSetupFunc()(env) 80 }, 81 Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ 82 // log prints to stderr. 83 "log": func(ts *testscript.TestScript, neg bool, args []string) { 84 log.Println(args) 85 }, 86 // dostounix converts \r\n to \n. 87 "dostounix": func(ts *testscript.TestScript, neg bool, args []string) { 88 filename := ts.MkAbs(args[0]) 89 b, err := os.ReadFile(filename) 90 if err != nil { 91 ts.Fatalf("%v", err) 92 } 93 b = bytes.Replace(b, []byte("\r\n"), []byte{'\n'}, -1) 94 if err := os.WriteFile(filename, b, 0666); err != nil { 95 ts.Fatalf("%v", err) 96 } 97 }, 98 // cat prints a file to stdout. 99 "cat": func(ts *testscript.TestScript, neg bool, args []string) { 100 filename := ts.MkAbs(args[0]) 101 b, err := os.ReadFile(filename) 102 if err != nil { 103 ts.Fatalf("%v", err) 104 } 105 fmt.Print(string(b)) 106 }, 107 // sleep sleeps for a second. 108 "sleep": func(ts *testscript.TestScript, neg bool, args []string) { 109 i := 1 110 if len(args) > 0 { 111 var err error 112 i, err = strconv.Atoi(args[0]) 113 if err != nil { 114 i = 1 115 } 116 } 117 time.Sleep(time.Duration(i) * time.Second) 118 119 }, 120 // ls lists a directory to stdout. 121 "ls": func(ts *testscript.TestScript, neg bool, args []string) { 122 var dirname string 123 if len(args) > 0 { 124 dirname = args[0] 125 } 126 dirname = ts.MkAbs(args[0]) 127 128 dir, err := os.Open(dirname) 129 if err != nil { 130 ts.Fatalf("%v", err) 131 } 132 fis, err := dir.Readdir(-1) 133 if err != nil { 134 ts.Fatalf("%v", err) 135 } 136 if len(fis) == 0 { 137 // To simplify empty dir checks. 138 fmt.Fprintln(ts.Stdout(), "Empty dir") 139 return 140 } 141 for _, fi := range fis { 142 fmt.Fprintf(ts.Stdout(), "%s %04o %s %s\n", fi.Mode(), fi.Mode().Perm(), fi.ModTime().Format(time.RFC3339Nano), fi.Name()) 143 } 144 }, 145 // append appends to a file with a leaading newline. 146 "append": func(ts *testscript.TestScript, neg bool, args []string) { 147 if len(args) < 2 { 148 ts.Fatalf("usage: append FILE TEXT") 149 } 150 151 filename := ts.MkAbs(args[0]) 152 words := args[1:] 153 for i, word := range words { 154 words[i] = strings.Trim(word, "\"") 155 } 156 text := strings.Join(words, " ") 157 158 _, err := os.Stat(filename) 159 if err != nil { 160 if os.IsNotExist(err) { 161 ts.Fatalf("file does not exist: %s", filename) 162 } 163 ts.Fatalf("failed to stat file: %v", err) 164 } 165 166 f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o644) 167 if err != nil { 168 ts.Fatalf("failed to open file: %v", err) 169 } 170 defer f.Close() 171 172 _, err = f.WriteString("\n" + text) 173 if err != nil { 174 ts.Fatalf("failed to write to file: %v", err) 175 } 176 }, 177 // replace replaces a string in a file. 178 "replace": func(ts *testscript.TestScript, neg bool, args []string) { 179 if len(args) < 3 { 180 ts.Fatalf("usage: replace FILE OLD NEW") 181 } 182 filename := ts.MkAbs(args[0]) 183 oldContent, err := os.ReadFile(filename) 184 if err != nil { 185 ts.Fatalf("failed to read file %v", err) 186 } 187 newContent := bytes.Replace(oldContent, []byte(args[1]), []byte(args[2]), -1) 188 err = os.WriteFile(filename, newContent, 0o644) 189 if err != nil { 190 ts.Fatalf("failed to write file: %v", err) 191 } 192 }, 193 194 // httpget checks that a HTTP resource's body matches (if it compiles as a regexp) or contains all of the strings given as arguments. 195 "httpget": func(ts *testscript.TestScript, neg bool, args []string) { 196 if len(args) < 2 { 197 ts.Fatalf("usage: httpgrep URL STRING...") 198 } 199 200 tryget := func() error { 201 resp, err := http.Get(args[0]) 202 if err != nil { 203 return fmt.Errorf("failed to get URL %q: %v", args[0], err) 204 } 205 206 defer resp.Body.Close() 207 body, err := io.ReadAll(resp.Body) 208 if err != nil { 209 return fmt.Errorf("failed to read response body: %v", err) 210 } 211 for _, s := range args[1:] { 212 re, err := regexp.Compile(s) 213 if err == nil { 214 ok := re.Match(body) 215 if ok != !neg { 216 return fmt.Errorf("response body %q for URL %q does not match %q", body, args[0], s) 217 } 218 } else { 219 ok := bytes.Contains(body, []byte(s)) 220 if ok != !neg { 221 return fmt.Errorf("response body %q for URL %q does not contain %q", body, args[0], s) 222 } 223 } 224 } 225 return nil 226 227 } 228 229 // The timing on server rebuilds can be a little tricky to get right, 230 // so we try again a few times until the server is ready. 231 // There may be smarter ways to do this, but this works. 232 start := time.Now() 233 for { 234 time.Sleep(200 * time.Millisecond) 235 err := tryget() 236 if err == nil { 237 return 238 } 239 if time.Since(start) > 6*time.Second { 240 ts.Fatalf("timeout waiting for %q: %v", args[0], err) 241 } 242 } 243 }, 244 // checkfile checks that a file exists and is not empty. 245 "checkfile": func(ts *testscript.TestScript, neg bool, args []string) { 246 var readonly, exec bool 247 loop: 248 for len(args) > 0 { 249 switch args[0] { 250 case "-readonly": 251 readonly = true 252 args = args[1:] 253 case "-exec": 254 exec = true 255 args = args[1:] 256 default: 257 break loop 258 } 259 } 260 if len(args) == 0 { 261 ts.Fatalf("usage: checkfile [-readonly] [-exec] file...") 262 } 263 264 for _, filename := range args { 265 filename = ts.MkAbs(filename) 266 fi, err := os.Stat(filename) 267 ok := err == nil != neg 268 if !ok { 269 ts.Fatalf("stat %s: %v", filename, err) 270 } 271 if fi.Size() == 0 { 272 ts.Fatalf("%s is empty", filename) 273 } 274 if readonly && fi.Mode()&0o222 != 0 { 275 ts.Fatalf("%s is writable", filename) 276 } 277 if exec && runtime.GOOS != "windows" && fi.Mode()&0o111 == 0 { 278 ts.Fatalf("%s is not executable", filename) 279 } 280 } 281 }, 282 283 // checkfilecount checks that the number of files in a directory is equal to the given count. 284 "checkfilecount": func(ts *testscript.TestScript, neg bool, args []string) { 285 if len(args) != 2 { 286 ts.Fatalf("usage: checkfilecount count dir") 287 } 288 count, err := strconv.Atoi(args[0]) 289 if err != nil { 290 ts.Fatalf("invalid count: %v", err) 291 } 292 if count < 0 { 293 ts.Fatalf("count must be non-negative") 294 } 295 dir := args[1] 296 dir = ts.MkAbs(dir) 297 298 found := 0 299 300 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { 301 if err != nil { 302 return err 303 } 304 if d.IsDir() { 305 return nil 306 } 307 found++ 308 return nil 309 }) 310 311 ok := found == count != neg 312 if !ok { 313 ts.Fatalf("found %d files, want %d", found, count) 314 } 315 }, 316 // waitServer waits for the .ready file to be created by the server. 317 "waitServer": func(ts *testscript.TestScript, neg bool, args []string) { 318 type testInfo struct { 319 BaseURLs []string 320 } 321 322 // The server will write a .ready file when ready. 323 // We wait for that. 324 readyFilename := ts.MkAbs(".ready") 325 limit := time.Now().Add(5 * time.Second) 326 for { 327 _, err := os.Stat(readyFilename) 328 if err != nil { 329 time.Sleep(500 * time.Millisecond) 330 if time.Now().After(limit) { 331 ts.Fatalf("timeout waiting for .ready file") 332 } 333 continue 334 } 335 var info testInfo 336 // Read the .ready file's JSON into info. 337 f, err := os.Open(readyFilename) 338 if err == nil { 339 err = json.NewDecoder(f).Decode(&info) 340 f.Close() 341 } else { 342 ts.Fatalf("failed to open .ready file: %v", err) 343 } 344 345 for i, s := range info.BaseURLs { 346 ts.Setenv(fmt.Sprintf("HUGOTEST_BASEURL_%d", i), s) 347 } 348 349 return 350 } 351 352 }, 353 "stopServer": func(ts *testscript.TestScript, neg bool, args []string) { 354 baseURL := ts.Getenv("HUGOTEST_BASEURL_0") 355 if baseURL == "" { 356 ts.Fatalf("HUGOTEST_BASEURL_0 not set") 357 } 358 if !strings.HasSuffix(baseURL, "/") { 359 baseURL += "/" 360 } 361 resp, err := http.Head(baseURL + "__stop") 362 if err != nil { 363 ts.Fatalf("failed to shutdown server: %v", err) 364 } 365 resp.Body.Close() 366 // Allow some time for the server to shut down. 367 time.Sleep(2 * time.Second) 368 369 }, 370 }, 371 } 372 373 func testSetupFunc() func(env *testscript.Env) error { 374 sourceDir, _ := os.Getwd() 375 return func(env *testscript.Env) error { 376 var keyVals []string 377 keyVals = append(keyVals, "HUGO_TESTRUN", "true") 378 keyVals = append(keyVals, "HUGO_CACHEDIR", filepath.Join(env.WorkDir, "hugocache")) 379 xdghome := filepath.Join(env.WorkDir, "xdgcachehome") 380 keyVals = append(keyVals, "XDG_CACHE_HOME", xdghome) 381 home := filepath.Join(env.WorkDir, "home") 382 keyVals = append(keyVals, "HOME", home) 383 384 if runtime.GOOS == "darwin" { 385 if err := os.MkdirAll(filepath.Join(home, "Library", "Caches"), 0777); err != nil { 386 return err 387 } 388 } 389 390 if runtime.GOOS == "linux" { 391 if err := os.MkdirAll(xdghome, 0777); err != nil { 392 return err 393 } 394 } 395 396 keyVals = append(keyVals, "SOURCE", sourceDir) 397 398 goVersion := runtime.Version() 399 400 goVersion = strings.TrimPrefix(goVersion, "go") 401 if strings.HasPrefix(goVersion, "1.20") { 402 // Strip patch version. 403 goVersion = goVersion[:strings.LastIndex(goVersion, ".")] 404 } 405 406 keyVals = append(keyVals, "GOVERSION", goVersion) 407 envhelpers.SetEnvVars(&env.Vars, keyVals...) 408 409 return nil 410 } 411 }