@@ -10,6 +10,7 @@ import (
1010 "fmt"
1111 "io/ioutil"
1212 "net/http"
13+ "net/http/httptest"
1314 "sync"
1415 "time"
1516
@@ -24,6 +25,12 @@ type ValgrindCLI struct {
2425 Timeout time.Duration
2526}
2627
28+ type ValgrindCGI struct {
29+ CGI
30+ Valgrind string
31+ Timeout time.Duration
32+ }
33+
2734func (tx * ValgrindCLI ) Execute () (http.Header , []byte , error ) {
2835 if len (tx .Path ) == 0 {
2936 return nil , []byte ("skip: executable not specified" ), nil
@@ -34,8 +41,13 @@ func (tx *ValgrindCLI) Execute() (http.Header, []byte, error) {
3441 // valgrind reports to their tests in the same way we use the appname
3542 // to link the transaction data. Failing that, perhaps we could use
3643 // the valgrind process's pid instead.
37- valgrindMu .Lock ()
38- defer valgrindMu .Unlock ()
44+ //
45+ // We prevent concurrent invocations by disallowing valgrind when
46+ // the threads argument is greater than 1. Previously, we used the
47+ // commented out locks below. This was changed to allow subprocesses
48+ // (i.e. curl) to run without deadlocking.
49+ //valgrindMu.Lock()
50+ //defer valgrindMu.Unlock()
3951
4052 cmd := valgrind .Memcheck (tx .Valgrind , "--quiet" )
4153 cmd .Args = append (cmd .Args , "--xml=yes" )
@@ -105,6 +117,87 @@ func (tx *ValgrindCLI) Execute() (http.Header, []byte, error) {
105117 return nil , output , err
106118}
107119
120+ func (tx * ValgrindCGI ) Execute () (http.Header , []byte , error ) {
121+ if len (tx .handler .Path ) == 0 {
122+ return nil , []byte ("skip: executable not specified" ), nil
123+ }
124+
125+ // For now, we don't have a mechanism to handle concurrent invocations
126+ // of Valgrind. In the future, we could use the appname to connect
127+ // valgrind reports to their tests in the same way we use the appname
128+ // to link the transaction data. Failing that, perhaps we could use
129+ // the valgrind process's pid instead.
130+ //
131+ // We prevent concurrent invocations by disallowing valgrind when
132+ // the threads argument is greater than 1. Previously, we used the
133+ // commented out locks below. This was changed to allow subprocesses
134+ // (i.e. curl) to run without deadlocking.
135+ //valgrindMu.Lock()
136+ //defer valgrindMu.Unlock()
137+
138+ cmd := valgrind .Memcheck (tx .Valgrind , "--quiet" )
139+ cmd .Args = append (cmd .Args , "--xml=yes" )
140+ cmd .Args = append (cmd .Args , "--xml-socket=" + valgrindLn .Addr ().String ())
141+ cmd .Args = append (cmd .Args , "--" )
142+ cmd .Args = append (cmd .Args , tx .handler .Path )
143+ if len (tx .handler .Args ) > 0 {
144+ cmd .Args = append (cmd .Args , tx .handler .Args ... )
145+ }
146+
147+ log .Debugf ("command: %v" , cmd )
148+
149+ // Replace the handler with valgrind
150+ tx .handler .Path = cmd .Path
151+ // The first Arg is the Path.
152+ // ServeHTTP will re-add Path
153+ // to the front of Args
154+ tx .handler .Args = cmd .Args [1 :]
155+
156+ ch := make (chan resultOrError , 1 )
157+ go func () {
158+ var result resultOrError
159+ result .R , result .E = acceptOneReport (tx .Timeout )
160+ ch <- result
161+ }()
162+
163+ resp := httptest .NewRecorder ()
164+ tx .handler .ServeHTTP (resp , tx .request )
165+
166+ vgOutput := <- ch
167+
168+ // Append the output from Valgrind to the test output.
169+ //
170+ // TODO: Eventually we want to report valgrind output separately, so we can
171+ // treat valgrind errors similarly to failed test expectations. i.e. We'd
172+ // like to add them to Test.Failures. After all, each test has an implicit
173+ // expectation that it will not exhibit memory bugs!
174+ //
175+ // TODO: Once the former is in place, we should be able to parse the memory
176+ // leak reports produced by the Zend Memory Manager from the test output and
177+ // report them the same way as valgrind errors.
178+ output := resp .Body .Bytes ()
179+ if vgOutput .R != nil && len (vgOutput .R .Errors ) > 0 {
180+ // Safe to ignore the error here, Report.MarshalText() never fails.
181+ data , _ := vgOutput .R .MarshalText ()
182+ output = append (output , '\n' )
183+ output = append (output , data ... )
184+ }
185+
186+ // Ensure a non-nil error is returned when valgrind detects errors.
187+ // Otherwise, the test could be marked as passing if it does not have
188+ // any expectations on the test output. This sucks.
189+ //
190+ // TODO: Remove this when valgrind errors can be treated as failed test
191+ // expectations.
192+ err := vgOutput .E
193+ if err == nil && vgOutput .R != nil && len (vgOutput .R .Errors ) > 0 {
194+ err = fmt .Errorf ("detected %d memory errors" , len (vgOutput .R .Errors ))
195+ }
196+
197+ return resp .HeaderMap , output , err
198+
199+ }
200+
108201// resultOrError is a poor man's sum type.
109202type resultOrError struct {
110203 R * valgrind.Report
0 commit comments