Source file src/cmd/internal/script/scripttest/readme.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package scripttest
     6  
     7  import (
     8  	"bytes"
     9  	"cmd/internal/script"
    10  	"internal/diff"
    11  	"internal/testenv"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"testing"
    16  	"text/template"
    17  )
    18  
    19  func checkScriptReadme(t *testing.T, engine *script.Engine, env []string, scriptspath, gotool string, fixReadme bool) {
    20  	var args struct {
    21  		Language   string
    22  		Commands   string
    23  		Conditions string
    24  	}
    25  
    26  	cmds := new(strings.Builder)
    27  	if err := engine.ListCmds(cmds, true); err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	args.Commands = cmds.String()
    31  
    32  	conds := new(strings.Builder)
    33  	if err := engine.ListConds(conds, nil); err != nil {
    34  		t.Fatal(err)
    35  	}
    36  	args.Conditions = conds.String()
    37  
    38  	doc := new(strings.Builder)
    39  	cmd := testenv.Command(t, gotool, "doc", "cmd/internal/script")
    40  	cmd.Dir = t.TempDir() // make sure the test is not running inside the std or cmd module of another GOROOT
    41  	cmd.Env = env
    42  	cmd.Stdout = doc
    43  	if err := cmd.Run(); err != nil {
    44  		t.Fatal(cmd, ":", err)
    45  	}
    46  	_, lang, ok := strings.Cut(doc.String(), "# Script Language\n\n")
    47  	if !ok {
    48  		t.Fatalf("%q did not include Script Language section", cmd)
    49  	}
    50  	lang, _, ok = strings.Cut(lang, "\n\nvar ")
    51  	if !ok {
    52  		t.Fatalf("%q did not include vars after Script Language section", cmd)
    53  	}
    54  	args.Language = lang
    55  
    56  	tmpl := template.Must(template.New("README").Parse(readmeTmpl[1:]))
    57  	buf := new(bytes.Buffer)
    58  	if err := tmpl.Execute(buf, args); err != nil {
    59  		t.Fatal(err)
    60  	}
    61  
    62  	readmePath := filepath.Join(scriptspath, "README")
    63  	old, err := os.ReadFile(readmePath)
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  	diff := diff.Diff(readmePath, old, "readmeTmpl", buf.Bytes())
    68  	if diff == nil {
    69  		t.Logf("%s is up to date.", readmePath)
    70  		return
    71  	}
    72  
    73  	if fixReadme {
    74  		if err := os.WriteFile(readmePath, buf.Bytes(), 0666); err != nil {
    75  			t.Fatal(err)
    76  		}
    77  		t.Logf("wrote %d bytes to %s", buf.Len(), readmePath)
    78  	} else {
    79  		t.Logf("\n%s", diff)
    80  		t.Errorf("%s is stale. To update, run 'go generate cmd/go'.", readmePath)
    81  	}
    82  }
    83  
    84  const readmeTmpl = `
    85  This file is generated by 'go generate'. DO NOT EDIT.
    86  
    87  This directory holds test scripts *.txt run during 'go test cmd/<toolname>'.
    88  To run a specific script foo.txt
    89  
    90  	go test cmd/<toolname> -run=Script/^foo$
    91  
    92  In general script files should have short names: a few words,
    93   not whole sentences.
    94  The first word should be the general category of behavior being tested,
    95  often the name of a go subcommand (build, link, compile, ...) or concept (vendor, pattern).
    96  
    97  Each script is a text archive (go doc internal/txtar).
    98  The script begins with an actual command script to run
    99  followed by the content of zero or more supporting files to
   100  create in the script's temporary file system before it starts executing.
   101  
   102  As an example, run_hello.txt says:
   103  
   104  	# hello world
   105  	go run hello.go
   106  	stderr 'hello world'
   107  	! stdout .
   108  
   109  	-- hello.go --
   110  	package main
   111  	func main() { println("hello world") }
   112  
   113  Each script runs in a fresh temporary work directory tree, available to scripts as $WORK.
   114  Scripts also have access to other environment variables, including:
   115  
   116  	GOARCH=<target GOARCH>
   117  	GOOS=<target GOOS>
   118  	TMPDIR=$WORK/tmp
   119  	devnull=<value of os.DevNull>
   120  	goversion=<current Go version; for example, 1.12>
   121  
   122  On Plan 9, the variables $path and $home are set instead of $PATH and $HOME.
   123  On Windows, the variables $USERPROFILE and $TMP are set instead of
   124  $HOME and $TMPDIR.
   125  
   126  The lines at the top of the script are a sequence of commands to be executed by
   127  a small script engine configured in .../cmd/internal/script/scripttest/run.go (not the system shell).
   128  
   129  {{.Language}}
   130  
   131  When TestScript runs a script and the script fails, by default TestScript shows
   132  the execution of the most recent phase of the script (since the last # comment)
   133  and only shows the # comments for earlier phases.
   134  
   135  Note also that in reported output, the actual name of the per-script temporary directory
   136  has been consistently replaced with the literal string $WORK.
   137  
   138  The available commands are:
   139  {{.Commands}}
   140  
   141  The available conditions are:
   142  {{.Conditions}}
   143  `
   144  

View as plain text