Merge lp:~fgimenez/snappy/api-packages-test into lp:~snappy-dev/snappy/snappy-moved-to-github
- api-packages-test
- Merge into snappy-moved-to-github
Status: | Rejected |
---|---|
Rejected by: | Federico Gimenez |
Proposed branch: | lp:~fgimenez/snappy/api-packages-test |
Merge into: | lp:~snappy-dev/snappy/snappy-moved-to-github |
Diff against target: |
1021 lines (+689/-152) 9 files modified
_integration-tests/tests/build_test.go (+41/-48) _integration-tests/tests/snapd_1_0_packages_test.go (+97/-0) _integration-tests/tests/snapd_1_0_test.go (+41/-0) _integration-tests/tests/snapd_root_test.go (+41/-0) _integration-tests/tests/snapd_test.go (+183/-32) _integration-tests/testutils/build/snap.go (+71/-0) _integration-tests/testutils/build/snap_test.go (+82/-0) _integration-tests/testutils/wait/wait.go (+23/-8) _integration-tests/testutils/wait/wait_test.go (+110/-64) |
To merge this branch: | bzr merge lp:~fgimenez/snappy/api-packages-test |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Leo Arias (community) | Needs Fixing | ||
Review via email: mp+272064@code.launchpad.net |
Commit message
API 1.0/packages test
Description of the change
API 1.0/packages test
Besides the tests themselves these are the main things going on with the changes:
* The apiExerciser interface has been added, the suites for testing API resources should implement this interface and just call exerciseApi(). There are other interfaces for each http verb, depending on which ones are implemented the tests are executed differently.
* The apiInteraction type allows to describe how to test one api resource, when implementing an http verb interface the suites just need to return an array of those, each element has fields like responsePattern, responseObject (this is populated via json.Unmarshal if present), payload if needed (as a generic io.Reader), and, when there's an async call involved, waitResponsePattern and waitFunction
* A wait.ForFunction has been added to the wait package, for the previous functionality to work. The wait.ForCommand has been updated to use under covers ForFunction and the unit tests have been modified to prevent a delay that we were having.
* The functions used to build snaps have been moved to the build package.
- 681. By Federico Gimenez
-
testing methods not allowed; payload as string
- 682. By Federico Gimenez
-
Removed setup and teardown from the apiExerciser interface, using SetUpTest and TearDownTest fixtures from check; comments
Federico Gimenez (fgimenez) wrote : | # |
Thanks for your comments Leo, they should be addressed now.
I'll try now to split this in several mp's to make it manageable
Federico Gimenez (fgimenez) wrote : | # |
Ok, it's already split (order matters):
https:/
https:/
https:/
https:/
https:/
Thanks!
Unmerged revisions
- 682. By Federico Gimenez
-
Removed setup and teardown from the apiExerciser interface, using SetUpTest and TearDownTest fixtures from check; comments
- 681. By Federico Gimenez
-
testing methods not allowed; payload as string
- 680. By Federico Gimenez
-
reverted image changes
- 679. By Federico Gimenez
-
doing the 404 check for each resource
- 678. By Federico Gimenez
-
some comments
- 677. By Federico Gimenez
-
moved package related types to the test file
- 676. By Federico Gimenez
-
/1.0/packages resource complete
- 675. By Federico Gimenez
-
/1.0 resource complete
- 674. By Federico Gimenez
-
interfaces definition; root and 404 complete
- 673. By Federico Gimenez
-
removed extra line
Preview Diff
1 | === modified file '_integration-tests/tests/build_test.go' |
2 | --- _integration-tests/tests/build_test.go 2015-07-30 07:54:08 +0000 |
3 | +++ _integration-tests/tests/build_test.go 2015-09-28 08:47:40 +0000 |
4 | @@ -23,68 +23,61 @@ |
5 | "fmt" |
6 | "os" |
7 | "os/exec" |
8 | - |
9 | - . "launchpad.net/snappy/_integration-tests/testutils/common" |
10 | - |
11 | - . "gopkg.in/check.v1" |
12 | -) |
13 | - |
14 | -const ( |
15 | - baseSnapPath = "_integration-tests/data/snaps" |
16 | - basicSnapName = "basic" |
17 | - wrongYamlSnapName = "wrong-yaml" |
18 | - missingReadmeSnapName = "missing-readme" |
19 | -) |
20 | - |
21 | -var _ = Suite(&buildSuite{}) |
22 | + "path/filepath" |
23 | + |
24 | + "launchpad.net/snappy/_integration-tests/testutils/build" |
25 | + "launchpad.net/snappy/_integration-tests/testutils/common" |
26 | + |
27 | + "gopkg.in/check.v1" |
28 | +) |
29 | + |
30 | +var _ = check.Suite(&buildSuite{}) |
31 | |
32 | type buildSuite struct { |
33 | - SnappySuite |
34 | -} |
35 | - |
36 | -func buildSnap(c *C, snapPath string) string { |
37 | - return ExecCommand(c, "snappy", "build", snapPath) |
38 | -} |
39 | - |
40 | -func (s *buildSuite) TestBuildBasicSnapOnSnappy(c *C) { |
41 | + common.SnappySuite |
42 | +} |
43 | + |
44 | +func (s *buildSuite) TestBuildBasicSnapOnSnappy(c *check.C) { |
45 | // build basic snap and check output |
46 | - snapPath := baseSnapPath + "/" + basicSnapName |
47 | - buildOutput := buildSnap(c, snapPath) |
48 | - snapName := basicSnapName + "_1.0_all.snap" |
49 | - expected := fmt.Sprintf("Generated '%s' snap\n", snapName) |
50 | - c.Check(buildOutput, Equals, expected) |
51 | - defer os.Remove(snapPath + "/" + snapName) |
52 | + snapPath, err := build.LocalSnap(c, build.BasicSnapName) |
53 | + defer os.Remove(snapPath) |
54 | + c.Assert(err, check.IsNil) |
55 | + |
56 | + snapName := filepath.Base(snapPath) |
57 | |
58 | // install built snap and check output |
59 | - installOutput := InstallSnap(c, snapName) |
60 | - defer RemoveSnap(c, basicSnapName) |
61 | - expected = "(?ms)" + |
62 | + installOutput := common.InstallSnap(c, snapName) |
63 | + defer common.RemoveSnap(c, build.BasicSnapName) |
64 | + expected := "(?ms)" + |
65 | "Installing " + snapName + "\n" + |
66 | ".*Signature check failed, but installing anyway as requested\n" + |
67 | "Name +Date +Version +Developer \n" + |
68 | ".*\n" + |
69 | - basicSnapName + " +.* +.* +sideload \n" + |
70 | + build.BasicSnapName + " +.* +.* +sideload \n" + |
71 | ".*\n" |
72 | |
73 | - c.Check(installOutput, Matches, expected) |
74 | + c.Check(installOutput, check.Matches, expected) |
75 | |
76 | // teardown, remove snap file |
77 | - c.Assert(os.Remove(snapName), IsNil, Commentf("Error removing %s", snapName)) |
78 | -} |
79 | - |
80 | -func (s *buildSuite) TestBuildWrongYamlSnapOnSnappy(c *C) { |
81 | - commonWrongTest(c, wrongYamlSnapName, "(?msi).*Can not parse.*yaml: line 2: mapping values are not allowed in this context.*") |
82 | -} |
83 | - |
84 | -func (s *buildSuite) TestBuildMissingReadmeSnapOnSnappy(c *C) { |
85 | - commonWrongTest(c, missingReadmeSnapName, ".*readme.md: no such file or directory\n") |
86 | -} |
87 | - |
88 | -func commonWrongTest(c *C, testName, expected string) { |
89 | + c.Assert(os.Remove(snapName), check.IsNil, |
90 | + check.Commentf("Error removing %s", snapName)) |
91 | +} |
92 | + |
93 | +func (s *buildSuite) TestBuildWrongYamlSnapOnSnappy(c *check.C) { |
94 | + commonWrongTest(c, build.WrongYamlSnapName, |
95 | + "(?msi).*Can not parse.*yaml: line 2: mapping values are not allowed in this context.*") |
96 | +} |
97 | + |
98 | +func (s *buildSuite) TestBuildMissingReadmeSnapOnSnappy(c *check.C) { |
99 | + commonWrongTest(c, build.MissingReadmeSnapName, |
100 | + ".*readme.md: no such file or directory\n") |
101 | +} |
102 | + |
103 | +func commonWrongTest(c *check.C, testName, expected string) { |
104 | // build wrong snap and check output |
105 | - cmd := exec.Command("snappy", "build", fmt.Sprintf("%s/%s", baseSnapPath, testName)) |
106 | + cmd := exec.Command("snappy", "build", fmt.Sprintf("%s/%s", build.BaseSnapPath, testName)) |
107 | echoOutput, err := cmd.CombinedOutput() |
108 | - c.Assert(err, NotNil, Commentf("%s should not be built", testName)) |
109 | + c.Assert(err, check.NotNil, check.Commentf("%s should not be built", testName)) |
110 | |
111 | - c.Assert(string(echoOutput), Matches, expected) |
112 | + c.Assert(string(echoOutput), check.Matches, expected) |
113 | } |
114 | |
115 | === added file '_integration-tests/tests/snapd_1_0_packages_test.go' |
116 | --- _integration-tests/tests/snapd_1_0_packages_test.go 1970-01-01 00:00:00 +0000 |
117 | +++ _integration-tests/tests/snapd_1_0_packages_test.go 2015-09-28 08:47:40 +0000 |
118 | @@ -0,0 +1,97 @@ |
119 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
120 | + |
121 | +/* |
122 | + * Copyright (C) 2015 Canonical Ltd |
123 | + * |
124 | + * This program is free software: you can redistribute it and/or modify |
125 | + * it under the terms of the GNU General Public License version 3 as |
126 | + * published by the Free Software Foundation. |
127 | + * |
128 | + * This program is distributed in the hope that it will be useful, |
129 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
130 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
131 | + * GNU General Public License for more details. |
132 | + * |
133 | + * You should have received a copy of the GNU General Public License |
134 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
135 | + * |
136 | + */ |
137 | + |
138 | +package tests |
139 | + |
140 | +import ( |
141 | + "os" |
142 | + |
143 | + "launchpad.net/snappy/_integration-tests/testutils/build" |
144 | + |
145 | + "gopkg.in/check.v1" |
146 | +) |
147 | + |
148 | +var _ = check.Suite(&snapd10PackagesTestSuite{}) |
149 | + |
150 | +type pkgsResponse struct { |
151 | + Result pkgContainer |
152 | + response |
153 | +} |
154 | + |
155 | +type pkgContainer struct { |
156 | + Packages pkgItems |
157 | + Paging map[string]interface{} |
158 | +} |
159 | + |
160 | +type pkgItems map[string]pkgItem |
161 | + |
162 | +type pkgItem struct { |
163 | + Description string |
164 | + DownloadSize string `json:"download_size"` |
165 | + Icon string |
166 | + InstalledSize string `json:"installed_size"` |
167 | + Name string |
168 | + Origin string |
169 | + Resource string |
170 | + Status string |
171 | + Type string |
172 | + Vendor string |
173 | + Version string |
174 | +} |
175 | + |
176 | +type snapd10PackagesTestSuite struct { |
177 | + snapdTestSuite |
178 | + snapPath string |
179 | +} |
180 | + |
181 | +func (s *snapd10PackagesTestSuite) SetUpTest(c *check.C) { |
182 | + s.snapdTestSuite.SetUpTest(c) |
183 | + var err error |
184 | + s.snapPath, err = build.LocalSnap(c, build.BasicSnapName) |
185 | + c.Assert(err, check.IsNil) |
186 | + |
187 | +} |
188 | + |
189 | +func (s *snapd10PackagesTestSuite) TearDownTest(c *check.C) { |
190 | + s.snapdTestSuite.TearDownTest(c) |
191 | + os.Remove(s.snapPath) |
192 | +} |
193 | + |
194 | +func (s *snapd10PackagesTestSuite) resource() string { |
195 | + return baseURL + "/1.0/packages" |
196 | +} |
197 | + |
198 | +func (s *snapd10PackagesTestSuite) TestResource(c *check.C) { |
199 | + exerciseAPI(c, s) |
200 | +} |
201 | + |
202 | +func (s *snapd10PackagesTestSuite) getInteractions() apiInteractions { |
203 | + return []apiInteraction{{ |
204 | + responseObject: &pkgsResponse{}}} |
205 | +} |
206 | + |
207 | +func (s *snapd10PackagesTestSuite) postInteractions() apiInteractions { |
208 | + return []apiInteraction{{ |
209 | + payload: s.snapPath, |
210 | + waitPattern: `(?U){.*,"status":"active".*"status":"OK","status_code":200,"type":"sync"}`, |
211 | + waitFunction: func() (string, error) { |
212 | + output, err := genericRequest(s.resource()+"/"+build.BasicSnapName+".sideload", "GET", nil) |
213 | + return string(output), err |
214 | + }}} |
215 | +} |
216 | |
217 | === added file '_integration-tests/tests/snapd_1_0_test.go' |
218 | --- _integration-tests/tests/snapd_1_0_test.go 1970-01-01 00:00:00 +0000 |
219 | +++ _integration-tests/tests/snapd_1_0_test.go 2015-09-28 08:47:40 +0000 |
220 | @@ -0,0 +1,41 @@ |
221 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
222 | + |
223 | +/* |
224 | + * Copyright (C) 2015 Canonical Ltd |
225 | + * |
226 | + * This program is free software: you can redistribute it and/or modify |
227 | + * it under the terms of the GNU General Public License version 3 as |
228 | + * published by the Free Software Foundation. |
229 | + * |
230 | + * This program is distributed in the hope that it will be useful, |
231 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
232 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
233 | + * GNU General Public License for more details. |
234 | + * |
235 | + * You should have received a copy of the GNU General Public License |
236 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
237 | + * |
238 | + */ |
239 | + |
240 | +package tests |
241 | + |
242 | +import "gopkg.in/check.v1" |
243 | + |
244 | +var _ = check.Suite(&snapd10TestSuite{}) |
245 | + |
246 | +type snapd10TestSuite struct { |
247 | + snapdTestSuite |
248 | +} |
249 | + |
250 | +func (s *snapd10TestSuite) TestResource(c *check.C) { |
251 | + exerciseAPI(c, s) |
252 | +} |
253 | + |
254 | +func (s *snapd10TestSuite) resource() string { |
255 | + return baseURL + "/1.0" |
256 | +} |
257 | + |
258 | +func (s *snapd10TestSuite) getInteractions() apiInteractions { |
259 | + return []apiInteraction{{ |
260 | + responsePattern: `(?U){"result":{"api_compat":"0","default_channel":".*","flavor":".*","release":".*"},"status":"OK","status_code":200,"type":"sync"}`}} |
261 | +} |
262 | |
263 | === added file '_integration-tests/tests/snapd_root_test.go' |
264 | --- _integration-tests/tests/snapd_root_test.go 1970-01-01 00:00:00 +0000 |
265 | +++ _integration-tests/tests/snapd_root_test.go 2015-09-28 08:47:40 +0000 |
266 | @@ -0,0 +1,41 @@ |
267 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
268 | + |
269 | +/* |
270 | + * Copyright (C) 2015 Canonical Ltd |
271 | + * |
272 | + * This program is free software: you can redistribute it and/or modify |
273 | + * it under the terms of the GNU General Public License version 3 as |
274 | + * published by the Free Software Foundation. |
275 | + * |
276 | + * This program is distributed in the hope that it will be useful, |
277 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
278 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
279 | + * GNU General Public License for more details. |
280 | + * |
281 | + * You should have received a copy of the GNU General Public License |
282 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
283 | + * |
284 | + */ |
285 | + |
286 | +package tests |
287 | + |
288 | +import "gopkg.in/check.v1" |
289 | + |
290 | +var _ = check.Suite(&snapdRootTestSuite{}) |
291 | + |
292 | +type snapdRootTestSuite struct { |
293 | + snapdTestSuite |
294 | +} |
295 | + |
296 | +func (s *snapdRootTestSuite) TestResource(c *check.C) { |
297 | + exerciseAPI(c, s) |
298 | +} |
299 | + |
300 | +func (s *snapdRootTestSuite) resource() string { |
301 | + return baseURL + "/" |
302 | +} |
303 | + |
304 | +func (s *snapdRootTestSuite) getInteractions() apiInteractions { |
305 | + return []apiInteraction{{ |
306 | + responsePattern: `(?U){"result":\[".*"\],"status":"OK","status_code":200,"type":"sync"}`}} |
307 | +} |
308 | |
309 | === modified file '_integration-tests/tests/snapd_test.go' |
310 | --- _integration-tests/tests/snapd_test.go 2015-09-15 16:07:54 +0000 |
311 | +++ _integration-tests/tests/snapd_test.go 2015-09-28 08:47:40 +0000 |
312 | @@ -20,10 +20,15 @@ |
313 | package tests |
314 | |
315 | import ( |
316 | + "encoding/json" |
317 | + "io" |
318 | "io/ioutil" |
319 | + "log" |
320 | "net/http" |
321 | + "os" |
322 | "os/exec" |
323 | "strconv" |
324 | + "strings" |
325 | |
326 | "launchpad.net/snappy/_integration-tests/testutils/common" |
327 | "launchpad.net/snappy/_integration-tests/testutils/wait" |
328 | @@ -32,7 +37,10 @@ |
329 | ) |
330 | |
331 | // make sure that there are no collisions |
332 | -const port = "9999" |
333 | +const ( |
334 | + port = "9999" |
335 | + baseURL = "http://127.0.0.1:" + port |
336 | +) |
337 | |
338 | var _ = check.Suite(&snapdTestSuite{}) |
339 | |
340 | @@ -43,7 +51,7 @@ |
341 | |
342 | func (s *snapdTestSuite) SetUpTest(c *check.C) { |
343 | s.SnappySuite.SetUpTest(c) |
344 | - s.cmd = exec.Command("/lib/systemd/systemd-activate", |
345 | + s.cmd = exec.Command("sudo", "/lib/systemd/systemd-activate", |
346 | "-l", "0.0.0.0:"+port, "snapd") |
347 | |
348 | s.cmd.Start() |
349 | @@ -62,34 +70,177 @@ |
350 | } |
351 | } |
352 | |
353 | -func (s *snapdTestSuite) TestServiceIsUp(c *check.C) { |
354 | - resp, err := http.Get("http://127.0.0.1:" + port) |
355 | - c.Assert(err, check.IsNil) |
356 | - defer resp.Body.Close() |
357 | - |
358 | - body, err := ioutil.ReadAll(resp.Body) |
359 | - c.Assert(err, check.IsNil) |
360 | - |
361 | - expected := `{"result":["/1.0"],"status":"OK","status_code":200,"type":"sync"}` |
362 | - c.Assert(string(body), check.Equals, expected) |
363 | -} |
364 | - |
365 | -func (s *snapdTestSuite) TestResources(c *check.C) { |
366 | - resources := []struct { |
367 | - path string |
368 | - respPattern string |
369 | - }{{"/", `(?U){"result":\[".*"\],"status":"OK","status_code":200,"type":"sync"}`}, |
370 | - {"/1.0", `(?U){"result":{"api_compat":"0","default_channel":".*","flavor":".*","release":".*"},"status":"OK","status_code":200,"type":"sync"}`}, |
371 | - {"/not-a-resource", `{"result":{},"status":"Not Found","status_code":404,"type":"error"}`}} |
372 | - |
373 | - for _, resource := range resources { |
374 | - resp, err := http.Get("http://127.0.0.1:" + port + resource.path) |
375 | - c.Check(err, check.IsNil) |
376 | - defer resp.Body.Close() |
377 | - |
378 | - body, err := ioutil.ReadAll(resp.Body) |
379 | - c.Check(err, check.IsNil) |
380 | - |
381 | - c.Check(string(body), check.Matches, resource.respPattern) |
382 | - } |
383 | +type response struct { |
384 | + Result interface{} |
385 | + Status string |
386 | + Type string |
387 | + StatusCode int `json:"status_code"` |
388 | +} |
389 | + |
390 | +type asyncResponse struct { |
391 | + Result asyncResult |
392 | + response |
393 | +} |
394 | + |
395 | +type asyncResult struct { |
396 | + CreatedAt string `json:"created_at"` |
397 | + MayCancel bool `json:"may_cancel"` |
398 | + Output string |
399 | + Resource string |
400 | + Status string |
401 | + UpdatedAt string `json:"updated_at"` |
402 | +} |
403 | + |
404 | +// type for describing interactions with the api |
405 | +type apiInteraction struct { |
406 | + // payload can describe a file path or be the content to be appended to the |
407 | + // body of the request |
408 | + payload string |
409 | + // expected pattern for the response |
410 | + responsePattern string |
411 | + // expected structure of the response |
412 | + responseObject interface{} |
413 | + // for async request, the test will keep executing the waitFunction |
414 | + // until the waitPattern is received or a timeout expires |
415 | + waitPattern string |
416 | + waitFunction func() (string, error) |
417 | +} |
418 | + |
419 | +type apiInteractions []apiInteraction |
420 | + |
421 | +// all the api test suites must satisfy this interface |
422 | +type apiExerciser interface { |
423 | + // returns the path of the resource to be tested, as in "/1.0/packages" |
424 | + resource() string |
425 | +} |
426 | + |
427 | +// concrete interface for apis exercising GET |
428 | +type apiGetExerciser interface { |
429 | + getInteractions() apiInteractions |
430 | +} |
431 | + |
432 | +// concrete interface for apis exercising POST |
433 | +type apiPostExerciser interface { |
434 | + postInteractions() apiInteractions |
435 | +} |
436 | + |
437 | +// concrete interface for apis exercising PUT |
438 | +type apiPutExerciser interface { |
439 | + putInteractions() apiInteractions |
440 | +} |
441 | + |
442 | +// concrete interface for apis exercising DELETE |
443 | +type apiDeleteExerciser interface { |
444 | + deleteInteractions() apiInteractions |
445 | +} |
446 | + |
447 | +// this is the entry point for all the api tests |
448 | +func exerciseAPI(c *check.C, a apiExerciser) { |
449 | + resource := a.resource() |
450 | + |
451 | + do404(c, resource) |
452 | + |
453 | + if getInstance, ok := a.(apiGetExerciser); ok { |
454 | + doInteractions(c, resource, "GET", getInstance.getInteractions()) |
455 | + } else { |
456 | + doMethodNotAllowed(c, resource, "GET") |
457 | + } |
458 | + |
459 | + if postInstance, ok := a.(apiPostExerciser); ok { |
460 | + doInteractions(c, resource, "POST", postInstance.postInteractions()) |
461 | + } else { |
462 | + doMethodNotAllowed(c, resource, "POST") |
463 | + } |
464 | + |
465 | + if putInstance, ok := a.(apiPutExerciser); ok { |
466 | + doInteractions(c, resource, "PUT", putInstance.putInteractions()) |
467 | + } else { |
468 | + doMethodNotAllowed(c, resource, "PUT") |
469 | + } |
470 | + |
471 | + if deleteInstance, ok := a.(apiDeleteExerciser); ok { |
472 | + doInteractions(c, resource, "DELETE", deleteInstance.deleteInteractions()) |
473 | + } else { |
474 | + doMethodNotAllowed(c, resource, "DELETE") |
475 | + } |
476 | +} |
477 | + |
478 | +func doInteractions(c *check.C, resource, verb string, interactions apiInteractions) { |
479 | + log.Printf("*** Exercising API for resource %s with verb %s", resource, verb) |
480 | + |
481 | + for _, interaction := range interactions { |
482 | + doInteraction(c, resource, verb, interaction) |
483 | + } |
484 | +} |
485 | + |
486 | +// all the interactions are dispatched with this function |
487 | +func doInteraction(c *check.C, resource, verb string, interaction apiInteraction) { |
488 | + log.Printf("** Trying interaction %v", interaction) |
489 | + |
490 | + var ( |
491 | + payload io.Reader |
492 | + err error |
493 | + ) |
494 | + if _, err = os.Stat(interaction.payload); os.IsNotExist(err) { |
495 | + // The payload is not a file. Treat it as a string |
496 | + payload = strings.NewReader(interaction.payload) |
497 | + } else { |
498 | + payload, err = os.Open(interaction.payload) |
499 | + f, _ := payload.(*os.File) |
500 | + defer f.Close() |
501 | + } |
502 | + |
503 | + body, err := genericRequest(resource, verb, payload) |
504 | + c.Check(err, check.IsNil) |
505 | + |
506 | + if interaction.responseObject == nil { |
507 | + interaction.responseObject = &response{} |
508 | + } |
509 | + err = json.Unmarshal(body, interaction.responseObject) |
510 | + c.Check(err, check.IsNil) |
511 | + |
512 | + if interaction.responsePattern != "" { |
513 | + c.Check(string(body), check.Matches, interaction.responsePattern) |
514 | + } |
515 | + if interaction.waitPattern != "" { |
516 | + err = wait.ForFunction(c, interaction.waitPattern, interaction.waitFunction) |
517 | + c.Check(err, check.IsNil) |
518 | + } |
519 | +} |
520 | + |
521 | +func do404(c *check.C, resource string) { |
522 | + doInteraction(c, |
523 | + resource+"/not-a-resource", |
524 | + "GET", |
525 | + apiInteraction{ |
526 | + responsePattern: `{"result":{},"status":"Not Found","status_code":404,"type":"error"}`}) |
527 | +} |
528 | + |
529 | +func doMethodNotAllowed(c *check.C, resource, verb string) { |
530 | + doInteraction(c, |
531 | + resource, |
532 | + verb, |
533 | + apiInteraction{ |
534 | + responsePattern: `{"result":{},"status":"Method Not Allowed","status_code":405,"type":"error"}`}) |
535 | +} |
536 | + |
537 | +// this is the function which makes requests to the api |
538 | +func genericRequest(resource, verb string, payload io.Reader) (body []byte, err error) { |
539 | + if file, ok := payload.(*os.File); ok { |
540 | + defer file.Close() |
541 | + } |
542 | + |
543 | + req, err := http.NewRequest(verb, resource, payload) |
544 | + if err != nil { |
545 | + return |
546 | + } |
547 | + req.Header.Add("X-Allow-Unsigned", "1") |
548 | + |
549 | + client := &http.Client{} |
550 | + resp, err := client.Do(req) |
551 | + |
552 | + if err != nil { |
553 | + return |
554 | + } |
555 | + return ioutil.ReadAll(resp.Body) |
556 | } |
557 | |
558 | === added file '_integration-tests/testutils/build/snap.go' |
559 | --- _integration-tests/testutils/build/snap.go 1970-01-01 00:00:00 +0000 |
560 | +++ _integration-tests/testutils/build/snap.go 2015-09-28 08:47:40 +0000 |
561 | @@ -0,0 +1,71 @@ |
562 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
563 | + |
564 | +/* |
565 | + * Copyright (C) 2015 Canonical Ltd |
566 | + * |
567 | + * This program is free software: you can redistribute it and/or modify |
568 | + * it under the terms of the GNU General Public License version 3 as |
569 | + * published by the Free Software Foundation. |
570 | + * |
571 | + * This program is distributed in the hope that it will be useful, |
572 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
573 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
574 | + * GNU General Public License for more details. |
575 | + * |
576 | + * You should have received a copy of the GNU General Public License |
577 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
578 | + * |
579 | + */ |
580 | + |
581 | +package build |
582 | + |
583 | +import ( |
584 | + "fmt" |
585 | + "path/filepath" |
586 | + |
587 | + "gopkg.in/check.v1" |
588 | + "launchpad.net/snappy/_integration-tests/testutils/common" |
589 | +) |
590 | + |
591 | +const ( |
592 | + // BaseSnapPath is the path for the snap sources used in testing |
593 | + BaseSnapPath = "_integration-tests/data/snaps" |
594 | + // BasicSnapName is the name of the basic snap |
595 | + BasicSnapName = "basic" |
596 | + // WrongYamlSnapName is the name of a snap with an invalid meta yaml |
597 | + WrongYamlSnapName = "wrong-yaml" |
598 | + // MissingReadmeSnapName is the name of a snap without readme |
599 | + MissingReadmeSnapName = "missing-readme" |
600 | + |
601 | + snapFilenameSufix = "_1.0_all.snap" |
602 | +) |
603 | + |
604 | +var ( |
605 | + // dependency aliasing |
606 | + commonExecCommand = common.ExecCommand |
607 | +) |
608 | + |
609 | +func buildSnap(c *check.C, snapPath string) string { |
610 | + return commonExecCommand(c, "snappy", "build", snapPath, "-o", snapPath) |
611 | +} |
612 | + |
613 | +// LocalSnap issues the command to build a snap and returns the path of the generated file |
614 | +func LocalSnap(c *check.C, snapName string) (snapPath string, err error) { |
615 | + // build basic snap and check output |
616 | + buildPath := buildPath(snapName) |
617 | + |
618 | + buildOutput := buildSnap(c, buildPath) |
619 | + snapName = snapName + snapFilenameSufix |
620 | + |
621 | + path := filepath.Join(buildPath, snapName) |
622 | + expected := fmt.Sprintf("Generated '%s' snap\n", path) |
623 | + if buildOutput != expected { |
624 | + return "", fmt.Errorf("Error building snap, expected output %s, obtained %s", |
625 | + expected, buildOutput) |
626 | + } |
627 | + return path, nil |
628 | +} |
629 | + |
630 | +func buildPath(snap string) string { |
631 | + return filepath.Join(BaseSnapPath, snap) |
632 | +} |
633 | |
634 | === added file '_integration-tests/testutils/build/snap_test.go' |
635 | --- _integration-tests/testutils/build/snap_test.go 1970-01-01 00:00:00 +0000 |
636 | +++ _integration-tests/testutils/build/snap_test.go 2015-09-28 08:47:40 +0000 |
637 | @@ -0,0 +1,82 @@ |
638 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
639 | + |
640 | +/* |
641 | + * Copyright (C) 2014-2015 Canonical Ltd |
642 | + * |
643 | + * This program is free software: you can redistribute it and/or modify |
644 | + * it under the terms of the GNU General Public License version 3 as |
645 | + * published by the Free Software Foundation. |
646 | + * |
647 | + * This program is distributed in the hope that it will be useful, |
648 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
649 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
650 | + * GNU General Public License for more details. |
651 | + * |
652 | + * You should have received a copy of the GNU General Public License |
653 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
654 | + * |
655 | + */ |
656 | + |
657 | +package build |
658 | + |
659 | +import ( |
660 | + "fmt" |
661 | + "path/filepath" |
662 | + "strings" |
663 | + |
664 | + "gopkg.in/check.v1" |
665 | +) |
666 | + |
667 | +type snapBuildTestSuite struct { |
668 | + execCalls map[string]int |
669 | + execReturnValue string |
670 | + backExecCommand func(*check.C, ...string) string |
671 | + defaultSnapName string |
672 | +} |
673 | + |
674 | +var _ = check.Suite(&snapBuildTestSuite{}) |
675 | + |
676 | +func (s *snapBuildTestSuite) SetUpSuite(c *check.C) { |
677 | + s.backExecCommand = commonExecCommand |
678 | + commonExecCommand = s.fakeExecCommand |
679 | + s.defaultSnapName = "mySnapName" |
680 | +} |
681 | + |
682 | +func (s *snapBuildTestSuite) TearDownSuite(c *check.C) { |
683 | + commonExecCommand = s.backExecCommand |
684 | +} |
685 | + |
686 | +func (s *snapBuildTestSuite) SetUpTest(c *check.C) { |
687 | + s.execCalls = make(map[string]int) |
688 | + snapName := s.defaultSnapName + snapFilenameSufix |
689 | + path := filepath.Join(buildPath(s.defaultSnapName), snapName) |
690 | + s.execReturnValue = fmt.Sprintf("Generated '%s' snap\n", path) |
691 | +} |
692 | + |
693 | +func (s *snapBuildTestSuite) fakeExecCommand(c *check.C, args ...string) (output string) { |
694 | + s.execCalls[strings.Join(args, " ")]++ |
695 | + return s.execReturnValue |
696 | +} |
697 | + |
698 | +func (s *snapBuildTestSuite) TestBuildPath(c *check.C) { |
699 | + path := buildPath(s.defaultSnapName) |
700 | + |
701 | + expected := buildPath(s.defaultSnapName) |
702 | + c.Assert(path, check.Equals, expected) |
703 | +} |
704 | + |
705 | +func (s *snapBuildTestSuite) TestLocalSnapCallsExecCommand(c *check.C) { |
706 | + _, err := LocalSnap(c, s.defaultSnapName) |
707 | + |
708 | + c.Assert(err, check.IsNil) |
709 | + path := buildPath(s.defaultSnapName) |
710 | + c.Assert(s.execCalls["snappy build "+path+" -o "+path], check.Equals, 1) |
711 | +} |
712 | + |
713 | +func (s *snapBuildTestSuite) TestLocalSnapReturnsSnapPath(c *check.C) { |
714 | + snapPath, err := LocalSnap(c, s.defaultSnapName) |
715 | + |
716 | + c.Assert(err, check.IsNil) |
717 | + expected := filepath.Join(buildPath(s.defaultSnapName), s.defaultSnapName+snapFilenameSufix) |
718 | + c.Assert(snapPath, check.Equals, expected) |
719 | +} |
720 | |
721 | === modified file '_integration-tests/testutils/wait/wait.go' |
722 | --- _integration-tests/testutils/wait/wait.go 2015-09-04 07:41:30 +0000 |
723 | +++ _integration-tests/testutils/wait/wait.go 2015-09-28 08:47:40 +0000 |
724 | @@ -33,8 +33,11 @@ |
725 | // dependency aliasing |
726 | execCommand = common.ExecCommand |
727 | // ForCommand dep alias |
728 | - ForCommand = forCommand |
729 | + ForCommand = forCommand |
730 | + // ForFunction dep alias |
731 | + ForFunction = forFunction |
732 | maxWaitRetries = 100 |
733 | + interval = time.Duration(100) |
734 | ) |
735 | |
736 | // ForActiveService uses ForCommand to check for an active service |
737 | @@ -47,19 +50,27 @@ |
738 | return ForCommand(c, fmt.Sprintf(`(?msU)^.*tcp.*0\.0\.0\.0:%d .*`, port), "netstat", "-tapn") |
739 | } |
740 | |
741 | -// forCommand keeps trying to execute the given command to get an output that |
742 | +// forCommand uses ForFunction to check for the execCommand output |
743 | +func forCommand(c *check.C, outputPattern string, cmds ...string) (err error) { |
744 | + return ForFunction(c, outputPattern, func() (string, error) { return execCommand(c, cmds...), nil }) |
745 | +} |
746 | + |
747 | +// forFunction keeps trying to execute the given function to get an output that |
748 | // matches the given pattern until it is obtained or the maximun number of |
749 | // retries is executed |
750 | -func forCommand(c *check.C, outputPattern string, cmds ...string) (err error) { |
751 | - output := execCommand(c, cmds...) |
752 | - |
753 | +func forFunction(c *check.C, outputPattern string, inputFunc func() (string, error)) (err error) { |
754 | re := regexp.MustCompile(outputPattern) |
755 | |
756 | + output, err := inputFunc() |
757 | + if err != nil { |
758 | + return |
759 | + } |
760 | + |
761 | if match := re.FindString(output); match != "" { |
762 | return |
763 | } |
764 | |
765 | - checkInterval := time.Millisecond * 100 |
766 | + checkInterval := time.Millisecond * interval |
767 | var retries int |
768 | |
769 | ticker := time.NewTicker(checkInterval) |
770 | @@ -68,7 +79,11 @@ |
771 | for { |
772 | select { |
773 | case <-tickChan: |
774 | - output = execCommand(c, cmds...) |
775 | + output, err = inputFunc() |
776 | + if err != nil { |
777 | + ticker.Stop() |
778 | + return |
779 | + } |
780 | if match := re.FindString(output); match != "" { |
781 | ticker.Stop() |
782 | return |
783 | @@ -76,7 +91,7 @@ |
784 | retries++ |
785 | if retries >= maxWaitRetries { |
786 | ticker.Stop() |
787 | - return fmt.Errorf("Pattern not found in command output") |
788 | + return fmt.Errorf("Pattern not found in function output") |
789 | } |
790 | } |
791 | } |
792 | |
793 | === modified file '_integration-tests/testutils/wait/wait_test.go' |
794 | --- _integration-tests/testutils/wait/wait_test.go 2015-09-04 09:25:17 +0000 |
795 | +++ _integration-tests/testutils/wait/wait_test.go 2015-09-28 08:47:40 +0000 |
796 | @@ -28,111 +28,123 @@ |
797 | "gopkg.in/check.v1" |
798 | ) |
799 | |
800 | +const ( |
801 | + outputPattern = "myOutputPattern" |
802 | +) |
803 | + |
804 | // Hook up check.v1 into the "go test" runner |
805 | func Test(t *testing.T) { check.TestingT(t) } |
806 | |
807 | type waitTestSuite struct { |
808 | - execCalls map[string]int |
809 | - execReturnValue string |
810 | - backExecCommand func(*check.C, ...string) string |
811 | + myFuncCalls int |
812 | + myFuncReturnValue string |
813 | + myFuncError bool |
814 | + |
815 | + outputPattern string |
816 | |
817 | initTime time.Time |
818 | delay time.Duration |
819 | + |
820 | + backInterval time.Duration |
821 | + backMaxWaitRetries int |
822 | } |
823 | |
824 | var _ = check.Suite(&waitTestSuite{}) |
825 | |
826 | -func (s *waitTestSuite) SetUpSuite(c *check.C) { |
827 | - s.backExecCommand = execCommand |
828 | - execCommand = s.fakeExecCommand |
829 | -} |
830 | - |
831 | -func (s *waitTestSuite) TearDownSuite(c *check.C) { |
832 | - execCommand = s.backExecCommand |
833 | -} |
834 | - |
835 | func (s *waitTestSuite) SetUpTest(c *check.C) { |
836 | - s.execCalls = make(map[string]int) |
837 | - s.execReturnValue = "" |
838 | + s.myFuncCalls = 0 |
839 | + s.myFuncReturnValue = outputPattern |
840 | + s.myFuncError = false |
841 | + |
842 | + s.backInterval = interval |
843 | + s.backMaxWaitRetries = maxWaitRetries |
844 | + |
845 | // save the starting time to be able to measure delays |
846 | s.initTime = time.Now() |
847 | // reset delay, each test will set it up if needed |
848 | s.delay = 0 |
849 | -} |
850 | - |
851 | -func (s *waitTestSuite) fakeExecCommand(c *check.C, args ...string) (output string) { |
852 | - s.execCalls[strings.Join(args, " ")]++ |
853 | + |
854 | + interval = 1 |
855 | +} |
856 | + |
857 | +func (s *waitTestSuite) TearDownTest(c *check.C) { |
858 | + interval = s.backInterval |
859 | + maxWaitRetries = s.backMaxWaitRetries |
860 | +} |
861 | + |
862 | +func (s *waitTestSuite) myFunc() (output string, err error) { |
863 | + s.myFuncCalls++ |
864 | |
865 | // after the given delay (0 by default) this method will return the value specified at |
866 | - // execReturnValue, before it all the calls will return an empty string |
867 | + // myFuncReturnValue, before it all the calls will return an empty string |
868 | if time.Since(s.initTime) >= s.delay { |
869 | - output = s.execReturnValue |
870 | + output = s.myFuncReturnValue |
871 | + if s.myFuncError { |
872 | + err = fmt.Errorf("Error!") |
873 | + } |
874 | } |
875 | - |
876 | return |
877 | } |
878 | |
879 | -func (s *waitTestSuite) TestForCommandExists(c *check.C) { |
880 | - cmd := "mycommand" |
881 | - outputPattern := "myOutput" |
882 | - s.execReturnValue = "myOutput" |
883 | - |
884 | - err := ForCommand(c, outputPattern, cmd) |
885 | +func (s *waitTestSuite) TestForFunctionExists(c *check.C) { |
886 | + err := ForFunction(c, outputPattern, s.myFunc) |
887 | |
888 | c.Assert(err, check.IsNil, check.Commentf("Got error %s", err)) |
889 | } |
890 | |
891 | -func (s *waitTestSuite) TestForCommandCallsGivenCommand(c *check.C) { |
892 | - cmd := []string{"mycmd", "mypar"} |
893 | - outputPattern := "myOutput" |
894 | - s.execReturnValue = "myOutput" |
895 | - |
896 | - ForCommand(c, outputPattern, cmd...) |
897 | - |
898 | - execCalls := s.execCalls["mycmd mypar"] |
899 | - |
900 | - c.Assert(execCalls, check.Equals, 1, |
901 | - check.Commentf("Expected 1 call to ExecCommand with 'mycmd mypar', got %d", execCalls)) |
902 | -} |
903 | - |
904 | -func (s *waitTestSuite) TestForCommandFailsOnUnmatchedOutput(c *check.C) { |
905 | - cmd := []string{"mycmd", "mypar"} |
906 | - outputPattern := "myOutput" |
907 | - s.execReturnValue = "anotherOutput" |
908 | - |
909 | - backMaxWaitRetries := maxWaitRetries |
910 | - defer func() { maxWaitRetries = backMaxWaitRetries }() |
911 | +func (s *waitTestSuite) TestForFunctionCallsGivenFunction(c *check.C) { |
912 | + ForFunction(c, outputPattern, s.myFunc) |
913 | + |
914 | + c.Assert(s.myFuncCalls, check.Equals, 1, |
915 | + check.Commentf("Expected 1 call to ExecCommand with 'mycmd mypar', got %d", s.myFuncCalls)) |
916 | +} |
917 | + |
918 | +func (s *waitTestSuite) TestForFunctionReturnsFunctionError(c *check.C) { |
919 | + s.myFuncError = true |
920 | + |
921 | + err := ForFunction(c, outputPattern, s.myFunc) |
922 | + |
923 | + c.Assert(err, check.NotNil, check.Commentf("Didn't get expected error")) |
924 | +} |
925 | + |
926 | +func (s *waitTestSuite) TestForFunctionReturnsFunctionErrorOnRetry(c *check.C) { |
927 | + s.myFuncError = true |
928 | + s.delay = 1 * time.Millisecond |
929 | + |
930 | + err := ForFunction(c, outputPattern, s.myFunc) |
931 | + |
932 | + c.Assert(err, check.NotNil, check.Commentf("Didn't get expected error")) |
933 | +} |
934 | + |
935 | +func (s *waitTestSuite) TestForFunctionFailsOnUnmatchedOutput(c *check.C) { |
936 | maxWaitRetries = 0 |
937 | |
938 | - err := ForCommand(c, outputPattern, cmd...) |
939 | + s.myFuncReturnValue = "anotherPattern" |
940 | + err := ForFunction(c, outputPattern, s.myFunc) |
941 | |
942 | c.Assert(err, check.NotNil, check.Commentf("Didn't get expected error")) |
943 | } |
944 | |
945 | -func (s *waitTestSuite) TestForCommandRetriesCalls(c *check.C) { |
946 | - cmd := []string{"mycmd", "mypar"} |
947 | - outputPattern := "myOutput" |
948 | - s.execReturnValue = "myOutput" |
949 | - |
950 | - s.delay = 10 * time.Millisecond |
951 | - |
952 | - err := ForCommand(c, outputPattern, cmd...) |
953 | +func (s *waitTestSuite) TestForFunctionRetriesCalls(c *check.C) { |
954 | + maxWaitRetries = 2 |
955 | + |
956 | + s.delay = 2 * time.Millisecond |
957 | + |
958 | + err := ForFunction(c, outputPattern, s.myFunc) |
959 | |
960 | c.Assert(err, check.IsNil, check.Commentf("Got error %s", err)) |
961 | + c.Assert(s.myFuncCalls > 0, check.Equals, true) |
962 | } |
963 | |
964 | -func (s *waitTestSuite) TestForCommandHonoursMaxWaitRetries(c *check.C) { |
965 | - cmd := []string{"mycmd", "mypar"} |
966 | - outputPattern := "myOutput" |
967 | - |
968 | - backMaxWaitRetries := maxWaitRetries |
969 | - defer func() { maxWaitRetries = backMaxWaitRetries }() |
970 | +func (s *waitTestSuite) TestForFunctionHonoursMaxWaitRetries(c *check.C) { |
971 | maxWaitRetries = 3 |
972 | |
973 | - ForCommand(c, outputPattern, cmd...) |
974 | + s.myFuncReturnValue = "anotherPattern" |
975 | + |
976 | + ForFunction(c, outputPattern, s.myFunc) |
977 | |
978 | // the first call is not actually a retry |
979 | - actualRetries := s.execCalls["mycmd mypar"] - 1 |
980 | + actualRetries := s.myFuncCalls - 1 |
981 | |
982 | c.Assert(actualRetries, check.Equals, maxWaitRetries, |
983 | check.Commentf("Actual number of retries %d does not match max retries %d", |
984 | @@ -170,3 +182,37 @@ |
985 | expectedCalled := `ForCommand called with pattern '(?msU)^.*tcp.*0\.0\.0\.0:1234 .*' and cmds 'netstat -tapn'` |
986 | c.Assert(called, check.Equals, expectedCalled, check.Commentf("Expected call to ForCommand didn't happen")) |
987 | } |
988 | + |
989 | +func (s *waitTestSuite) TestForCommandCallsForFunction(c *check.C) { |
990 | + backForFunction := ForFunction |
991 | + defer func() { ForFunction = backForFunction }() |
992 | + var called string |
993 | + |
994 | + ForFunction = func(c *check.C, pattern string, inputFunc func() (string, error)) (err error) { |
995 | + called = fmt.Sprintf("ForFunction called with pattern '%s' and execCommand wrapper", pattern) |
996 | + return |
997 | + } |
998 | + |
999 | + ForCommand(c, "pattern", "ls") |
1000 | + |
1001 | + expectedCalled := `ForFunction called with pattern 'pattern' and execCommand wrapper` |
1002 | + c.Assert(called, check.Equals, expectedCalled, check.Commentf("Expected call to ForFunction didn't happen")) |
1003 | +} |
1004 | + |
1005 | +func (s *waitTestSuite) TestForCommandUsesExecCommand(c *check.C) { |
1006 | + maxWaitRetries = 0 |
1007 | + |
1008 | + backExecCommand := execCommand |
1009 | + defer func() { execCommand = backExecCommand }() |
1010 | + var called string |
1011 | + |
1012 | + execCommand = func(c *check.C, cmds ...string) (output string) { |
1013 | + called = fmt.Sprintf("execCommand called with cmds %s", cmds) |
1014 | + return |
1015 | + } |
1016 | + |
1017 | + ForCommand(c, "pattern", "ls") |
1018 | + |
1019 | + expectedCalled := `execCommand called with cmds [ls]` |
1020 | + c.Assert(called, check.Equals, expectedCalled, check.Commentf("Expected call to execCommand didn't happen")) |
1021 | +} |
you are getting all fancy with interfaces and stuff in the tests :)
nice work.
I'm leaving some comments in the diff. The only big thing, in which maybe I am wrong, is the setUp and tearDown. We can talk about it tomorrow.