@@ -126,8 +126,9 @@ elapsed time in the summary line.
126126
127127The rule for a match in the cache is that the run involves the same
128128test binary and the flags on the command line come entirely from a
129- restricted set of 'cacheable' test flags, defined as -benchtime, -cpu,
130- -list, -parallel, -run, -short, -timeout, -failfast, -fullpath and -v.
129+ restricted set of 'cacheable' test flags, defined as -benchtime, -cover,
130+ -covermode, -coverprofile, -cpu, -list, -parallel, -run, -short, -timeout,
131+ -failfast, -fullpath and -v.
131132If a run of go test has any test or non-test flags outside this set,
132133the result is not cached. To disable test caching, use any test flag
133134or argument other than the cacheable flags. The idiomatic way to disable
@@ -1338,6 +1339,13 @@ type runCache struct {
13381339 id2 cache.ActionID
13391340}
13401341
1342+ func coverProfTempFile (a * work.Action ) string {
1343+ if a .Objdir == "" {
1344+ panic ("internal error: objdir not set in coverProfTempFile" )
1345+ }
1346+ return a .Objdir + "_cover_.out"
1347+ }
1348+
13411349// stdoutMu and lockedStdout provide a locked standard output
13421350// that guarantees never to interlace writes from multiple
13431351// goroutines, so that we can have multiple JSON streams writing
@@ -1396,13 +1404,6 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
13961404 return nil
13971405 }
13981406
1399- coverProfTempFile := func (a * work.Action ) string {
1400- if a .Objdir == "" {
1401- panic ("internal error: objdir not set in coverProfTempFile" )
1402- }
1403- return a .Objdir + "_cover_.out"
1404- }
1405-
14061407 if p := a .Package ; len (p .TestGoFiles )+ len (p .XTestGoFiles ) == 0 {
14071408 reportNoTestFiles := true
14081409 if cfg .BuildCover && cfg .Experiment .CoverageRedesign && p .Internal .Cover .GenMeta {
@@ -1426,7 +1427,7 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
14261427 if err := work .WriteCoverageProfile (b , a , mf , cp , stdout ); err != nil {
14271428 return err
14281429 }
1429- mergeCoverProfile (stdout , cp )
1430+ mergeCoverProfile (cp )
14301431 }
14311432 }
14321433 }
@@ -1616,7 +1617,7 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
16161617 a .TestOutput = & buf
16171618 t := fmt .Sprintf ("%.3fs" , time .Since (t0 ).Seconds ())
16181619
1619- mergeCoverProfile (cmd . Stdout , a .Objdir + "_cover_.out" )
1620+ mergeCoverProfile (a .Objdir + "_cover_.out" )
16201621
16211622 if err == nil {
16221623 norun := ""
@@ -1638,10 +1639,10 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
16381639 cmd .Stdout .Write ([]byte ("\n " ))
16391640 }
16401641 fmt .Fprintf (cmd .Stdout , "ok \t %s\t %s%s%s\n " , a .Package .ImportPath , t , coveragePercentage (out ), norun )
1641- r .c .saveOutput (a )
1642+ r .c .saveOutput (a , coverProfTempFile ( a ) )
16421643 } else {
16431644 if testFailFast {
1644- testShouldFailFast . Store ( true )
1645+ r . c . saveOutput ( a , coverProfTempFile ( a ) )
16451646 }
16461647
16471648 base .SetExitStatus (1 )
@@ -1736,7 +1737,18 @@ func (c *runCache) tryCacheWithID(b *work.Builder, a *work.Action, id string) bo
17361737 // Note that this list is documented above,
17371738 // so if you add to this list, update the docs too.
17381739 cacheArgs = append (cacheArgs , arg )
1739-
1740+ case "-test.coverprofile" ,
1741+ "-test.outputdir" :
1742+ // The `-coverprofile` and `-outputdir` arguments, which
1743+ // only affect the location of profile output, are cacheable. This
1744+ // is based on the process where, upon a cache hit, stored profile
1745+ // data is copied to the specified output location. This mechanism
1746+ // ensures that output location preferences are honored without
1747+ // modifying the profile's content, thereby justifying their
1748+ // cacheability without impacting cache integrity. This allows
1749+ // cached coverage profiles to be written to different files.
1750+ // Note that this list is documented above,
1751+ // so if you add to this list, update the docs too.
17401752 default :
17411753 // nothing else is cacheable
17421754 if cache .DebugTest {
@@ -1848,6 +1860,21 @@ func (c *runCache) tryCacheWithID(b *work.Builder, a *work.Action, id string) bo
18481860 j ++
18491861 }
18501862 c .buf .Write (data [j :])
1863+
1864+ // Write coverage data to profile.
1865+ if cfg .BuildCover {
1866+ // The cached coverprofile has the same expiration time as the
1867+ // test result it corresponds to. That time is already checked
1868+ // above, so we can ignore the entry returned by GetFile here.
1869+ f , _ , err := cache .GetFile (cache .Default (), coverProfileAndInputKey (testID , testInputsID ))
1870+ if err != nil {
1871+ if cache .DebugTest {
1872+ fmt .Fprintf (os .Stderr , "testcache: %s: test coverage profile not found: %v\n " , a .Package .ImportPath , err )
1873+ }
1874+ return false
1875+ }
1876+ mergeCoverProfile (f )
1877+ }
18511878 return true
18521879}
18531880
@@ -1996,7 +2023,12 @@ func testAndInputKey(testID, testInputsID cache.ActionID) cache.ActionID {
19962023 return cache .Subkey (testID , fmt .Sprintf ("inputs:%x" , testInputsID ))
19972024}
19982025
1999- func (c * runCache ) saveOutput (a * work.Action ) {
2026+ // coverProfileAndInputKey returns the "coverprofile" cache key for the pair (testID, testInputsID).
2027+ func coverProfileAndInputKey (testID , testInputsID cache.ActionID ) cache.ActionID {
2028+ return cache .Subkey (testAndInputKey (testID , testInputsID ), "coverprofile" )
2029+ }
2030+
2031+ func (c * runCache ) saveOutput (a * work.Action , coverProfileFile string ) {
20002032 if c .id1 == (cache.ActionID {}) && c .id2 == (cache.ActionID {}) {
20012033 return
20022034 }
@@ -2017,19 +2049,37 @@ func (c *runCache) saveOutput(a *work.Action) {
20172049 if err != nil {
20182050 return
20192051 }
2052+ var saveCoverProfile = func (testID cache.ActionID ) {}
2053+ if coverProfileFile != "" {
2054+ coverProfile , err := os .Open (coverProfileFile )
2055+ if err == nil {
2056+ saveCoverProfile = func (testID cache.ActionID ) {
2057+ cache .Default ().Put (coverProfileAndInputKey (testID , testInputsID ), coverProfile )
2058+ }
2059+ defer func () {
2060+ if err := coverProfile .Close (); err != nil && cache .DebugTest {
2061+ fmt .Fprintf (os .Stderr , "testcache: %s: closing temporary coverprofile: %v" , a .Package .ImportPath , err )
2062+ }
2063+ }()
2064+ } else if cache .DebugTest {
2065+ fmt .Fprintf (os .Stderr , "testcache: %s: failed to open temporary coverprofile: %s" , a .Package .ImportPath , err )
2066+ }
2067+ }
20202068 if c .id1 != (cache.ActionID {}) {
20212069 if cache .DebugTest {
20222070 fmt .Fprintf (os .Stderr , "testcache: %s: save test ID %x => input ID %x => %x\n " , a .Package .ImportPath , c .id1 , testInputsID , testAndInputKey (c .id1 , testInputsID ))
20232071 }
20242072 cache .PutNoVerify (cache .Default (), c .id1 , bytes .NewReader (testlog ))
20252073 cache .PutNoVerify (cache .Default (), testAndInputKey (c .id1 , testInputsID ), bytes .NewReader (a .TestOutput .Bytes ()))
2074+ saveCoverProfile (c .id1 )
20262075 }
20272076 if c .id2 != (cache.ActionID {}) {
20282077 if cache .DebugTest {
20292078 fmt .Fprintf (os .Stderr , "testcache: %s: save test ID %x => input ID %x => %x\n " , a .Package .ImportPath , c .id2 , testInputsID , testAndInputKey (c .id2 , testInputsID ))
20302079 }
20312080 cache .PutNoVerify (cache .Default (), c .id2 , bytes .NewReader (testlog ))
20322081 cache .PutNoVerify (cache .Default (), testAndInputKey (c .id2 , testInputsID ), bytes .NewReader (a .TestOutput .Bytes ()))
2082+ saveCoverProfile (c .id2 )
20332083 }
20342084}
20352085
0 commit comments