@@ -103,6 +103,16 @@ type Params struct {
103103 // (for example because the function invoked os.Exit), then the
104104 // error will be ignored.
105105 IgnoreMissedCoverage bool
106+
107+ // UpdateScripts specifies that if a `cmp` command fails and
108+ // its first argument is `stdout` or `stderr` and its second argument
109+ // refers to a file inside the testscript file, the command will succeed
110+ // and the testscript file will be updated to reflect the actual output.
111+ //
112+ // The content will be quoted with txtar.Quote if needed;
113+ // a manual change will be needed if it is not unquoted in the
114+ // script.
115+ UpdateScripts bool
106116}
107117
108118// RunDir runs the tests in the given directory. All files in dir with a ".txt"
@@ -165,13 +175,15 @@ func RunT(t T, p Params) {
165175 t .Run (name , func (t T ) {
166176 t .Parallel ()
167177 ts := & TestScript {
168- t : t ,
169- testTempDir : testTempDir ,
170- name : name ,
171- file : file ,
172- params : p ,
173- ctxt : context .Background (),
174- deferred : func () {},
178+ t : t ,
179+ testTempDir : testTempDir ,
180+ name : name ,
181+ file : file ,
182+ params : p ,
183+ ctxt : context .Background (),
184+ deferred : func () {},
185+ scriptFiles : make (map [string ]string ),
186+ scriptUpdates : make (map [string ]string ),
175187 }
176188 defer func () {
177189 if p .TestWork || * testWork {
@@ -191,27 +203,30 @@ func RunT(t T, p Params) {
191203
192204// A TestScript holds execution state for a single test script.
193205type TestScript struct {
194- params Params
195- t T
196- testTempDir string
197- workdir string // temporary work dir ($WORK)
198- log bytes.Buffer // test execution log (printed at end of test)
199- mark int // offset of next log truncation
200- cd string // current directory during test execution; initially $WORK/gopath/src
201- name string // short name of test ("foo")
202- file string // full file name ("testdata/script/foo.txt")
203- lineno int // line number currently executing
204- line string // line currently executing
205- env []string // environment list (for os/exec)
206- envMap map [string ]string // environment mapping (matches env; on Windows keys are lowercase)
207- values map [interface {}]interface {} // values for custom commands
208- stdin string // standard input to next 'go' command; set by 'stdin' command.
209- stdout string // standard output from last 'go' command; for 'stdout' command
210- stderr string // standard error from last 'go' command; for 'stderr' command
211- stopped bool // test wants to stop early
212- start time.Time // time phase started
213- background []backgroundCmd // backgrounded 'exec' and 'go' commands
214- deferred func () // deferred cleanup actions.
206+ params Params
207+ t T
208+ testTempDir string
209+ workdir string // temporary work dir ($WORK)
210+ log bytes.Buffer // test execution log (printed at end of test)
211+ mark int // offset of next log truncation
212+ cd string // current directory during test execution; initially $WORK/gopath/src
213+ name string // short name of test ("foo")
214+ file string // full file name ("testdata/script/foo.txt")
215+ lineno int // line number currently executing
216+ line string // line currently executing
217+ env []string // environment list (for os/exec)
218+ envMap map [string ]string // environment mapping (matches env; on Windows keys are lowercase)
219+ values map [interface {}]interface {} // values for custom commands
220+ stdin string // standard input to next 'go' command; set by 'stdin' command.
221+ stdout string // standard output from last 'go' command; for 'stdout' command
222+ stderr string // standard error from last 'go' command; for 'stderr' command
223+ stopped bool // test wants to stop early
224+ start time.Time // time phase started
225+ background []backgroundCmd // backgrounded 'exec' and 'go' commands
226+ deferred func () // deferred cleanup actions.
227+ archive * txtar.Archive // the testscript being run.
228+ scriptFiles map [string ]string // files stored in the txtar archive (absolute paths -> path in script)
229+ scriptUpdates map [string ]string // updates to testscript files via UpdateScripts.
215230
216231 ctxt context.Context // per TestScript context
217232}
@@ -256,8 +271,10 @@ func (ts *TestScript) setup() string {
256271 // Unpack archive.
257272 a , err := txtar .ParseFile (ts .file )
258273 ts .Check (err )
274+ ts .archive = a
259275 for _ , f := range a .Files {
260276 name := ts .MkAbs (ts .expand (f .Name ))
277+ ts .scriptFiles [name ] = f .Name
261278 ts .Check (os .MkdirAll (filepath .Dir (name ), 0777 ))
262279 ts .Check (ioutil .WriteFile (name , f .Data , 0666 ))
263280 }
@@ -327,6 +344,7 @@ func (ts *TestScript) run() {
327344 fmt .Fprintf (& ts .log , "\n " )
328345 ts .mark = ts .log .Len ()
329346 }
347+ defer ts .applyScriptUpdates ()
330348
331349 // Run script.
332350 // See testdata/script/README for documentation of script form.
@@ -434,6 +452,40 @@ Script:
434452 }
435453}
436454
455+ func (ts * TestScript ) applyScriptUpdates () {
456+ if len (ts .scriptUpdates ) == 0 {
457+ return
458+ }
459+ for name , content := range ts .scriptUpdates {
460+ found := false
461+ for i := range ts .archive .Files {
462+ f := & ts .archive .Files [i ]
463+ if f .Name != name {
464+ continue
465+ }
466+ data := []byte (content )
467+ if txtar .NeedsQuote (data ) {
468+ data1 , err := txtar .Quote (data )
469+ if err != nil {
470+ ts .t .Fatal (fmt .Sprintf ("cannot update script file %q: %v" , f .Name , err ))
471+ continue
472+ }
473+ data = data1
474+ }
475+ f .Data = data
476+ found = true
477+ }
478+ // Sanity check.
479+ if ! found {
480+ panic ("script update file not found" )
481+ }
482+ }
483+ if err := ioutil .WriteFile (ts .file , txtar .Format (ts .archive ), 0666 ); err != nil {
484+ ts .t .Fatal ("cannot update script: " , err )
485+ }
486+ ts .Logf ("%s updated" , ts .file )
487+ }
488+
437489// condition reports whether the given condition is satisfied.
438490func (ts * TestScript ) condition (cond string ) (bool , error ) {
439491 switch cond {
0 commit comments