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  }