diff --git a/.circleci/config.yml b/.circleci/config.yml index 4702a8457..58eb73653 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: defaults: &defaults resource_class: large docker: - - image: bepsays/ci-hugoreleaser:1.22000.20100 + - image: bepsays/ci-hugoreleaser:1.22100.20000 environment: &buildenv GOMODCACHE: /root/project/gomodcache version: 2 @@ -60,7 +60,7 @@ jobs: environment: <<: [*buildenv] docker: - - image: bepsays/ci-hugoreleaser-linux-arm64:1.22000.20100 + - image: bepsays/ci-hugoreleaser-linux-arm64:1.22100.20000 steps: - *restore-cache - &attach-workspace diff --git a/.github/workflows/test-dart-sass-v1.yml b/.github/workflows/test-dart-sass-v1.yml index ff6f6a01b..d3556a647 100644 --- a/.github/workflows/test-dart-sass-v1.yml +++ b/.github/workflows/test-dart-sass-v1.yml @@ -14,7 +14,7 @@ jobs: test: strategy: matrix: - go-version: [1.20.x] + go-version: [1.21.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f64f3947..fb163cfbe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: test: strategy: matrix: - go-version: [1.19.x,1.20.x] + go-version: [1.20.x,1.21.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/main_test.go b/main_test.go index c5a59828f..eda829dbb 100644 --- a/main_test.go +++ b/main_test.go @@ -396,8 +396,13 @@ func testSetupFunc() func(env *testscript.Env) error { keyVals = append(keyVals, "SOURCE", sourceDir) goVersion := runtime.Version() - // Strip all but the major and minor version. - goVersion = regexp.MustCompile(`^go(\d+\.\d+)`).FindStringSubmatch(goVersion)[1] + + goVersion = strings.TrimPrefix(goVersion, "go") + if !strings.HasSuffix(goVersion, ".0") { + // Strip patch version. + goVersion = goVersion[:strings.LastIndex(goVersion, ".")] + } + keyVals = append(keyVals, "GOVERSION", goVersion) envhelpers.SetEnvVars(&env.Vars, keyVals...) diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go index bdc190a69..30c61e585 100644 --- a/scripts/fork_go_templates/main.go +++ b/scripts/fork_go_templates/main.go @@ -16,7 +16,7 @@ import ( ) func main() { - // The current is built with de4748c47c67392a57f250714509f590f68ad395 HEAD, tag: go1.20. + // The current is built with c19c4c566c HEAD, tag: go1.21.0. fmt.Println("Forking ...") defer fmt.Println("Done ...") diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 74044744b..c4c42a950 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -116,7 +116,7 @@ parts: go: plugin: nil stage-snaps: - - go/1.19/stable + - go/1.21/stable prime: - bin/go - pkg/tool diff --git a/tpl/internal/go_templates/cfg/cfg.go b/tpl/internal/go_templates/cfg/cfg.go index 78664d7a9..2af0ec707 100644 --- a/tpl/internal/go_templates/cfg/cfg.go +++ b/tpl/internal/go_templates/cfg/cfg.go @@ -38,6 +38,7 @@ const KnownEnv = ` GOARM GOBIN GOCACHE + GOCACHEPROG GOENV GOEXE GOEXPERIMENT @@ -59,6 +60,7 @@ const KnownEnv = ` GOROOT GOSUMDB GOTMPDIR + GOTOOLCHAIN GOTOOLDIR GOVCS GOWASM diff --git a/tpl/internal/go_templates/htmltemplate/attr_string.go b/tpl/internal/go_templates/htmltemplate/attr_string.go index babe70c08..51c3f2620 100644 --- a/tpl/internal/go_templates/htmltemplate/attr_string.go +++ b/tpl/internal/go_templates/htmltemplate/attr_string.go @@ -4,6 +4,18 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[attrNone-0] + _ = x[attrScript-1] + _ = x[attrScriptType-2] + _ = x[attrStyle-3] + _ = x[attrURL-4] + _ = x[attrSrcset-5] +} + const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcset" var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58} diff --git a/tpl/internal/go_templates/htmltemplate/delim_string.go b/tpl/internal/go_templates/htmltemplate/delim_string.go index 6d80e09a4..8d8285022 100644 --- a/tpl/internal/go_templates/htmltemplate/delim_string.go +++ b/tpl/internal/go_templates/htmltemplate/delim_string.go @@ -4,6 +4,16 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[delimNone-0] + _ = x[delimDoubleQuote-1] + _ = x[delimSingleQuote-2] + _ = x[delimSpaceOrTagEnd-3] +} + const _delim_name = "delimNonedelimDoubleQuotedelimSingleQuotedelimSpaceOrTagEnd" var _delim_index = [...]uint8{0, 9, 25, 41, 59} diff --git a/tpl/internal/go_templates/htmltemplate/doc.go b/tpl/internal/go_templates/htmltemplate/doc.go index 98b5658f4..6be5e0f84 100644 --- a/tpl/internal/go_templates/htmltemplate/doc.go +++ b/tpl/internal/go_templates/htmltemplate/doc.go @@ -5,16 +5,16 @@ /* Package template (html/template) implements data-driven templates for generating HTML output safe against code injection. It provides the -same interface as package text/template and should be used instead of -text/template whenever the output is HTML. +same interface as [text/template] and should be used instead of +[text/template] whenever the output is HTML. The documentation here focuses on the security features of the package. For information about how to program the templates themselves, see the -documentation for text/template. +documentation for [text/template]. # Introduction -This package wraps package text/template so you can share its template API +This package wraps [text/template] so you can share its template API to parse and execute HTML templates safely. tmpl, err := template.New("name").Parse(...) diff --git a/tpl/internal/go_templates/htmltemplate/element_string.go b/tpl/internal/go_templates/htmltemplate/element_string.go index 4573e0873..db286655a 100644 --- a/tpl/internal/go_templates/htmltemplate/element_string.go +++ b/tpl/internal/go_templates/htmltemplate/element_string.go @@ -4,6 +4,17 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[elementNone-0] + _ = x[elementScript-1] + _ = x[elementStyle-2] + _ = x[elementTextarea-3] + _ = x[elementTitle-4] +} + const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitle" var _element_index = [...]uint8{0, 11, 24, 36, 51, 63} diff --git a/tpl/internal/go_templates/htmltemplate/error.go b/tpl/internal/go_templates/htmltemplate/error.go index 0a62563cf..a4450a4a9 100644 --- a/tpl/internal/go_templates/htmltemplate/error.go +++ b/tpl/internal/go_templates/htmltemplate/error.go @@ -216,18 +216,13 @@ const ( // disallowed. Avoid using "html" and "urlquery" entirely in new templates. ErrPredefinedEscaper - // errJSTmplLit: "... appears in a JS template literal" + // ErrJSTemplate: "... appears in a JS template literal" // Example: - // + // // Discussion: // Package html/template does not support actions inside of JS template // literals. - // - // TODO(rolandshoemaker): we cannot add this as an exported error in a minor - // release, since it is backwards incompatible with the other minor - // releases. As such we need to leave it unexported, and then we'll add it - // in the next major release. - errJSTmplLit + ErrJSTemplate ) func (e *Error) Error() string { diff --git a/tpl/internal/go_templates/htmltemplate/escape.go b/tpl/internal/go_templates/htmltemplate/escape.go index ba9b4bbb8..f6b2e4b34 100644 --- a/tpl/internal/go_templates/htmltemplate/escape.go +++ b/tpl/internal/go_templates/htmltemplate/escape.go @@ -8,6 +8,8 @@ import ( "bytes" "fmt" "html" + + //"internal/godebug" "io" template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" @@ -161,6 +163,7 @@ func (e *escaper) escape(c context, n parse.Node) context { panic("escaping " + n.String() + " is unimplemented") } +// Modified by Hugo. // var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp") // escapeAction escapes an action template node. @@ -227,12 +230,13 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context { case stateJSDqStr, stateJSSqStr: s = append(s, "_html_template_jsstrescaper") case stateJSBqStr: - if SecurityAllowActionJSTmpl.Load() { // .Value() == "1" { + if SecurityAllowActionJSTmpl.Load() { + //debugAllowActionJSTmpl.IncNonDefault() s = append(s, "_html_template_jsstrescaper") } else { return context{ state: stateError, - err: errorf(errJSTmplLit, n, n.Line, "%s appears in a JS template literal", n), + err: errorf(ErrJSTemplate, n, n.Line, "%s appears in a JS template literal", n), } } case stateJSRegexp: @@ -756,7 +760,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context { } else if isComment(c.state) && c.delim == delimNone { switch c.state { case stateJSBlockCmt: - // https://es5.github.com/#x7.4: + // https://es5.github.io/#x7.4: // "Comments behave like white space and are // discarded except that, if a MultiLineComment // contains a line terminator character, then diff --git a/tpl/internal/go_templates/htmltemplate/exec_test.go b/tpl/internal/go_templates/htmltemplate/exec_test.go index 0f29cb060..8f9b893b4 100644 --- a/tpl/internal/go_templates/htmltemplate/exec_test.go +++ b/tpl/internal/go_templates/htmltemplate/exec_test.go @@ -925,7 +925,7 @@ func TestJSEscaping(t *testing.T) { {`'foo`, `\'foo`}, {`Go "jump" \`, `Go \"jump\" \\`}, {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`}, - {"unprintable \uFDFF", `unprintable \uFDFF`}, + {"unprintable \uFFFE", `unprintable \uFFFE`}, {``, `\u003Chtml\u003E`}, {`no = in attributes`, `no \u003D in attributes`}, {`' does not become HTML entity`, `\u0026#x27; does not become HTML entity`}, diff --git a/tpl/internal/go_templates/htmltemplate/transition.go b/tpl/internal/go_templates/htmltemplate/transition.go index 92eb35190..3b9fbfb68 100644 --- a/tpl/internal/go_templates/htmltemplate/transition.go +++ b/tpl/internal/go_templates/htmltemplate/transition.go @@ -397,7 +397,7 @@ func tLineCmt(c context, s []byte) (context, int) { return c, len(s) } c.state = endState - // Per section 7.4 of EcmaScript 5 : https://es5.github.com/#x7.4 + // Per section 7.4 of EcmaScript 5 : https://es5.github.io/#x7.4 // "However, the LineTerminator at the end of the line is not // considered to be part of the single-line comment; it is // recognized separately by the lexical grammar and becomes part diff --git a/tpl/internal/go_templates/htmltemplate/urlpart_string.go b/tpl/internal/go_templates/htmltemplate/urlpart_string.go index 813eea9e4..7bc957e81 100644 --- a/tpl/internal/go_templates/htmltemplate/urlpart_string.go +++ b/tpl/internal/go_templates/htmltemplate/urlpart_string.go @@ -4,6 +4,16 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[urlPartNone-0] + _ = x[urlPartPreQuery-1] + _ = x[urlPartQueryOrFrag-2] + _ = x[urlPartUnknown-3] +} + const _urlPart_name = "urlPartNoneurlPartPreQueryurlPartQueryOrFragurlPartUnknown" var _urlPart_index = [...]uint8{0, 11, 26, 44, 58} diff --git a/tpl/internal/go_templates/testenv/exec.go b/tpl/internal/go_templates/testenv/exec.go index 13b4c7102..88791b3b4 100644 --- a/tpl/internal/go_templates/testenv/exec.go +++ b/tpl/internal/go_templates/testenv/exec.go @@ -6,33 +6,78 @@ package testenv import ( "context" + "fmt" "os" "os/exec" "runtime" + "strconv" "strings" "sync" "testing" + "time" ) -// HasExec reports whether the current system can start new processes -// using os.StartProcess or (more commonly) exec.Command. -func HasExec() bool { - switch runtime.GOOS { - case "js", "ios": - return false - } - return true -} - // MustHaveExec checks that the current system can start new processes // using os.StartProcess or (more commonly) exec.Command. // If not, MustHaveExec calls t.Skip with an explanation. +// +// On some platforms MustHaveExec checks for exec support by re-executing the +// current executable, which must be a binary built by 'go test'. +// We intentionally do not provide a HasExec function because of the risk of +// inappropriate recursion in TestMain functions. +// +// To check for exec support outside of a test, just try to exec the command. +// If exec is not supported, testenv.SyscallIsNotSupported will return true +// for the resulting error. func MustHaveExec(t testing.TB) { - if !HasExec() { - t.Skipf("skipping test: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH) + tryExecOnce.Do(func() { + tryExecErr = tryExec() + }) + if tryExecErr != nil { + t.Skipf("skipping test: cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, tryExecErr) } } +var ( + tryExecOnce sync.Once + tryExecErr error +) + +func tryExec() error { + switch runtime.GOOS { + case "wasip1", "js", "ios": + default: + // Assume that exec always works on non-mobile platforms and Android. + return nil + } + + // ios has an exec syscall but on real iOS devices it might return a + // permission error. In an emulated environment (such as a Corellium host) + // it might succeed, so if we need to exec we'll just have to try it and + // find out. + // + // As of 2023-04-19 wasip1 and js don't have exec syscalls at all, but we + // may as well use the same path so that this branch can be tested without + // an ios environment. + + /*if !testing.Testing() { + // This isn't a standard 'go test' binary, so we don't know how to + // self-exec in a way that should succeed without side effects. + // Just forget it. + return errors.New("can't probe for exec support with a non-test executable") + }*/ + + // We know that this is a test executable. We should be able to run it with a + // no-op flag to check for overall exec support. + exe, err := os.Executable() + if err != nil { + return fmt.Errorf("can't probe for exec support: %w", err) + } + cmd := exec.Command(exe, "-test.list=^$") + cmd.Env = origEnv + return cmd.Run() +} + var execPaths sync.Map // path -> error // MustHaveExecPath checks that the current system can start the named executable @@ -82,7 +127,87 @@ func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd { // - fails the test if the command does not complete before the test's deadline, and // - sets a Cleanup function that verifies that the test did not leak a subprocess. func CommandContext(t testing.TB, ctx context.Context, name string, args ...string) *exec.Cmd { - panic("Not implemented, Hugo is not using this") + t.Helper() + MustHaveExec(t) + + var ( + cancelCtx context.CancelFunc + gracePeriod time.Duration // unlimited unless the test has a deadline (to allow for interactive debugging) + ) + + if t, ok := t.(interface { + testing.TB + Deadline() (time.Time, bool) + }); ok { + if td, ok := t.Deadline(); ok { + // Start with a minimum grace period, just long enough to consume the + // output of a reasonable program after it terminates. + gracePeriod = 100 * time.Millisecond + if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { + scale, err := strconv.Atoi(s) + if err != nil { + t.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", err) + } + gracePeriod *= time.Duration(scale) + } + + // If time allows, increase the termination grace period to 5% of the + // test's remaining time. + testTimeout := time.Until(td) + if gp := testTimeout / 20; gp > gracePeriod { + gracePeriod = gp + } + + // When we run commands that execute subprocesses, we want to reserve two + // grace periods to clean up: one for the delay between the first + // termination signal being sent (via the Cancel callback when the Context + // expires) and the process being forcibly terminated (via the WaitDelay + // field), and a second one for the delay between the process being + // terminated and the test logging its output for debugging. + // + // (We want to ensure that the test process itself has enough time to + // log the output before it is also terminated.) + cmdTimeout := testTimeout - 2*gracePeriod + + if cd, ok := ctx.Deadline(); !ok || time.Until(cd) > cmdTimeout { + // Either ctx doesn't have a deadline, or its deadline would expire + // after (or too close before) the test has already timed out. + // Add a shorter timeout so that the test will produce useful output. + ctx, cancelCtx = context.WithTimeout(ctx, cmdTimeout) + } + } + } + + cmd := exec.CommandContext(ctx, name, args...) + cmd.Cancel = func() error { + if cancelCtx != nil && ctx.Err() == context.DeadlineExceeded { + // The command timed out due to running too close to the test's deadline. + // There is no way the test did that intentionally — it's too close to the + // wire! — so mark it as a test failure. That way, if the test expects the + // command to fail for some other reason, it doesn't have to distinguish + // between that reason and a timeout. + t.Errorf("test timed out while running command: %v", cmd) + } else { + // The command is being terminated due to ctx being canceled, but + // apparently not due to an explicit test deadline that we added. + // Log that information in case it is useful for diagnosing a failure, + // but don't actually fail the test because of it. + t.Logf("%v: terminating command: %v", ctx.Err(), cmd) + } + return cmd.Process.Signal(Sigquit) + } + cmd.WaitDelay = gracePeriod + + t.Cleanup(func() { + if cancelCtx != nil { + cancelCtx() + } + if cmd.Process != nil && cmd.ProcessState == nil { + t.Errorf("command was started, but test did not wait for it to complete: %v", cmd) + } + }) + + return cmd } // Command is like exec.Command, but applies the same changes as diff --git a/tpl/internal/go_templates/testenv/testenv.go b/tpl/internal/go_templates/testenv/testenv.go index 91de6e76c..2430ae6cf 100644 --- a/tpl/internal/go_templates/testenv/testenv.go +++ b/tpl/internal/go_templates/testenv/testenv.go @@ -11,9 +11,14 @@ package testenv import ( + "bytes" "errors" "flag" "fmt" + + "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg" + + //"internal/platform" "os" "os/exec" "path/filepath" @@ -22,10 +27,14 @@ import ( "strings" "sync" "testing" - - "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg" ) +// Save the original environment during init for use in checks. A test +// binary may modify its environment before calling HasExec to change its +// behavior (such as mimicking a command-line tool), and that modified +// environment might cause environment checks to behave erratically. +var origEnv = os.Environ() + // Builder reports the name of the builder running this test // (for example, "linux-amd64" or "windows-386-gce"). // If the test is not running on the build infrastructure, @@ -37,29 +46,26 @@ func Builder() string { // HasGoBuild reports whether the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. func HasGoBuild() bool { - if os.Getenv("GO_GCFLAGS") != "" { - // It's too much work to require every caller of the go command - // to pass along "-gcflags="+os.Getenv("GO_GCFLAGS"). - // For now, if $GO_GCFLAGS is set, report that we simply can't - // run go build. - return false - } - switch runtime.GOOS { - case "android", "js", "ios": - return false - } - return true + // Modified by Hugo (not needed) + return false } +var ( + goBuildOnce sync.Once + goBuildErr error +) + // MustHaveGoBuild checks that the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. // If not, MustHaveGoBuild calls t.Skip with an explanation. func MustHaveGoBuild(t testing.TB) { if os.Getenv("GO_GCFLAGS") != "" { + t.Helper() t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS") } if !HasGoBuild() { - t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH) + t.Helper() + t.Skipf("skipping test: 'go build' unavailable: %v", goBuildErr) } } @@ -77,6 +83,25 @@ func MustHaveGoRun(t testing.TB) { } } +// HasParallelism reports whether the current system can execute multiple +// threads in parallel. +// There is a copy of this function in cmd/dist/test.go. +func HasParallelism() bool { + switch runtime.GOOS { + case "js", "wasip1": + return false + } + return true +} + +// MustHaveParallelism checks that the current system can execute multiple +// threads in parallel. If not, MustHaveParallelism calls t.Skip with an explanation. +func MustHaveParallelism(t testing.TB) { + if !HasParallelism() { + t.Skipf("skipping test: no parallelism available on %s/%s", runtime.GOOS, runtime.GOARCH) + } +} + // GoToolPath reports the path to the Go tool. // It is a convenience wrapper around GoTool. // If the tool is unavailable GoToolPath calls t.Skip. @@ -124,6 +149,9 @@ func findGOROOT() (string, error) { // runs the test in the directory containing the packaged under test.) That // means that if we start walking up the tree, we should eventually find // GOROOT/src/go.mod, and we can report the parent directory of that. + // + // Notably, this works even if we can't run 'go env GOROOT' as a + // subprocess. cwd, err := os.Getwd() if err != nil { @@ -174,7 +202,8 @@ func findGOROOT() (string, error) { // GOROOT reports the path to the directory containing the root of the Go // project source tree. This is normally equivalent to runtime.GOROOT, but -// works even if the test binary was built with -trimpath. +// works even if the test binary was built with -trimpath and cannot exec +// 'go env GOROOT'. // // If GOROOT cannot be found, GOROOT skips t if t is non-nil, // or panics otherwise. @@ -195,25 +224,18 @@ func GoTool() (string, error) { if !HasGoBuild() { return "", errors.New("platform cannot run go tool") } - var exeSuffix string - if runtime.GOOS == "windows" { - exeSuffix = ".exe" - } - goroot, err := findGOROOT() - if err != nil { - return "", fmt.Errorf("cannot find go tool: %w", err) - } - path := filepath.Join(goroot, "bin", "go"+exeSuffix) - if _, err := os.Stat(path); err == nil { - return path, nil - } - goBin, err := exec.LookPath("go" + exeSuffix) - if err != nil { - return "", errors.New("cannot find go tool: " + err.Error()) - } - return goBin, nil + goToolOnce.Do(func() { + goToolPath, goToolErr = exec.LookPath("go") + }) + return goToolPath, goToolErr } +var ( + goToolOnce sync.Once + goToolPath string + goToolErr error +) + // HasSrc reports whether the entire source tree is available under GOROOT. func HasSrc() bool { switch runtime.GOOS { @@ -226,50 +248,82 @@ func HasSrc() bool { // HasExternalNetwork reports whether the current system can use // external (non-localhost) networks. func HasExternalNetwork() bool { - return !testing.Short() && runtime.GOOS != "js" + return !testing.Short() && runtime.GOOS != "js" && runtime.GOOS != "wasip1" } // MustHaveExternalNetwork checks that the current system can use // external (non-localhost) networks. // If not, MustHaveExternalNetwork calls t.Skip with an explanation. func MustHaveExternalNetwork(t testing.TB) { - if runtime.GOOS == "js" { + if runtime.GOOS == "js" || runtime.GOOS == "wasip1" { + t.Helper() t.Skipf("skipping test: no external network on %s", runtime.GOOS) } if testing.Short() { + t.Helper() t.Skipf("skipping test: no external network in -short mode") } } -var haveCGO bool - // HasCGO reports whether the current system can use cgo. func HasCGO() bool { - return haveCGO + hasCgoOnce.Do(func() { + goTool, err := GoTool() + if err != nil { + return + } + cmd := exec.Command(goTool, "env", "CGO_ENABLED") + cmd.Env = origEnv + out, err := cmd.Output() + if err != nil { + panic(fmt.Sprintf("%v: %v", cmd, out)) + } + hasCgo, err = strconv.ParseBool(string(bytes.TrimSpace(out))) + if err != nil { + panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out)) + } + }) + return hasCgo } +var ( + hasCgoOnce sync.Once + hasCgo bool +) + // MustHaveCGO calls t.Skip if cgo is not available. func MustHaveCGO(t testing.TB) { - if !haveCGO { + if !HasCGO() { t.Skipf("skipping test: no cgo") } } // CanInternalLink reports whether the current system can link programs with // internal linking. -func CanInternalLink() bool { - panic("not implemented, not needed by Hugo") +func CanInternalLink(withCgo bool) bool { + // Modified by Hugo (not needed) + return false } // MustInternalLink checks that the current system can link programs with internal // linking. // If not, MustInternalLink calls t.Skip with an explanation. -func MustInternalLink(t testing.TB) { - if !CanInternalLink() { +func MustInternalLink(t testing.TB, withCgo bool) { + if !CanInternalLink(withCgo) { + if withCgo && CanInternalLink(false) { + t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH) + } t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH) } } +// MustHaveBuildMode reports whether the current system can build programs in +// the given build mode. +// If not, MustHaveBuildMode calls t.Skip with an explanation. +func MustHaveBuildMode(t testing.TB, buildmode string) { + // Modified by Hugo (not needed) +} + // HasSymlink reports whether the current system can use os.Symlink. func HasSymlink() bool { ok, _ := hasSymlink() @@ -281,7 +335,7 @@ func HasSymlink() bool { func MustHaveSymlink(t testing.TB) { ok, reason := hasSymlink() if !ok { - t.Skipf("skipping test: cannot make symlinks on %s/%s%s", runtime.GOOS, runtime.GOARCH, reason) + t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason) } } @@ -320,7 +374,7 @@ func SkipFlakyNet(t testing.TB) { // CPUIsSlow reports whether the CPU running the test is suspected to be slow. func CPUIsSlow() bool { switch runtime.GOARCH { - case "arm", "mips", "mipsle", "mips64", "mips64le": + case "arm", "mips", "mipsle", "mips64", "mips64le", "wasm": return true } return false @@ -346,8 +400,51 @@ func SkipIfOptimizationOff(t testing.TB) { } // WriteImportcfg writes an importcfg file used by the compiler or linker to -// dstPath containing entries for the packages in std and cmd in addition -// to the package to package file mappings in additionalPackageFiles. -func WriteImportcfg(t testing.TB, dstPath string, additionalPackageFiles map[string]string) { - panic("not implemented, not needed by Hugo") +// dstPath containing entries for the file mappings in packageFiles, as well +// as for the packages transitively imported by the package(s) in pkgs. +// +// pkgs may include any package pattern that is valid to pass to 'go list', +// so it may also be a list of Go source files all in the same directory. +func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string, pkgs ...string) { + t.Helper() + + icfg := new(bytes.Buffer) + icfg.WriteString("# import config\n") + for k, v := range packageFiles { + fmt.Fprintf(icfg, "packagefile %s=%s\n", k, v) + } + + if len(pkgs) > 0 { + // Use 'go list' to resolve any missing packages and rewrite the import map. + cmd := Command(t, GoToolPath(t), "list", "-export", "-deps", "-f", `{{if ne .ImportPath "command-line-arguments"}}{{if .Export}}{{.ImportPath}}={{.Export}}{{end}}{{end}}`) + cmd.Args = append(cmd.Args, pkgs...) + cmd.Stderr = new(strings.Builder) + out, err := cmd.Output() + if err != nil { + t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr) + } + + for _, line := range strings.Split(string(out), "\n") { + if line == "" { + continue + } + importPath, export, ok := strings.Cut(line, "=") + if !ok { + t.Fatalf("invalid line in output from %v:\n%s", cmd, line) + } + if packageFiles[importPath] == "" { + fmt.Fprintf(icfg, "packagefile %s=%s\n", importPath, export) + } + } + } + + if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil { + t.Fatal(err) + } +} + +// SyscallIsNotSupported reports whether err may indicate that a system call is +// not supported by the current platform or execution environment. +func SyscallIsNotSupported(err error) bool { + return syscallIsNotSupported(err) } diff --git a/tpl/internal/go_templates/testenv/testenv_cgo.go b/tpl/internal/go_templates/testenv/testenv_cgo.go deleted file mode 100644 index 7426a29c1..000000000 --- a/tpl/internal/go_templates/testenv/testenv_cgo.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build cgo - -package testenv - -func init() { - haveCGO = true -} diff --git a/tpl/internal/go_templates/testenv/testenv_notunix.go b/tpl/internal/go_templates/testenv/testenv_notunix.go index 180206bc9..9d55f6258 100644 --- a/tpl/internal/go_templates/testenv/testenv_notunix.go +++ b/tpl/internal/go_templates/testenv/testenv_notunix.go @@ -2,12 +2,19 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build windows || plan9 || (js && wasm) +//go:build windows || plan9 || (js && wasm) || wasip1 package testenv -import "os" +import ( + "os" +) // Sigquit is the signal to send to kill a hanging subprocess. // On Unix we send SIGQUIT, but on non-Unix we only have os.Kill. var Sigquit = os.Kill + +func syscallIsNotSupported(err error) bool { + // Removed by Hugo (not supported in Go 1.20). + return false +} diff --git a/tpl/internal/go_templates/testenv/testenv_notwin.go b/tpl/internal/go_templates/testenv/testenv_notwin.go index 81171fd19..30e159a6e 100644 --- a/tpl/internal/go_templates/testenv/testenv_notwin.go +++ b/tpl/internal/go_templates/testenv/testenv_notwin.go @@ -7,13 +7,39 @@ package testenv import ( + "fmt" + "os" + "path/filepath" "runtime" ) func hasSymlink() (ok bool, reason string) { switch runtime.GOOS { - case "android", "plan9": + case "plan9": return false, "" + case "android", "wasip1": + // For wasip1, some runtimes forbid absolute symlinks, + // or symlinks that escape the current working directory. + // Perform a simple test to see whether the runtime + // supports symlinks or not. If we get a permission + // error, the runtime does not support symlinks. + dir, err := os.MkdirTemp("", "") + if err != nil { + return false, "" + } + defer func() { + _ = os.RemoveAll(dir) + }() + fpath := filepath.Join(dir, "testfile.txt") + if err := os.WriteFile(fpath, nil, 0644); err != nil { + return false, "" + } + if err := os.Symlink(fpath, filepath.Join(dir, "testlink")); err != nil { + if SyscallIsNotSupported(err) { + return false, fmt.Sprintf("symlinks unsupported: %s", err.Error()) + } + return false, "" + } } return true, "" diff --git a/tpl/internal/go_templates/testenv/testenv_test.go b/tpl/internal/go_templates/testenv/testenv_test.go index 2f72080c2..564bff1ab 100644 --- a/tpl/internal/go_templates/testenv/testenv_test.go +++ b/tpl/internal/go_templates/testenv/testenv_test.go @@ -5,16 +5,16 @@ package testenv_test import ( + "github.com/gohugoio/hugo/tpl/internal/go_templates/testenv" + //"internal/platform" "os" "path/filepath" "runtime" + "strings" "testing" - - "github.com/gohugoio/hugo/tpl/internal/go_templates/testenv" ) func TestGoToolLocation(t *testing.T) { - t.Skip("skipping test that requires go command") testenv.MustHaveGoBuild(t) var exeSuffix string @@ -53,3 +53,33 @@ func TestGoToolLocation(t *testing.T) { t.Fatalf("%q is not the same file as %q", absWant, goTool) } } + +func TestHasGoBuild(t *testing.T) { + // Removed by Hugo. +} + +func TestMustHaveExec(t *testing.T) { + hasExec := false + t.Run("MustHaveExec", func(t *testing.T) { + testenv.MustHaveExec(t) + t.Logf("MustHaveExec did not skip") + hasExec = true + }) + + switch runtime.GOOS { + case "js", "wasip1": + if hasExec { + // js and wasip1 lack an “exec” syscall. + t.Errorf("expected MustHaveExec to skip on %v", runtime.GOOS) + } + case "ios": + if b := testenv.Builder(); strings.HasSuffix(b, "-corellium") && !hasExec { + // Most ios environments can't exec, but the corellium builder can. + t.Errorf("expected MustHaveExec not to skip on %v", b) + } + default: + if b := testenv.Builder(); b != "" && !hasExec { + t.Errorf("expected MustHaveExec not to skip on %v", b) + } + } +} diff --git a/tpl/internal/go_templates/testenv/testenv_unix.go b/tpl/internal/go_templates/testenv/testenv_unix.go index a97e88da2..abb89eeca 100644 --- a/tpl/internal/go_templates/testenv/testenv_unix.go +++ b/tpl/internal/go_templates/testenv/testenv_unix.go @@ -6,8 +6,15 @@ package testenv -import "syscall" +import ( + "syscall" +) // Sigquit is the signal to send to kill a hanging subprocess. // Send SIGQUIT to get a stack trace. var Sigquit = syscall.SIGQUIT + +func syscallIsNotSupported(err error) bool { + // Removed by Hugo (not supported in Go 1.20) + return false +} diff --git a/tpl/internal/go_templates/texttemplate/doc.go b/tpl/internal/go_templates/texttemplate/doc.go index 7817a17b9..4c01b05eb 100644 --- a/tpl/internal/go_templates/texttemplate/doc.go +++ b/tpl/internal/go_templates/texttemplate/doc.go @@ -5,7 +5,7 @@ /* Package template implements data-driven templates for generating textual output. -To generate HTML output, see package html/template, which has the same interface +To generate HTML output, see [html/template], which has the same interface as this package but automatically secures HTML output against certain attacks. Templates are executed by applying them to a data structure. Annotations in the diff --git a/tpl/internal/go_templates/texttemplate/exec_test.go b/tpl/internal/go_templates/texttemplate/exec_test.go index b44d61bb1..b8b3f3bbe 100644 --- a/tpl/internal/go_templates/texttemplate/exec_test.go +++ b/tpl/internal/go_templates/texttemplate/exec_test.go @@ -953,7 +953,7 @@ func TestJSEscaping(t *testing.T) { {`'foo`, `\'foo`}, {`Go "jump" \`, `Go \"jump\" \\`}, {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`}, - {"unprintable \uFDFF", `unprintable \uFDFF`}, + {"unprintable \uFFFE", `unprintable \uFFFE`}, {``, `\u003Chtml\u003E`}, {`no = in attributes`, `no \u003D in attributes`}, {`' does not become HTML entity`, `\u0026#x27; does not become HTML entity`}, diff --git a/tpl/internal/go_templates/texttemplate/funcs.go b/tpl/internal/go_templates/texttemplate/funcs.go index dbea6e705..b5a8c9ec5 100644 --- a/tpl/internal/go_templates/texttemplate/funcs.go +++ b/tpl/internal/go_templates/texttemplate/funcs.go @@ -23,7 +23,7 @@ import ( // Execute returns that error. // // Errors returned by Execute wrap the underlying error; call errors.As to -// uncover them. +// unwrap them. // // When template execution invokes a function with an argument list, that list // must be assignable to the function's parameter types. Functions meant to