Merge lp:~fgimenez/snappy/api-packages-test into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Federico Gimenez
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
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.

To post a comment you must log in.
681. By Federico Gimenez

testing methods not allowed; payload as string

Revision history for this message
Leo Arias (elopio) wrote :

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.

review: Needs Fixing
682. By Federico Gimenez

Removed setup and teardown from the apiExerciser interface, using SetUpTest and TearDownTest fixtures from check; comments

Revision history for this message
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

Revision history for this message
Federico Gimenez (fgimenez) wrote :

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches