@@ -10,6 +10,7 @@ import (
10
10
"fmt"
11
11
"io/ioutil"
12
12
"net/http"
13
+ "net/http/httptest"
13
14
"sync"
14
15
"time"
15
16
@@ -24,6 +25,12 @@ type ValgrindCLI struct {
24
25
Timeout time.Duration
25
26
}
26
27
28
+ type ValgrindCGI struct {
29
+ CGI
30
+ Valgrind string
31
+ Timeout time.Duration
32
+ }
33
+
27
34
func (tx * ValgrindCLI ) Execute () (http.Header , []byte , error ) {
28
35
if len (tx .Path ) == 0 {
29
36
return nil , []byte ("skip: executable not specified" ), nil
@@ -34,8 +41,13 @@ func (tx *ValgrindCLI) Execute() (http.Header, []byte, error) {
34
41
// valgrind reports to their tests in the same way we use the appname
35
42
// to link the transaction data. Failing that, perhaps we could use
36
43
// 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()
39
51
40
52
cmd := valgrind .Memcheck (tx .Valgrind , "--quiet" )
41
53
cmd .Args = append (cmd .Args , "--xml=yes" )
@@ -105,6 +117,87 @@ func (tx *ValgrindCLI) Execute() (http.Header, []byte, error) {
105
117
return nil , output , err
106
118
}
107
119
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
+
108
201
// resultOrError is a poor man's sum type.
109
202
type resultOrError struct {
110
203
R * valgrind.Report
0 commit comments