Merge lp:~sergiusens/snappy/lockDownRemove into lp:~snappy-dev/snappy/snappy-moved-to-github
- lockDownRemove
- Merge into snappy-moved-to-github
Proposed by
Sergio Schvezov
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | no longer in the source branch. |
Merged at revision: | 398 |
Proposed branch: | lp:~sergiusens/snappy/lockDownRemove |
Merge into: | lp:~snappy-dev/snappy/snappy-moved-to-github |
Diff against target: |
496 lines (+298/-123) 4 files modified
snappy/oem.go (+197/-0) snappy/oem_test.go (+59/-0) snappy/snapp.go (+9/-121) snappy/snapp_test.go (+33/-2) |
To merge this branch: | bzr merge lp:~sergiusens/snappy/lockDownRemove |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Review via email: mp+256849@code.launchpad.net |
Commit message
Moving oem related tasks to an oem.go unit and adding support for Uninstall prevention of builtins.
Description of the change
This will need a remerge of mvo's changes, but wanted to get the general idea.
To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) : | # |
review:
Approve
Revision history for this message
Snappy Tarmac (snappydevtarmac) wrote : | # |
Revision history for this message
John Lenton (chipaca) : | # |
review:
Approve
- 398. By Sergio Schvezov
-
Moving oem related tasks to an oem.go unit and adding support for Uninstall prevention of builtins. by sergiusens approved by chipaca
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'snappy/oem.go' |
2 | --- snappy/oem.go 1970-01-01 00:00:00 +0000 |
3 | +++ snappy/oem.go 2015-04-20 23:57:04 +0000 |
4 | @@ -0,0 +1,197 @@ |
5 | +/* |
6 | + * Copyright (C) 2014-2015 Canonical Ltd |
7 | + * |
8 | + * This program is free software: you can redistribute it and/or modify |
9 | + * it under the terms of the GNU General Public License version 3 as |
10 | + * published by the Free Software Foundation. |
11 | + * |
12 | + * This program is distributed in the hope that it will be useful, |
13 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | + * GNU General Public License for more details. |
16 | + * |
17 | + * You should have received a copy of the GNU General Public License |
18 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | + * |
20 | + */ |
21 | + |
22 | +// TODO this should be it's own package, but depends on splitting out |
23 | +// package.yaml's |
24 | + |
25 | +package snappy |
26 | + |
27 | +import ( |
28 | + "errors" |
29 | + "fmt" |
30 | + "io/ioutil" |
31 | + "os" |
32 | + "os/exec" |
33 | + "path/filepath" |
34 | + "strings" |
35 | +) |
36 | + |
37 | +// OEM represents the structure inside the package.yaml for the oem component |
38 | +// of an oem package type. |
39 | +type OEM struct { |
40 | + Store Store `yaml:"store,omitempty"` |
41 | + Hardware struct { |
42 | + Assign []HardwareAssign `yaml:"assign,omitempty"` |
43 | + } `yaml:"hardware,omitempty"` |
44 | + Software Software `yaml:"software,omitempty"` |
45 | +} |
46 | + |
47 | +// Store holds information relevant to the store provided by an OEM snap |
48 | +type Store struct { |
49 | + ID string `yaml:"id,omitempty"` |
50 | +} |
51 | + |
52 | +// Software describes the installed software provided by an OEM snap |
53 | +type Software struct { |
54 | + BuiltIn []string `yaml:"built-in,omitempty"` |
55 | +} |
56 | + |
57 | +// HardwareAssign describes the hardware a app can use |
58 | +type HardwareAssign struct { |
59 | + PartID string `yaml:"part-id,omitempty"` |
60 | + Rules []struct { |
61 | + Kernel string `yaml:"kernel,omitempty"` |
62 | + Subsystem string `yaml:"subsystem,omitempty"` |
63 | + WithSubsystems string `yaml:"with-subsystems,omitempty"` |
64 | + WithDriver string `yaml:"with-driver,omitempty"` |
65 | + WithAttrs []string `yaml:"with-attrs,omitempty"` |
66 | + WithProps []string `yaml:"with-props,omitempty"` |
67 | + } `yaml:"rules,omitempty"` |
68 | +} |
69 | + |
70 | +func (hw *HardwareAssign) generateUdevRuleContent() (string, error) { |
71 | + s := "" |
72 | + for _, r := range hw.Rules { |
73 | + if r.Kernel != "" { |
74 | + s += fmt.Sprintf(`KERNEL=="%v", `, r.Kernel) |
75 | + } |
76 | + if r.Subsystem != "" { |
77 | + s += fmt.Sprintf(`SUBSYSTEM=="%v", `, r.Subsystem) |
78 | + } |
79 | + if r.WithSubsystems != "" { |
80 | + s += fmt.Sprintf(`SUBSYSTEMS=="%v", `, r.WithSubsystems) |
81 | + } |
82 | + if r.WithDriver != "" { |
83 | + s += fmt.Sprintf(`DRIVER=="%v", `, r.WithDriver) |
84 | + } |
85 | + for _, a := range r.WithAttrs { |
86 | + l := strings.Split(a, "=") |
87 | + s += fmt.Sprintf(`ATTRS{%v}=="%v", `, l[0], l[1]) |
88 | + } |
89 | + for _, a := range r.WithProps { |
90 | + l := strings.SplitN(a, "=", 2) |
91 | + s += fmt.Sprintf(`ENV{%v}=="%v", `, l[0], l[1]) |
92 | + } |
93 | + s += fmt.Sprintf(`TAG:="snappy-assign", ENV{SNAPPY_APP}:="%s"`, hw.PartID) |
94 | + s += "\n\n" |
95 | + } |
96 | + |
97 | + return s, nil |
98 | +} |
99 | + |
100 | +// getOem is a convenience function to not go into the details for the business |
101 | +// logic for an oem package in every other function |
102 | +var getOem = getOemImpl |
103 | + |
104 | +var getOemImpl = func() (*packageYaml, error) { |
105 | + oems, _ := ActiveSnapsByType(SnapTypeOem) |
106 | + if len(oems) == 1 { |
107 | + return oems[0].(*SnapPart).m, nil |
108 | + } |
109 | + |
110 | + return nil, errors.New("no oem snap") |
111 | +} |
112 | + |
113 | +// StoreID returns the store id setup by the oem package or an empty string |
114 | +func StoreID() string { |
115 | + oem, err := getOem() |
116 | + if err != nil { |
117 | + return "" |
118 | + } |
119 | + |
120 | + return oem.OEM.Store.ID |
121 | +} |
122 | + |
123 | +// IsBuiltInSoftware returns true if the package is part of the built-in software |
124 | +// defined by the oem. |
125 | +func IsBuiltInSoftware(name string) bool { |
126 | + oem, err := getOem() |
127 | + if err != nil { |
128 | + return false |
129 | + } |
130 | + |
131 | + for _, builtin := range oem.OEM.Software.BuiltIn { |
132 | + if builtin == name { |
133 | + return true |
134 | + } |
135 | + } |
136 | + |
137 | + return false |
138 | +} |
139 | + |
140 | +func cleanupOemHardwareUdevRules(m *packageYaml) error { |
141 | + oldFiles, err := filepath.Glob(filepath.Join(snapUdevRulesDir, fmt.Sprintf("80-snappy_%s_*.rules", m.Name))) |
142 | + if err != nil { |
143 | + return err |
144 | + } |
145 | + |
146 | + for _, f := range oldFiles { |
147 | + os.Remove(f) |
148 | + } |
149 | + |
150 | + return nil |
151 | +} |
152 | + |
153 | +func writeOemHardwareUdevRules(m *packageYaml) error { |
154 | + // cleanup |
155 | + if err := cleanupOemHardwareUdevRules(m); err != nil { |
156 | + return err |
157 | + } |
158 | + // write new files |
159 | + for _, h := range m.OEM.Hardware.Assign { |
160 | + rulesContent, err := h.generateUdevRuleContent() |
161 | + if err != nil { |
162 | + return err |
163 | + } |
164 | + outfile := filepath.Join(snapUdevRulesDir, fmt.Sprintf("80-snappy_%s_%s.rules", m.Name, h.PartID)) |
165 | + if err := ioutil.WriteFile(outfile, []byte(rulesContent), 0644); err != nil { |
166 | + return err |
167 | + } |
168 | + } |
169 | + |
170 | + return nil |
171 | +} |
172 | + |
173 | +// var to make testing easier |
174 | +var runUdevAdm = runUdevAdmImpl |
175 | + |
176 | +func runUdevAdmImpl(args ...string) error { |
177 | + cmd := exec.Command(args[0], args[1:]...) |
178 | + cmd.Stdout = os.Stdout |
179 | + cmd.Stderr = os.Stderr |
180 | + return cmd.Run() |
181 | +} |
182 | + |
183 | +func activateOemHardwareUdevRules(m *packageYaml) error { |
184 | + if err := runUdevAdm("udevadm", "control", "--reload-rules"); err != nil { |
185 | + return err |
186 | + } |
187 | + |
188 | + return runUdevAdm("udevadm", "trigger") |
189 | +} |
190 | + |
191 | +func installOemHardwareUdevRules(m *packageYaml) error { |
192 | + if err := writeOemHardwareUdevRules(m); err != nil { |
193 | + return err |
194 | + } |
195 | + |
196 | + if err := activateOemHardwareUdevRules(m); err != nil { |
197 | + return err |
198 | + } |
199 | + |
200 | + return nil |
201 | +} |
202 | |
203 | === added file 'snappy/oem_test.go' |
204 | --- snappy/oem_test.go 1970-01-01 00:00:00 +0000 |
205 | +++ snappy/oem_test.go 2015-04-20 23:57:04 +0000 |
206 | @@ -0,0 +1,59 @@ |
207 | +/* |
208 | + * Copyright (C) 2014-2015 Canonical Ltd |
209 | + * |
210 | + * This program is free software: you can redistribute it and/or modify |
211 | + * it under the terms of the GNU General Public License version 3 as |
212 | + * published by the Free Software Foundation. |
213 | + * |
214 | + * This program is distributed in the hope that it will be useful, |
215 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
216 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
217 | + * GNU General Public License for more details. |
218 | + * |
219 | + * You should have received a copy of the GNU General Public License |
220 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
221 | + * |
222 | + */ |
223 | + |
224 | +// TODO this should be it's own package, but depends on splitting out |
225 | +// package.yaml's |
226 | + |
227 | +package snappy |
228 | + |
229 | +import ( |
230 | + . "launchpad.net/gocheck" |
231 | +) |
232 | + |
233 | +type OemSuite struct { |
234 | +} |
235 | + |
236 | +var _ = Suite(&OemSuite{}) |
237 | + |
238 | +var ( |
239 | + getOemOrig = getOem |
240 | +) |
241 | + |
242 | +func (s *OemSuite) SetUpTest(c *C) { |
243 | + getOem = func() (*packageYaml, error) { |
244 | + return &packageYaml{ |
245 | + OEM: OEM{ |
246 | + Software: Software{[]string{"makeuppackage", "anotherpackage"}}, |
247 | + Store: Store{"ninjablocks"}, |
248 | + }, |
249 | + }, nil |
250 | + } |
251 | +} |
252 | + |
253 | +func (s *OemSuite) TearDownTest(c *C) { |
254 | + getOem = getOemImpl |
255 | +} |
256 | + |
257 | +func (s *OemSuite) TestIsBuildIn(c *C) { |
258 | + c.Check(IsBuiltInSoftware("notapackage"), Equals, false) |
259 | + c.Check(IsBuiltInSoftware("makeuppackage"), Equals, true) |
260 | + c.Check(IsBuiltInSoftware("anotherpackage"), Equals, true) |
261 | +} |
262 | + |
263 | +func (s *OemSuite) TestStoreID(c *C) { |
264 | + c.Assert(StoreID(), Equals, "ninjablocks") |
265 | +} |
266 | |
267 | === modified file 'snappy/snapp.go' |
268 | --- snappy/snapp.go 2015-04-20 16:51:29 +0000 |
269 | +++ snappy/snapp.go 2015-04-20 23:57:04 +0000 |
270 | @@ -173,6 +173,8 @@ |
271 | |
272 | var commasplitter = regexp.MustCompile(`\s*,\s*`).Split |
273 | |
274 | +// TODO split into payloads per package type composing the common |
275 | +// elements for all snaps. |
276 | type packageYaml struct { |
277 | Name string |
278 | Version string |
279 | @@ -192,14 +194,7 @@ |
280 | Binaries []Binary `yaml:"binaries,omitempty"` |
281 | |
282 | // oem snap only |
283 | - OEM struct { |
284 | - Store struct { |
285 | - ID string `yaml:"id,omitempty"` |
286 | - } `yaml:"store,omitempty"` |
287 | - Hardware struct { |
288 | - Assign []HardwareAssign `yaml:"assign,omitempty"` |
289 | - } `yaml:"hardware,omitempty"` |
290 | - } `yaml:"oem,omitempty"` |
291 | + OEM OEM `yaml:"oem,omitempty"` |
292 | Config SystemConfig `yaml:"config,omitempty"` |
293 | |
294 | // this is a bit ugly, but right now integration is a one:one |
295 | @@ -210,19 +205,6 @@ |
296 | LicenseVersion string `yaml:"license-version,omitempty"` |
297 | } |
298 | |
299 | -// HardwareAssign describes the hardware a app can use |
300 | -type HardwareAssign struct { |
301 | - PartID string `yaml:"part-id,omitempty"` |
302 | - Rules []struct { |
303 | - Kernel string `yaml:"kernel,omitempty"` |
304 | - Subsystem string `yaml:"subsystem,omitempty"` |
305 | - WithSubsystems string `yaml:"with-subsystems,omitempty"` |
306 | - WithDriver string `yaml:"with-driver,omitempty"` |
307 | - WithAttrs []string `yaml:"with-attrs,omitempty"` |
308 | - WithProps []string `yaml:"with-props,omitempty"` |
309 | - } `yaml:"rules,omitempty"` |
310 | -} |
311 | - |
312 | type remoteSnap struct { |
313 | Alias string `json:"alias,omitempty"` |
314 | AnonDownloadURL string `json:"anon_download_url,omitempty"` |
315 | @@ -248,99 +230,6 @@ |
316 | } `json:"_embedded"` |
317 | } |
318 | |
319 | -func cleanupOemHardwareUdevRules(m *packageYaml) error { |
320 | - oldFiles, err := filepath.Glob(filepath.Join(snapUdevRulesDir, fmt.Sprintf("80-snappy_%s_*.rules", m.Name))) |
321 | - if err != nil { |
322 | - return err |
323 | - } |
324 | - |
325 | - for _, f := range oldFiles { |
326 | - os.Remove(f) |
327 | - } |
328 | - |
329 | - return nil |
330 | -} |
331 | - |
332 | -func writeOemHardwareUdevRules(m *packageYaml) error { |
333 | - // cleanup |
334 | - if err := cleanupOemHardwareUdevRules(m); err != nil { |
335 | - return err |
336 | - } |
337 | - // write new files |
338 | - for _, h := range m.OEM.Hardware.Assign { |
339 | - rulesContent, err := h.generateUdevRuleContent() |
340 | - if err != nil { |
341 | - return err |
342 | - } |
343 | - outfile := filepath.Join(snapUdevRulesDir, fmt.Sprintf("80-snappy_%s_%s.rules", m.Name, h.PartID)) |
344 | - if err := ioutil.WriteFile(outfile, []byte(rulesContent), 0644); err != nil { |
345 | - return err |
346 | - } |
347 | - } |
348 | - |
349 | - return nil |
350 | -} |
351 | - |
352 | -// var to make testing easier |
353 | -var runUdevAdm = runUdevAdmImpl |
354 | - |
355 | -func runUdevAdmImpl(args ...string) error { |
356 | - cmd := exec.Command(args[0], args[1:]...) |
357 | - cmd.Stdout = os.Stdout |
358 | - cmd.Stderr = os.Stderr |
359 | - return cmd.Run() |
360 | -} |
361 | - |
362 | -func activateOemHardwareUdevRules(m *packageYaml) error { |
363 | - if err := runUdevAdm("udevadm", "control", "--reload-rules"); err != nil { |
364 | - return err |
365 | - } |
366 | - |
367 | - return runUdevAdm("udevadm", "trigger") |
368 | -} |
369 | - |
370 | -func installOemHardwareUdevRules(m *packageYaml) error { |
371 | - if err := writeOemHardwareUdevRules(m); err != nil { |
372 | - return err |
373 | - } |
374 | - |
375 | - if err := activateOemHardwareUdevRules(m); err != nil { |
376 | - return err |
377 | - } |
378 | - |
379 | - return nil |
380 | -} |
381 | - |
382 | -func (hw *HardwareAssign) generateUdevRuleContent() (string, error) { |
383 | - s := "" |
384 | - for _, r := range hw.Rules { |
385 | - if r.Kernel != "" { |
386 | - s += fmt.Sprintf(`KERNEL=="%v", `, r.Kernel) |
387 | - } |
388 | - if r.Subsystem != "" { |
389 | - s += fmt.Sprintf(`SUBSYSTEM=="%v", `, r.Subsystem) |
390 | - } |
391 | - if r.WithSubsystems != "" { |
392 | - s += fmt.Sprintf(`SUBSYSTEMS=="%v", `, r.WithSubsystems) |
393 | - } |
394 | - if r.WithDriver != "" { |
395 | - s += fmt.Sprintf(`DRIVER=="%v", `, r.WithDriver) |
396 | - } |
397 | - for _, a := range r.WithAttrs { |
398 | - l := strings.Split(a, "=") |
399 | - s += fmt.Sprintf(`ATTRS{%v}=="%v", `, l[0], l[1]) |
400 | - } |
401 | - for _, a := range r.WithProps { |
402 | - l := strings.SplitN(a, "=", 2) |
403 | - s += fmt.Sprintf(`ENV{%v}=="%v", `, l[0], l[1]) |
404 | - } |
405 | - s += fmt.Sprintf(`TAG:="snappy-assign", ENV{SNAPPY_APP}:="%s"`, hw.PartID) |
406 | - s += "\n\n" |
407 | - } |
408 | - |
409 | - return s, nil |
410 | -} |
411 | - |
412 | func parsePackageYamlFile(yamlPath string) (*packageYaml, error) { |
413 | |
414 | yamlData, err := ioutil.ReadFile(yamlPath) |
415 | @@ -689,6 +578,10 @@ |
416 | return ErrPackageNotRemovable |
417 | } |
418 | |
419 | + if IsBuiltInSoftware(s.Name()) && s.IsActive() { |
420 | + return ErrPackageNotRemovable |
421 | + } |
422 | + |
423 | deps, err := s.DependentNames() |
424 | if err != nil { |
425 | return err |
426 | @@ -1206,13 +1099,8 @@ |
427 | req.Header.Set("X-Ubuntu-Architecture", string(Architecture())) |
428 | req.Header.Set("X-Ubuntu-Release", release.String()) |
429 | |
430 | - // check if the oem part sets a custom store-id |
431 | - oems, _ := ActiveSnapsByType(SnapTypeOem) |
432 | - if len(oems) == 1 { |
433 | - storeID := oems[0].(*SnapPart).m.OEM.Store.ID |
434 | - if storeID != "" { |
435 | - req.Header.Set("X-Ubuntu-Store", storeID) |
436 | - } |
437 | + if storeID := StoreID(); storeID != "" { |
438 | + req.Header.Set("X-Ubuntu-Store", storeID) |
439 | } |
440 | |
441 | // sso |
442 | |
443 | === modified file 'snappy/snapp_test.go' |
444 | --- snappy/snapp_test.go 2015-04-20 18:40:29 +0000 |
445 | +++ snappy/snapp_test.go 2015-04-20 23:57:04 +0000 |
446 | @@ -835,6 +835,37 @@ |
447 | repo.Details("xkcd") |
448 | } |
449 | |
450 | +func (s *SnapTestSuite) TestUninstallBuiltIn(c *C) { |
451 | + // install custom oem snap with store-id |
452 | + oemYaml, err := makeInstalledMockSnap(s.tempdir, `name: oem-test |
453 | +version: 1.0 |
454 | +vendor: mvo |
455 | +oem: |
456 | + store: |
457 | + id: my-store |
458 | + software: |
459 | + built-in: |
460 | + - hello-app |
461 | +type: oem |
462 | +`) |
463 | + c.Assert(err, IsNil) |
464 | + makeSnapActive(oemYaml) |
465 | + |
466 | + packageYaml, err := makeInstalledMockSnap(s.tempdir, "") |
467 | + c.Assert(err, IsNil) |
468 | + makeSnapActive(packageYaml) |
469 | + |
470 | + p := &MockProgressMeter{} |
471 | + |
472 | + snap := NewLocalSnapRepository(filepath.Join(s.tempdir, "apps")) |
473 | + c.Assert(snap, NotNil) |
474 | + installed, err := snap.Installed() |
475 | + c.Assert(err, IsNil) |
476 | + parts := FindSnapsByName("hello-app", installed) |
477 | + c.Assert(parts, HasLen, 1) |
478 | + c.Check(parts[0].Uninstall(p), Equals, ErrPackageNotRemovable) |
479 | +} |
480 | + |
481 | var securityBinaryPackageYaml = []byte(`name: test-snap |
482 | version: 1.2.8 |
483 | vendor: Jamie Strandboge <jamie@canonical.com> |
484 | @@ -1209,10 +1240,10 @@ |
485 | - subsystem: tty |
486 | with-subsystems: usb-serial |
487 | with-driver: pl2303 |
488 | - with-attrs: |
489 | + with-attrs: |
490 | - idVendor=0xf00f00 |
491 | - idProduct=0xb00 |
492 | - with-props: |
493 | + with-props: |
494 | - BAUD=9600 |
495 | - META1=foo* |
496 | - META2=foo? |
The attempt to merge lp:~sergiusens/snappy/lockDownRemove into lp:snappy failed. Below is the output from the failed tests.
Checking formatting com/p/go. crypto failed; trying to fetch newer version com/blakesmith/ ar failed; trying to fetch newer version com/p/go. crypto now at 69e2a90ed92d038 12364aeb947b706 8dc42e561e com/cheggaaa/ pb failed; trying to fetch newer version com/blakesmith/ ar now at c9a977dd0cc1392 b023382c7bfa5a2 2af8d3b730 com/jessevdk/ go-flags failed; trying to fetch newer version com/cheggaaa/ pb now at e8c7cc515bfde3e 267957a3b110080 ceed51354e com/juju/ loggo failed; trying to fetch newer version com/jessevdk/ go-flags now at 15347ef417a3003 49807983f15af9e 65cd2e1b3a com/mvo5/ goconfigparser failed; trying to fetch newer version com/juju/ loggo now at 4c7cbce140ca070 eeb59a28f4bf950 7e511711f9 com/mvo5/ goconfigparser now at 26426272dda20cc 76aa1fa44286dc7 43d2972fe8 net/gocheck failed; trying to fetch newer version 6fb6c4e0d370a05 f24a0bf213 net/gocheck now at <email address hidden> lNdVwxZXOl/ src/launchpad. net/snappy net/snappy/ clickdeb 0.212s coverage: 80.0% of statements net/snappy/ cmd/snappy 0.017s coverage: 13.0% of statements net/snappy/ coreconfig 0.044s coverage: 100.0% of statements net/snappy/ helpers 1.442s coverage: 82.4% of statements net/snappy/ logger 0.035s coverage: 92.5% of statements net/snappy/ partition 0.105s coverage: 81.7% of statements net/snappy/ policy 0.077s coverage: 87.8% of statements net/snappy/ priv 0.004s coverage: 84.8% of statements net/snappy/ progress 0.011s coverage: 45.2% of statements net/snappy/ release 0.006s coverage: 100.0% of statements
Installing godeps
Install golint
Obtaining dependencies
update code.google.
update github.
code.google.
update github.
github.
update github.
github.
update github.
github.
update github.
github.
update gopkg.in/yaml.v2 failed; trying to fetch newer version
github.
update launchpad.
gopkg.in/yaml.v2 now at 49c95bdc2184325
launchpad.
Building
Running tests from /tmp/tmp.
=== RUN Test
OK: 8 passed
--- PASS: Test (0.21 seconds)
PASS
coverage: 80.0% of statements
ok launchpad.
=== RUN Test
OK: 6 passed
--- PASS: Test (0.01 seconds)
PASS
coverage: 13.0% of statements
ok launchpad.
=== RUN Test
OK: 24 passed
--- PASS: Test (0.04 seconds)
PASS
coverage: 100.0% of statements
ok launchpad.
=== RUN Test
OK: 30 passed
--- PASS: Test (1.44 seconds)
PASS
coverage: 82.4% of statements
ok launchpad.
=== RUN Test
OK: 7 passed
--- PASS: Test (0.03 seconds)
PASS
coverage: 92.5% of statements
ok launchpad.
=== RUN Test
OK: 36 passed
--- PASS: Test (0.10 seconds)
PASS
coverage: 81.7% of statements
ok launchpad.
=== RUN Test
OK: 12 passed
--- PASS: Test (0.07 seconds)
PASS
coverage: 87.8% of statements
ok launchpad.
=== RUN Test
OK: 3 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 84.8% of statements
ok launchpad.
=== RUN Test
OK: 3 passed
--- PASS: Test (0.01 seconds)
PASS
coverage: 45.2% of statements
ok launchpad.
=== RUN Test
OK: 6 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 100.0% of statements
ok launchpad.
=== RUN Test
2015-04-20 23:41:47 ERROR snappy logger.go:199 hello-app.potato failed to install: a package by that name is already installed
201...