Merge lp:~jameinel/juju-core/only-943 into lp:~juju/juju-core/trunk

Proposed by John A Meinel
Status: Merged
Merged at revision: 949
Proposed branch: lp:~jameinel/juju-core/only-943
Merge into: lp:~juju/juju-core/trunk
Diff against target: 1612 lines (+497/-125)
50 files modified
cmd/cmd.go (+6/-1)
cmd/cmd_test.go (+7/-7)
cmd/filevar_test.go (+1/-1)
cmd/juju/addrelation_test.go (+2/-1)
cmd/juju/addunit_test.go (+2/-1)
cmd/juju/bootstrap_test.go (+1/-1)
cmd/juju/cmd_test.go (+2/-2)
cmd/juju/config_test.go (+4/-3)
cmd/juju/constraints_test.go (+2/-1)
cmd/juju/deploy_test.go (+2/-1)
cmd/juju/destroymachine_test.go (+2/-1)
cmd/juju/destroyrelation_test.go (+2/-1)
cmd/juju/destroyservice_test.go (+2/-1)
cmd/juju/destroyunit_test.go (+2/-1)
cmd/juju/expose_test.go (+2/-1)
cmd/juju/init_test.go (+5/-5)
cmd/juju/resolved_test.go (+2/-1)
cmd/juju/scp_test.go (+1/-1)
cmd/juju/ssh_test.go (+1/-1)
cmd/juju/status_test.go (+1/-1)
cmd/juju/unexpose_test.go (+2/-1)
cmd/juju/upgradejuju_test.go (+2/-3)
cmd/jujud/agent_test.go (+1/-1)
cmd/jujud/bootstrap_test.go (+2/-2)
cmd/logging_test.go (+4/-4)
cmd/output_test.go (+4/-3)
cmd/supercommand_test.go (+1/-1)
cmd/util_test.go (+0/-5)
environs/agent/agent.go (+1/-1)
juju/api.go (+1/-1)
juju/conn.go (+2/-2)
state/api/api_test.go (+2/-2)
state/api/error.go (+2/-1)
state/open.go (+28/-5)
state/state_test.go (+5/-5)
state/unit_test.go (+1/-1)
store/server.go (+67/-2)
store/server_test.go (+47/-4)
store/store.go (+164/-19)
store/store_test.go (+60/-3)
testing/cmd.go (+27/-4)
worker/uniter/jujuc/config-get_test.go (+3/-3)
worker/uniter/jujuc/ports_test.go (+1/-1)
worker/uniter/jujuc/relation-get_test.go (+4/-3)
worker/uniter/jujuc/relation-ids_test.go (+3/-2)
worker/uniter/jujuc/relation-list_test.go (+3/-2)
worker/uniter/jujuc/relation-set_test.go (+2/-2)
worker/uniter/jujuc/server.go (+6/-1)
worker/uniter/jujuc/unit-get_test.go (+3/-3)
worker/uniter/jujuc/util_test.go (+0/-5)
To merge this branch: bzr merge lp:~jameinel/juju-core/only-943
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+151005@code.launchpad.net

Description of the change

revert only r943

William merged a change which reverted trunk back to 942
because rev 943 broke the test suite.

This just re-introduces all of the merges 944-947, so that
Gustavo and Ian don't have to re-propose and get approval
for their changes again.

I've run the test suite to make sure it passes as long as
just 943 has been removed.

In the future, you can just cherrypick-out specific revisions
with (eg):
 bzr merge . -r 943..942

And then run the test suite, and propose it. (Which is essentially
what this patch does, except it also reverts 948 which removed
all of those other revisions.)

https://codereview.appspot.com/7424046/

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

Reviewers: mp+151005_code.launchpad.net,

Message:
Please take a look.

Description:
revert only r943

William merged a change which reverted trunk back to 942
because rev 943 broke the test suite.

This just re-introduces all of the merges 944-947, so that
Gustavo and Ian don't have to re-propose and get approval
for their changes again.

I've run the test suite to make sure it passes as long as
just 943 has been removed.

In the future, you can just cherrypick-out specific revisions
with (eg):
  bzr merge . -r 943..942

And then run the test suite, and propose it. (Which is essentially
what this patch does, except it also reverts 948 which removed
all of those other revisions.)

https://code.launchpad.net/~jameinel/juju-core/only-943/+merge/151005

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/7424046/

Affected files:
   A [revision details]
   M cmd/cmd.go
   M cmd/cmd_test.go
   M cmd/filevar_test.go
   M cmd/juju/addrelation_test.go
   M cmd/juju/addunit_test.go
   M cmd/juju/bootstrap_test.go
   M cmd/juju/cmd_test.go
   M cmd/juju/config_test.go
   M cmd/juju/constraints_test.go
   M cmd/juju/deploy_test.go
   M cmd/juju/destroymachine_test.go
   M cmd/juju/destroyrelation_test.go
   M cmd/juju/destroyservice_test.go
   M cmd/juju/destroyunit_test.go
   M cmd/juju/expose_test.go
   M cmd/juju/init_test.go
   M cmd/juju/resolved_test.go
   M cmd/juju/scp_test.go
   M cmd/juju/ssh_test.go
   M cmd/juju/status_test.go
   M cmd/juju/unexpose_test.go
   M cmd/juju/upgradejuju_test.go
   M cmd/jujud/agent_test.go
   M cmd/jujud/bootstrap_test.go
   M cmd/logging_test.go
   M cmd/output_test.go
   M cmd/supercommand_test.go
   M cmd/util_test.go
   M environs/agent/agent.go
   M juju/api.go
   M juju/conn.go
   M state/api/api_test.go
   M state/api/error.go
   M state/open.go
   M state/state_test.go
   M state/unit_test.go
   M store/server.go
   M store/server_test.go
   M store/store.go
   M store/store_test.go
   M testing/cmd.go
   M worker/uniter/jujuc/config-get_test.go
   M worker/uniter/jujuc/ports_test.go
   M worker/uniter/jujuc/relation-get_test.go
   M worker/uniter/jujuc/relation-ids_test.go
   M worker/uniter/jujuc/relation-list_test.go
   M worker/uniter/jujuc/relation-set_test.go
   M worker/uniter/jujuc/server.go
   M worker/uniter/jujuc/unit-get_test.go
   M worker/uniter/jujuc/util_test.go

Revision history for this message
Ian Booth (wallyworld) wrote :

LGTM. Thanks for doing this.

https://codereview.appspot.com/7424046/

Revision history for this message
William Reade (fwereade) wrote :

LGTM -- thank you very much.

https://codereview.appspot.com/7424046/

Revision history for this message
John A Meinel (jameinel) wrote :

*** Submitted:

revert only r943

William merged a change which reverted trunk back to 942
because rev 943 broke the test suite.

This just re-introduces all of the merges 944-947, so that
Gustavo and Ian don't have to re-propose and get approval
for their changes again.

I've run the test suite to make sure it passes as long as
just 943 has been removed.

In the future, you can just cherrypick-out specific revisions
with (eg):
  bzr merge . -r 943..942

And then run the test suite, and propose it. (Which is essentially
what this patch does, except it also reverts 948 which removed
all of those other revisions.)

R=wallyworld, fwereade
CC=
https://codereview.appspot.com/7424046

https://codereview.appspot.com/7424046/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/cmd.go'
2--- cmd/cmd.go 2013-02-28 08:11:29 +0000
3+++ cmd/cmd.go 2013-02-28 12:30:29 +0000
4@@ -165,7 +165,12 @@
5 if err != nil {
6 panic(err)
7 }
8- return &Context{abs, os.Stdin, os.Stdout, os.Stderr}
9+ return &Context{
10+ Dir: abs,
11+ Stdin: os.Stdin,
12+ Stdout: os.Stdout,
13+ Stderr: os.Stderr,
14+ }
15 }
16
17 // CheckEmpty is a utility function that returns an error if args is not empty.
18
19=== modified file 'cmd/cmd_test.go'
20--- cmd/cmd_test.go 2013-02-28 08:11:29 +0000
21+++ cmd/cmd_test.go 2013-02-28 12:30:29 +0000
22@@ -16,7 +16,7 @@
23 var _ = Suite(&CmdSuite{})
24
25 func (s *CmdSuite) TestContext(c *C) {
26- ctx := dummyContext(c)
27+ ctx := testing.Context(c)
28 c.Assert(ctx.AbsPath("/foo/bar"), Equals, "/foo/bar")
29 c.Assert(ctx.AbsPath("foo/bar"), Equals, filepath.Join(ctx.Dir, "foo/bar"))
30 }
31@@ -49,7 +49,7 @@
32
33 func (s *CmdSuite) TestMainInitError(c *C) {
34 for _, t := range initErrorTests {
35- ctx := dummyContext(c)
36+ ctx := testing.Context(c)
37 result := cmd.Main(t.c, ctx, []string{"--unknown"})
38 c.Assert(result, Equals, 2)
39 c.Assert(bufferString(ctx.Stdout), Equals, "")
40@@ -59,7 +59,7 @@
41 }
42
43 func (s *CmdSuite) TestMainRunError(c *C) {
44- ctx := dummyContext(c)
45+ ctx := testing.Context(c)
46 result := cmd.Main(&TestCommand{Name: "verb"}, ctx, []string{"--option", "error"})
47 c.Assert(result, Equals, 1)
48 c.Assert(bufferString(ctx.Stdout), Equals, "")
49@@ -67,7 +67,7 @@
50 }
51
52 func (s *CmdSuite) TestMainRunSilentError(c *C) {
53- ctx := dummyContext(c)
54+ ctx := testing.Context(c)
55 result := cmd.Main(&TestCommand{Name: "verb"}, ctx, []string{"--option", "silent-error"})
56 c.Assert(result, Equals, 1)
57 c.Assert(bufferString(ctx.Stdout), Equals, "")
58@@ -75,7 +75,7 @@
59 }
60
61 func (s *CmdSuite) TestMainSuccess(c *C) {
62- ctx := dummyContext(c)
63+ ctx := testing.Context(c)
64 result := cmd.Main(&TestCommand{Name: "verb"}, ctx, []string{"--option", "success!"})
65 c.Assert(result, Equals, 0)
66 c.Assert(bufferString(ctx.Stdout), Equals, "success!\n")
67@@ -84,7 +84,7 @@
68
69 func (s *CmdSuite) TestStdin(c *C) {
70 const phrase = "Do you, Juju?"
71- ctx := dummyContext(c)
72+ ctx := testing.Context(c)
73 ctx.Stdin = bytes.NewBuffer([]byte(phrase))
74 result := cmd.Main(&TestCommand{Name: "verb"}, ctx, []string{"--option", "echo"})
75 c.Assert(result, Equals, 0)
76@@ -94,7 +94,7 @@
77
78 func (s *CmdSuite) TestMainHelp(c *C) {
79 for _, arg := range []string{"-h", "--help"} {
80- ctx := dummyContext(c)
81+ ctx := testing.Context(c)
82 result := cmd.Main(&TestCommand{Name: "verb"}, ctx, []string{arg})
83 c.Assert(result, Equals, 0)
84 c.Assert(bufferString(ctx.Stdout), Equals, "")
85
86=== modified file 'cmd/filevar_test.go'
87--- cmd/filevar_test.go 2013-02-28 08:11:29 +0000
88+++ cmd/filevar_test.go 2013-02-28 12:30:29 +0000
89@@ -17,7 +17,7 @@
90 var _ = Suite(&FileVarSuite{})
91
92 func (s *FileVarSuite) SetUpTest(c *C) {
93- s.ctx = &cmd.Context{c.MkDir(), nil, nil, nil}
94+ s.ctx = testing.Context(c)
95 s.ValidPath = s.ctx.AbsPath("valid.yaml")
96 s.InvalidPath = s.ctx.AbsPath("invalid.yaml")
97 f, err := os.Create(s.ValidPath)
98
99=== modified file 'cmd/juju/addrelation_test.go'
100--- cmd/juju/addrelation_test.go 2013-02-28 08:11:29 +0000
101+++ cmd/juju/addrelation_test.go 2013-02-28 12:30:29 +0000
102@@ -12,7 +12,8 @@
103 var _ = Suite(&AddRelationSuite{})
104
105 func runAddRelation(c *C, args ...string) error {
106- return testing.RunCommand(c, &AddRelationCommand{}, args)
107+ _, err := testing.RunCommand(c, &AddRelationCommand{}, args)
108+ return err
109 }
110
111 var msWpAlreadyExists = `cannot add relation "wp:db ms:server": relation already exists`
112
113=== modified file 'cmd/juju/addunit_test.go'
114--- cmd/juju/addunit_test.go 2013-02-28 08:11:29 +0000
115+++ cmd/juju/addunit_test.go 2013-02-28 12:30:29 +0000
116@@ -13,7 +13,8 @@
117 var _ = Suite(&AddUnitSuite{})
118
119 func runAddUnit(c *C, args ...string) error {
120- return testing.RunCommand(c, &AddUnitCommand{}, args)
121+ _, err := testing.RunCommand(c, &AddUnitCommand{}, args)
122+ return err
123 }
124
125 func (s *AddUnitSuite) TestAddUnit(c *C) {
126
127=== modified file 'cmd/juju/bootstrap_test.go'
128--- cmd/juju/bootstrap_test.go 2013-02-28 08:11:29 +0000
129+++ cmd/juju/bootstrap_test.go 2013-02-28 12:30:29 +0000
130@@ -91,7 +91,7 @@
131 func (*BootstrapSuite) TestMissingEnvironment(c *C) {
132 defer makeFakeHome(c, "empty").restore()
133 // bootstrap without an environments.yaml
134- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
135+ ctx := testing.Context(c)
136 code := cmd.Main(&BootstrapCommand{}, ctx, nil)
137 c.Check(code, Equals, 1)
138 errStr := ctx.Stderr.(*bytes.Buffer).String()
139
140=== modified file 'cmd/juju/cmd_test.go'
141--- cmd/juju/cmd_test.go 2013-02-28 08:11:29 +0000
142+++ cmd/juju/cmd_test.go 2013-02-28 12:30:29 +0000
143@@ -198,7 +198,7 @@
144 }
145
146 // test relative --config path
147- ctx := &cmd.Context{c.MkDir(), nil, nil, nil}
148+ ctx := coretesting.Context(c)
149 expected := []byte("test: data")
150 path := ctx.AbsPath("testconfig.yaml")
151 file, err := os.Create(path)
152@@ -323,7 +323,7 @@
153
154 // test --config path
155 expected := []byte("this: is some test data")
156- ctx := &cmd.Context{c.MkDir(), nil, nil, nil}
157+ ctx := coretesting.Context(c)
158 path := ctx.AbsPath("testconfig.yaml")
159 file, err := os.Create(path)
160 c.Assert(err, IsNil)
161
162=== modified file 'cmd/juju/config_test.go'
163--- cmd/juju/config_test.go 2013-02-28 08:11:29 +0000
164+++ cmd/juju/config_test.go 2013-02-28 12:30:29 +0000
165@@ -8,6 +8,7 @@
166 "launchpad.net/goyaml"
167 "launchpad.net/juju-core/cmd"
168 "launchpad.net/juju-core/juju/testing"
169+ coretesting "launchpad.net/juju-core/testing"
170 )
171
172 // juju get and set tests (because one needs the other)
173@@ -61,7 +62,7 @@
174 _, err := s.State.AddService("dummy-service", sch)
175 c.Assert(err, IsNil)
176 for _, t := range getTests {
177- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
178+ ctx := coretesting.Context(c)
179 code := cmd.Main(&GetCommand{}, ctx, []string{t.service})
180 c.Check(code, Equals, 0)
181 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), Equals, "")
182@@ -129,7 +130,7 @@
183 for i, t := range setTests {
184 args := append([]string{"dummy-service"}, t.args...)
185 c.Logf("test %d. %s", i, t.about)
186- ctx := &cmd.Context{dir, &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
187+ ctx := coretesting.ContextForDir(c, dir)
188 code := cmd.Main(&SetCommand{}, ctx, args)
189 if t.err != "" {
190 c.Check(code, Not(Equals), 0)
191@@ -144,7 +145,7 @@
192 }
193
194 func setupConfigfile(c *C, dir string) {
195- ctx := &cmd.Context{dir, nil, nil, nil}
196+ ctx := coretesting.ContextForDir(c, dir)
197 path := ctx.AbsPath("testconfig.yaml")
198 file, err := os.Create(path)
199 c.Assert(err, IsNil)
200
201=== modified file 'cmd/juju/constraints_test.go'
202--- cmd/juju/constraints_test.go 2013-02-28 08:11:29 +0000
203+++ cmd/juju/constraints_test.go 2013-02-28 12:30:29 +0000
204@@ -6,6 +6,7 @@
205 "launchpad.net/juju-core/cmd"
206 "launchpad.net/juju-core/juju/testing"
207 "launchpad.net/juju-core/state"
208+ coretesting "launchpad.net/juju-core/testing"
209 )
210
211 type ConstraintsCommandsSuite struct {
212@@ -15,7 +16,7 @@
213 var _ = Suite(&ConstraintsCommandsSuite{})
214
215 func runCmdLine(c *C, com cmd.Command, args ...string) (code int, stdout, stderr string) {
216- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
217+ ctx := coretesting.Context(c)
218 code = cmd.Main(com, ctx, args)
219 stdout = ctx.Stdout.(*bytes.Buffer).String()
220 stderr = ctx.Stderr.(*bytes.Buffer).String()
221
222=== modified file 'cmd/juju/deploy_test.go'
223--- cmd/juju/deploy_test.go 2013-02-28 08:11:29 +0000
224+++ cmd/juju/deploy_test.go 2013-02-28 12:30:29 +0000
225@@ -106,7 +106,8 @@
226 var _ = Suite(&DeploySuite{})
227
228 func runDeploy(c *C, args ...string) error {
229- return coretesting.RunCommand(c, &DeployCommand{}, args)
230+ _, err := coretesting.RunCommand(c, &DeployCommand{}, args)
231+ return err
232 }
233
234 var initErrorTests = []struct {
235
236=== modified file 'cmd/juju/destroymachine_test.go'
237--- cmd/juju/destroymachine_test.go 2013-02-28 08:11:29 +0000
238+++ cmd/juju/destroymachine_test.go 2013-02-28 12:30:29 +0000
239@@ -13,7 +13,8 @@
240 var _ = Suite(&DestroyMachineSuite{})
241
242 func runDestroyMachine(c *C, args ...string) error {
243- return testing.RunCommand(c, &DestroyMachineCommand{}, args)
244+ _, err := testing.RunCommand(c, &DestroyMachineCommand{}, args)
245+ return err
246 }
247
248 func (s *DestroyMachineSuite) TestDestroyMachine(c *C) {
249
250=== modified file 'cmd/juju/destroyrelation_test.go'
251--- cmd/juju/destroyrelation_test.go 2013-02-28 08:11:29 +0000
252+++ cmd/juju/destroyrelation_test.go 2013-02-28 12:30:29 +0000
253@@ -12,7 +12,8 @@
254 var _ = Suite(&DestroyRelationSuite{})
255
256 func runDestroyRelation(c *C, args ...string) error {
257- return testing.RunCommand(c, &DestroyRelationCommand{}, args)
258+ _, err := testing.RunCommand(c, &DestroyRelationCommand{}, args)
259+ return err
260 }
261
262 func (s *DestroyRelationSuite) TestDestroyRelation(c *C) {
263
264=== modified file 'cmd/juju/destroyservice_test.go'
265--- cmd/juju/destroyservice_test.go 2013-02-28 08:11:29 +0000
266+++ cmd/juju/destroyservice_test.go 2013-02-28 12:30:29 +0000
267@@ -13,7 +13,8 @@
268 var _ = Suite(&DestroyServiceSuite{})
269
270 func runDestroyService(c *C, args ...string) error {
271- return testing.RunCommand(c, &DestroyServiceCommand{}, args)
272+ _, err := testing.RunCommand(c, &DestroyServiceCommand{}, args)
273+ return err
274 }
275
276 func (s *DestroyServiceSuite) TestSuccess(c *C) {
277
278=== modified file 'cmd/juju/destroyunit_test.go'
279--- cmd/juju/destroyunit_test.go 2013-02-28 08:11:29 +0000
280+++ cmd/juju/destroyunit_test.go 2013-02-28 12:30:29 +0000
281@@ -14,7 +14,8 @@
282 var _ = Suite(&DestroyUnitSuite{})
283
284 func runDestroyUnit(c *C, args ...string) error {
285- return testing.RunCommand(c, &DestroyUnitCommand{}, args)
286+ _, err := testing.RunCommand(c, &DestroyUnitCommand{}, args)
287+ return err
288 }
289
290 func (s *DestroyUnitSuite) TestDestroyUnit(c *C) {
291
292=== modified file 'cmd/juju/expose_test.go'
293--- cmd/juju/expose_test.go 2013-02-28 08:11:29 +0000
294+++ cmd/juju/expose_test.go 2013-02-28 12:30:29 +0000
295@@ -13,7 +13,8 @@
296 var _ = Suite(&ExposeSuite{})
297
298 func runExpose(c *C, args ...string) error {
299- return testing.RunCommand(c, &ExposeCommand{}, args)
300+ _, err := testing.RunCommand(c, &ExposeCommand{}, args)
301+ return err
302 }
303
304 func (s *ExposeSuite) assertExposed(c *C, service string) {
305
306=== modified file 'cmd/juju/init_test.go'
307--- cmd/juju/init_test.go 2013-02-28 08:11:29 +0000
308+++ cmd/juju/init_test.go 2013-02-28 12:30:29 +0000
309@@ -1,12 +1,12 @@
310 package main
311
312 import (
313+ "bytes"
314 "io/ioutil"
315-
316- "bytes"
317 . "launchpad.net/gocheck"
318 "launchpad.net/juju-core/cmd"
319 "launchpad.net/juju-core/environs"
320+ "launchpad.net/juju-core/testing"
321 "strings"
322 )
323
324@@ -18,7 +18,7 @@
325 func (*InitSuite) TestBoilerPlateEnvironment(c *C) {
326 defer makeFakeHome(c, "empty").restore()
327 // run without an environments.yaml
328- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
329+ ctx := testing.Context(c)
330 code := cmd.Main(&InitCommand{}, ctx, []string{"-w"})
331 c.Check(code, Equals, 0)
332 outStr := ctx.Stdout.(*bytes.Buffer).String()
333@@ -44,7 +44,7 @@
334 _, err := environs.WriteEnvirons(environpath, env)
335 c.Assert(err, IsNil)
336
337- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
338+ ctx := testing.Context(c)
339 code := cmd.Main(&InitCommand{}, ctx, []string{"-w"})
340 c.Check(code, Equals, 0)
341 errOut := ctx.Stdout.(*bytes.Buffer).String()
342@@ -70,7 +70,7 @@
343 _, err := environs.WriteEnvirons(environpath, env)
344 c.Assert(err, IsNil)
345
346- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
347+ ctx := testing.Context(c)
348 code := cmd.Main(&InitCommand{}, ctx, nil)
349 c.Check(code, Equals, 0)
350 errOut := ctx.Stdout.(*bytes.Buffer).String()
351
352=== modified file 'cmd/juju/resolved_test.go'
353--- cmd/juju/resolved_test.go 2013-02-28 08:11:29 +0000
354+++ cmd/juju/resolved_test.go 2013-02-28 12:30:29 +0000
355@@ -13,7 +13,8 @@
356 var _ = Suite(&ResolvedSuite{})
357
358 func runResolved(c *C, args []string) error {
359- return testing.RunCommand(c, &ResolvedCommand{}, args)
360+ _, err := testing.RunCommand(c, &ResolvedCommand{}, args)
361+ return err
362 }
363
364 var resolvedTests = []struct {
365
366=== modified file 'cmd/juju/scp_test.go'
367--- cmd/juju/scp_test.go 2013-02-28 08:11:29 +0000
368+++ cmd/juju/scp_test.go 2013-02-28 12:30:29 +0000
369@@ -63,7 +63,7 @@
370
371 for _, t := range scpTests {
372 c.Logf("testing juju scp %s", t.args)
373- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
374+ ctx := coretesting.Context(c)
375 code := cmd.Main(&SCPCommand{}, ctx, t.args)
376 c.Check(code, Equals, 0)
377 c.Check(ctx.Stderr.(*bytes.Buffer).String(), Equals, "")
378
379=== modified file 'cmd/juju/ssh_test.go'
380--- cmd/juju/ssh_test.go 2013-02-28 08:11:29 +0000
381+++ cmd/juju/ssh_test.go 2013-02-28 12:30:29 +0000
382@@ -107,7 +107,7 @@
383
384 for _, t := range sshTests {
385 c.Logf("testing juju ssh %s", t.args)
386- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
387+ ctx := coretesting.Context(c)
388 code := cmd.Main(&SSHCommand{}, ctx, t.args)
389 c.Check(code, Equals, 0)
390 c.Check(ctx.Stderr.(*bytes.Buffer).String(), Equals, "")
391
392=== modified file 'cmd/juju/status_test.go'
393--- cmd/juju/status_test.go 2013-02-28 08:11:29 +0000
394+++ cmd/juju/status_test.go 2013-02-28 12:30:29 +0000
395@@ -248,7 +248,7 @@
396 for _, t := range statusTests {
397 c.Logf("testing %s: %s", format, t.title)
398 t.prepare(s.State, s.Conn, c)
399- ctx := &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
400+ ctx := coretesting.Context(c)
401 code := cmd.Main(&StatusCommand{}, ctx, []string{"--format", format})
402 c.Check(code, Equals, 0)
403 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), Equals, "")
404
405=== modified file 'cmd/juju/unexpose_test.go'
406--- cmd/juju/unexpose_test.go 2013-02-28 08:11:29 +0000
407+++ cmd/juju/unexpose_test.go 2013-02-28 12:30:29 +0000
408@@ -13,7 +13,8 @@
409 var _ = Suite(&UnexposeSuite{})
410
411 func runUnexpose(c *C, args ...string) error {
412- return testing.RunCommand(c, &UnexposeCommand{}, args)
413+ _, err := testing.RunCommand(c, &UnexposeCommand{}, args)
414+ return err
415 }
416
417 func (s *UnexposeSuite) assertExposed(c *C, service string, expected bool) {
418
419=== modified file 'cmd/juju/upgradejuju_test.go'
420--- cmd/juju/upgradejuju_test.go 2013-02-28 08:11:29 +0000
421+++ cmd/juju/upgradejuju_test.go 2013-02-28 12:30:29 +0000
422@@ -3,7 +3,6 @@
423 import (
424 "io/ioutil"
425 . "launchpad.net/gocheck"
426- "launchpad.net/juju-core/cmd"
427 "launchpad.net/juju-core/environs"
428 "launchpad.net/juju-core/juju/testing"
429 "launchpad.net/juju-core/state"
430@@ -188,7 +187,7 @@
431 c.Check(err, ErrorMatches, test.expectInitErr)
432 continue
433 }
434- err = com.Run(&cmd.Context{c.MkDir(), nil, ioutil.Discard, ioutil.Discard})
435+ err = com.Run(coretesting.Context(c))
436 if test.expectErr != "" {
437 c.Check(err, ErrorMatches, test.expectErr)
438 continue
439@@ -230,7 +229,7 @@
440
441 func (s *UpgradeJujuSuite) TestUpgradeJujuWithRealPutTools(c *C) {
442 s.Reset(c)
443- err := coretesting.RunCommand(c, &UpgradeJujuCommand{}, []string{"--upload-tools", "--dev"})
444+ _, err := coretesting.RunCommand(c, &UpgradeJujuCommand{}, []string{"--upload-tools", "--dev"})
445 c.Assert(err, IsNil)
446 p := environs.ToolsStoragePath(version.Current)
447 r, err := s.Conn.Environ.Storage().Get(p)
448
449=== modified file 'cmd/jujud/agent_test.go'
450--- cmd/jujud/agent_test.go 2013-02-28 08:11:29 +0000
451+++ cmd/jujud/agent_test.go 2013-02-28 12:30:29 +0000
452@@ -342,7 +342,7 @@
453 info := s.StateInfo(c)
454 info.EntityName = ent.EntityName()
455 info.Password = "initial"
456- testOpenState(c, info, state.ErrUnauthorized)
457+ testOpenState(c, info, state.Unauthorizedf("unauth"))
458
459 // Read the configuration and check that we can connect with it.
460 c.Assert(refreshConfig(conf), IsNil)
461
462=== modified file 'cmd/jujud/bootstrap_test.go'
463--- cmd/jujud/bootstrap_test.go 2013-02-28 08:11:29 +0000
464+++ cmd/jujud/bootstrap_test.go 2013-02-28 12:30:29 +0000
465@@ -145,7 +145,7 @@
466 st.Close()
467 }
468 if expectErr != nil {
469- c.Assert(err, Equals, expectErr)
470+ c.Assert(state.IsUnauthorizedError(err), Equals, true)
471 } else {
472 c.Assert(err, IsNil)
473 }
474@@ -177,7 +177,7 @@
475 Addrs: []string{testing.MgoAddr},
476 CACert: []byte(testing.CACert),
477 }
478- testOpenState(c, info, state.ErrUnauthorized)
479+ testOpenState(c, info, state.Unauthorizedf("some auth problem"))
480
481 info.EntityName, info.Password = "machine-0", "foo"
482 testOpenState(c, info, nil)
483
484=== modified file 'cmd/logging_test.go'
485--- cmd/logging_test.go 2013-02-28 08:11:29 +0000
486+++ cmd/logging_test.go 2013-02-28 12:30:29 +0000
487@@ -61,7 +61,7 @@
488 {"foo", false, false, NotNil},
489 } {
490 l := &cmd.Log{t.path, t.verbose, t.debug}
491- ctx := dummyContext(c)
492+ ctx := testing.Context(c)
493 err := l.Start(ctx)
494 c.Assert(err, IsNil)
495 c.Assert(log.Target, t.target)
496@@ -71,7 +71,7 @@
497
498 func (s *LogSuite) TestStderr(c *C) {
499 l := &cmd.Log{Verbose: true}
500- ctx := dummyContext(c)
501+ ctx := testing.Context(c)
502 err := l.Start(ctx)
503 c.Assert(err, IsNil)
504 log.Printf("hello")
505@@ -80,7 +80,7 @@
506
507 func (s *LogSuite) TestRelPathLog(c *C) {
508 l := &cmd.Log{Path: "foo.log"}
509- ctx := dummyContext(c)
510+ ctx := testing.Context(c)
511 err := l.Start(ctx)
512 c.Assert(err, IsNil)
513 log.Printf("hello")
514@@ -93,7 +93,7 @@
515 func (s *LogSuite) TestAbsPathLog(c *C) {
516 path := filepath.Join(c.MkDir(), "foo.log")
517 l := &cmd.Log{Path: path}
518- ctx := dummyContext(c)
519+ ctx := testing.Context(c)
520 err := l.Start(ctx)
521 c.Assert(err, IsNil)
522 log.Printf("hello")
523
524=== modified file 'cmd/output_test.go'
525--- cmd/output_test.go 2013-02-28 08:11:29 +0000
526+++ cmd/output_test.go 2013-02-28 12:30:29 +0000
527@@ -4,6 +4,7 @@
528 "launchpad.net/gnuflag"
529 . "launchpad.net/gocheck"
530 "launchpad.net/juju-core/cmd"
531+ "launchpad.net/juju-core/testing"
532 )
533
534 // OutputCommand is a command that uses the output.go formatters.
535@@ -102,7 +103,7 @@
536 }
537 for i, t := range tests {
538 c.Logf(" test %d", i)
539- ctx := dummyContext(c)
540+ ctx := testing.Context(c)
541 result := cmd.Main(&OutputCommand{value: t.value}, ctx, args)
542 c.Assert(result, Equals, 0)
543 c.Assert(bufferString(ctx.Stdout), Equals, t.output)
544@@ -110,7 +111,7 @@
545 }
546 }
547
548- ctx := dummyContext(c)
549+ ctx := testing.Context(c)
550 result := cmd.Main(&OutputCommand{}, ctx, []string{"--format", "cuneiform"})
551 c.Assert(result, Equals, 2)
552 c.Assert(bufferString(ctx.Stdout), Equals, "")
553@@ -122,7 +123,7 @@
554 // this argument format.
555 // LP #1059921
556 func (s *CmdSuite) TestFormatAlternativeSyntax(c *C) {
557- ctx := dummyContext(c)
558+ ctx := testing.Context(c)
559 result := cmd.Main(&OutputCommand{}, ctx, []string{"--format=json"})
560 c.Assert(result, Equals, 0)
561 c.Assert(bufferString(ctx.Stdout), Equals, "null\n")
562
563=== modified file 'cmd/supercommand_test.go'
564--- cmd/supercommand_test.go 2013-02-28 08:11:29 +0000
565+++ cmd/supercommand_test.go 2013-02-28 12:30:29 +0000
566@@ -99,7 +99,7 @@
567 }()
568 jc := &cmd.SuperCommand{Name: "jujutest", Log: &cmd.Log{}}
569 jc.Register(&TestCommand{Name: "blah"})
570- ctx := dummyContext(c)
571+ ctx := testing.Context(c)
572 code := cmd.Main(jc, ctx, []string{"blah", "--option", "error", "--debug"})
573 c.Assert(code, Equals, 1)
574 c.Assert(bufferString(ctx.Stderr), Matches, `.* JUJU jujutest blah command failed: BAM!
575
576=== modified file 'cmd/util_test.go'
577--- cmd/util_test.go 2013-02-28 08:11:29 +0000
578+++ cmd/util_test.go 2013-02-28 12:30:29 +0000
579@@ -6,14 +6,9 @@
580 "fmt"
581 "io"
582 "launchpad.net/gnuflag"
583- . "launchpad.net/gocheck"
584 "launchpad.net/juju-core/cmd"
585 )
586
587-func dummyContext(c *C) *cmd.Context {
588- return &cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}}
589-}
590-
591 func bufferString(stream io.Writer) string {
592 return stream.(*bytes.Buffer).String()
593 }
594
595=== modified file 'environs/agent/agent.go'
596--- environs/agent/agent.go 2013-02-28 08:11:29 +0000
597+++ environs/agent/agent.go 2013-02-28 12:30:29 +0000
598@@ -205,7 +205,7 @@
599 if err == nil {
600 return st, "", nil
601 }
602- if err != state.ErrUnauthorized {
603+ if !state.IsUnauthorizedError(err) {
604 return nil, "", err
605 }
606 // Access isn't authorized even though we have a password
607
608=== modified file 'juju/api.go'
609--- juju/api.go 2013-02-28 08:11:29 +0000
610+++ juju/api.go 2013-02-28 12:30:29 +0000
611@@ -29,7 +29,7 @@
612 info.Password = password
613
614 st, err := api.Open(info)
615- // TODO(rog): handle ErrUnauthorized when the API handles passwords.
616+ // TODO(rog): handle errUnauthorized when the API handles passwords.
617 if err != nil {
618 return nil, err
619 }
620
621=== modified file 'juju/conn.go'
622--- juju/conn.go 2013-02-28 08:11:29 +0000
623+++ juju/conn.go 2013-02-28 12:30:29 +0000
624@@ -43,7 +43,7 @@
625 }
626 info.Password = password
627 st, err := state.Open(info)
628- if err == state.ErrUnauthorized {
629+ if state.IsUnauthorizedError(err) {
630 // We can't connect with the administrator password,;
631 // perhaps this was the first connection and the
632 // password has not been changed yet.
633@@ -54,7 +54,7 @@
634 // initialized and the initial password set.
635 for a := redialStrategy.Start(); a.Next(); {
636 st, err = state.Open(info)
637- if err != state.ErrUnauthorized {
638+ if !state.IsUnauthorizedError(err) {
639 break
640 }
641 }
642
643=== modified file 'state/api/api_test.go'
644--- state/api/api_test.go 2013-02-28 08:11:29 +0000
645+++ state/api/api_test.go 2013-02-28 12:30:29 +0000
646@@ -679,7 +679,7 @@
647 c.Assert(ok, Equals, true)
648
649 // Wait long enough for the Next request to be sent
650- // so it's blocking on the server side.
651+ // so it's blocking on the server side.
652 time.Sleep(50 * time.Millisecond)
653 c.Logf("stopping server")
654 err = srv.Stop()
655@@ -752,7 +752,7 @@
656 err: state.NotFoundf("hello"),
657 code: api.CodeNotFound,
658 }, {
659- err: state.ErrUnauthorized,
660+ err: state.Unauthorizedf("hello"),
661 code: api.CodeUnauthorized,
662 }, {
663 err: state.ErrCannotEnterScopeYet,
664
665=== modified file 'state/api/error.go'
666--- state/api/error.go 2013-02-28 08:11:29 +0000
667+++ state/api/error.go 2013-02-28 12:30:29 +0000
668@@ -33,7 +33,6 @@
669 )
670
671 var singletonErrorCodes = map[error]string{
672- state.ErrUnauthorized: CodeUnauthorized,
673 state.ErrCannotEnterScopeYet: CodeCannotEnterScopeYet,
674 state.ErrCannotEnterScope: CodeCannotEnterScope,
675 state.ErrExcessiveContention: CodeExcessiveContention,
676@@ -62,6 +61,8 @@
677 code := singletonErrorCodes[err]
678 switch {
679 case code != "":
680+ case state.IsUnauthorizedError(err):
681+ code = CodeUnauthorized
682 case state.IsNotFound(err):
683 code = CodeNotFound
684 case state.IsNotAssigned(err):
685
686=== modified file 'state/open.go'
687--- state/open.go 2013-02-28 08:11:29 +0000
688+++ state/open.go 2013-02-28 12:30:29 +0000
689@@ -42,7 +42,7 @@
690 // Open connects to the server described by the given
691 // info, waits for it to be initialized, and returns a new State
692 // representing the environment connected to.
693-// It returns ErrUnauthorized if access is unauthorized.
694+// It returns unauthorizedError if access is unauthorized.
695 func Open(info *Info) (*State, error) {
696 log.Printf("state: opening state; mongo addresses: %q; entity %q", info.Addrs, info.EntityName)
697 if len(info.Addrs) == 0 {
698@@ -89,7 +89,7 @@
699
700 // Initialize sets up an initial empty state and returns it.
701 // This needs to be performed only once for a given environment.
702-// It returns ErrUnauthorized if access is unauthorized.
703+// It returns unauthorizedError if access is unauthorized.
704 func Initialize(info *Info, cfg *config.Config) (rst *State, err error) {
705 st, err := Open(info)
706 if err != nil {
707@@ -148,7 +148,30 @@
708 logSizeTests = 1000000
709 )
710
711-var ErrUnauthorized = errors.New("unauthorized access")
712+// unauthorizedError represents the error that an operation is unauthorized.
713+// Use IsUnauthorized() to determine if the error was related to authorization failure.
714+type unauthorizedError struct {
715+ msg string
716+ error
717+}
718+
719+func IsUnauthorizedError(err error) bool {
720+ _, ok := err.(*unauthorizedError)
721+ return ok
722+}
723+
724+func (e *unauthorizedError) Error() string {
725+ if e.error != nil {
726+ return fmt.Sprintf("%s: %v", e.msg, e.error.Error())
727+ }
728+ return e.msg
729+}
730+
731+// Unauthorizedf returns an error for which IsUnauthorizedError returns true.
732+// It is mainly used for testing.
733+func Unauthorizedf(format string, args ...interface{}) error {
734+ return &unauthorizedError{fmt.Sprintf(format, args...), nil}
735+}
736
737 func maybeUnauthorized(err error, msg string) error {
738 if err == nil {
739@@ -157,10 +180,10 @@
740 // Unauthorized access errors have no error code,
741 // just a simple error string.
742 if err.Error() == "auth fails" {
743- return ErrUnauthorized
744+ return &unauthorizedError{msg, err}
745 }
746 if err, ok := err.(*mgo.QueryError); ok && err.Code == 10057 {
747- return ErrUnauthorized
748+ return &unauthorizedError{msg, err}
749 }
750 return fmt.Errorf("%s: %v", msg, err)
751 }
752
753=== modified file 'state/state_test.go'
754--- state/state_test.go 2013-02-28 08:11:29 +0000
755+++ state/state_test.go 2013-02-28 12:30:29 +0000
756@@ -1173,11 +1173,11 @@
757 info := state.TestingStateInfo()
758 info.EntityName, info.Password = "arble", "bar"
759 err := tryOpenState(info)
760- c.Assert(err, Equals, state.ErrUnauthorized)
761+ c.Assert(state.IsUnauthorizedError(err), Equals, true)
762
763 info.EntityName, info.Password = "arble", ""
764 err = tryOpenState(info)
765- c.Assert(err, Equals, state.ErrUnauthorized)
766+ c.Assert(state.IsUnauthorizedError(err), Equals, true)
767
768 info.EntityName, info.Password = "", ""
769 err = tryOpenState(info)
770@@ -1253,7 +1253,7 @@
771 info.EntityName = ent.EntityName()
772 info.Password = "bar"
773 err = tryOpenState(info)
774- c.Assert(err, Equals, state.ErrUnauthorized)
775+ c.Assert(state.IsUnauthorizedError(err), Equals, true)
776
777 // Check that we can log in with the correct password.
778 info.Password = "foo"
779@@ -1271,7 +1271,7 @@
780 // Check that we cannot log in with the old password.
781 info.Password = "foo"
782 err = tryOpenState(info)
783- c.Assert(err, Equals, state.ErrUnauthorized)
784+ c.Assert(state.IsUnauthorizedError(err), Equals, true)
785
786 // Check that we can log in with the correct password.
787 info.Password = "bar"
788@@ -1299,7 +1299,7 @@
789 defer s.State.SetAdminMongoPassword("")
790 info := state.TestingStateInfo()
791 err = tryOpenState(info)
792- c.Assert(err, Equals, state.ErrUnauthorized)
793+ c.Assert(state.IsUnauthorizedError(err), Equals, true)
794
795 info.Password = "foo"
796 err = tryOpenState(info)
797
798=== modified file 'state/unit_test.go'
799--- state/unit_test.go 2013-02-28 08:11:29 +0000
800+++ state/unit_test.go 2013-02-28 12:30:29 +0000
801@@ -214,7 +214,7 @@
802 info.EntityName = m.EntityName()
803 info.Password = "foo1"
804 err = tryOpenState(info)
805- c.Assert(err, Equals, state.ErrUnauthorized)
806+ c.Assert(state.IsUnauthorizedError(err), Equals, true)
807
808 // Connect as the machine entity.
809 info.EntityName = m.EntityName()
810
811=== modified file 'store/server.go'
812--- store/server.go 2013-02-28 08:11:29 +0000
813+++ store/server.go 2013-02-28 12:30:29 +0000
814@@ -156,17 +156,22 @@
815 return
816 }
817 key := strings.Split(base, ":")
818+ r.ParseForm()
819+ listing := r.Form.Get("list") == "1"
820 prefix := false
821 if key[len(key)-1] == "*" {
822 prefix = true
823 key = key[:len(key)-1]
824- if len(key) == 0 {
825+ if len(key) == 0 && !listing {
826 // No point in counting something unknown.
827 w.WriteHeader(http.StatusForbidden)
828 return
829 }
830 }
831- r.ParseForm()
832+ if listing && prefix {
833+ s.serveStatsList(w, r, key)
834+ return
835+ }
836 sum, err := s.store.SumCounter(key, prefix)
837 if err != nil {
838 log.Printf("store: cannot sum counter: %v", err)
839@@ -174,6 +179,10 @@
840 return
841 }
842 data := []byte(strconv.FormatInt(sum, 10))
843+ if listing {
844+ // Listing a single item.. silly, but whatever.
845+ data = []byte(base + " " + string(data) + "\n")
846+ }
847 w.Header().Set("Content-Type", "text/plain")
848 w.Header().Set("Content-Length", strconv.Itoa(len(data)))
849 _, err = w.Write(data)
850@@ -183,6 +192,62 @@
851 }
852 }
853
854+func (s *Server) serveStatsList(w http.ResponseWriter, r *http.Request, prefix []string) {
855+ entries, err := s.store.ListCounters(prefix)
856+ if err != nil {
857+ log.Printf("store: cannot list counter: %v", err)
858+ w.WriteHeader(http.StatusInternalServerError)
859+ return
860+ }
861+
862+ // First build keys and figure max key length.
863+ var buf []byte
864+ var maxKeyLength int
865+ type resultItem struct {
866+ key string
867+ count int64
868+ }
869+ var result []resultItem
870+ for i := range entries {
871+ entry := &entries[i]
872+ for j := range entry.Key {
873+ buf = append(buf, entry.Key[j]...)
874+ buf = append(buf, ':')
875+ }
876+ if entry.Prefix {
877+ buf = append(buf, '*')
878+ } else {
879+ buf = buf[:len(buf)-1]
880+ }
881+ if maxKeyLength < len(buf) {
882+ maxKeyLength = len(buf)
883+ }
884+ result = append(result, resultItem{string(buf), entry.Count})
885+ buf = buf[:0]
886+ }
887+
888+ // Then join all keys and counts in a single formatted buffer.
889+ spaces := make([]byte, maxKeyLength+2)
890+ for i := range spaces {
891+ spaces[i] = ' '
892+ }
893+ for i := range result {
894+ item := &result[i]
895+ buf = append(buf, item.key...)
896+ buf = append(buf, spaces[len(item.key):]...)
897+ buf = strconv.AppendInt(buf, item.count, 10)
898+ buf = append(buf, '\n')
899+ }
900+
901+ w.Header().Set("Content-Type", "text/plain")
902+ w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
903+ _, err = w.Write(buf)
904+ if err != nil {
905+ log.Printf("store: cannot write content: %v", err)
906+ w.WriteHeader(http.StatusInternalServerError)
907+ }
908+}
909+
910 func (s *Server) serveBlitzKey(w http.ResponseWriter, r *http.Request) {
911 w.Header().Set("Connection", "close")
912 w.Header().Set("Content-Type", "text/plain")
913
914=== modified file 'store/server_test.go'
915--- store/server_test.go 2013-02-28 08:11:29 +0000
916+++ store/server_test.go 2013-02-28 12:30:29 +0000
917@@ -163,7 +163,7 @@
918 }
919
920 func (s *StoreSuite) TestStatsCounter(c *C) {
921- for _, key := range [][]string{{"a", "b"}, {"a", "b"}, {"a"}} {
922+ for _, key := range [][]string{{"a", "b"}, {"a", "b"}, {"a", "c"}, {"a"}} {
923 err := s.store.IncCounter(key)
924 c.Assert(err, IsNil)
925 }
926@@ -171,9 +171,11 @@
927 server, _ := s.prepareServer(c)
928
929 expected := map[string]string{
930- "a:b": "2",
931- "a:*": "3",
932- "a": "1",
933+ "a:b": "2",
934+ "a:b:*": "0",
935+ "a:*": "3",
936+ "a": "1",
937+ "a:b:c": "0",
938 }
939
940 for counter, n := range expected {
941@@ -190,6 +192,47 @@
942 }
943 }
944
945+func (s *StoreSuite) TestStatsCounterList(c *C) {
946+ incs := [][]string{
947+ {"a"},
948+ {"a", "b"},
949+ {"a", "b", "c"},
950+ {"a", "b", "c"},
951+ {"a", "b", "d"},
952+ {"a", "b", "e"},
953+ {"a", "f", "g"},
954+ {"a", "f", "h"},
955+ {"a", "i"},
956+ {"j", "k"},
957+ }
958+ for _, key := range incs {
959+ err := s.store.IncCounter(key)
960+ c.Assert(err, IsNil)
961+ }
962+
963+ server, _ := s.prepareServer(c)
964+
965+ tests := [][]string{
966+ {"a", "a 1\n"},
967+ {"a:*", "a:b:* 4\na:f:* 2\na:b 1\na:i 1\n"},
968+ {"a:b:*", "a:b:c 2\na:b:d 1\na:b:e 1\n"},
969+ }
970+
971+ for i := range tests {
972+ req, err := http.NewRequest("GET", "/stats/counter/"+tests[i][0], nil)
973+ c.Assert(err, IsNil)
974+ req.Form = url.Values{"list": []string{"1"}}
975+ rec := httptest.NewRecorder()
976+ server.ServeHTTP(rec, req)
977+
978+ data, err := ioutil.ReadAll(rec.Body)
979+ c.Assert(string(data), Equals, tests[i][1])
980+
981+ c.Assert(rec.Header().Get("Content-Type"), Equals, "text/plain")
982+ c.Assert(rec.Header().Get("Content-Length"), Equals, strconv.Itoa(len(tests[i][1])))
983+ }
984+}
985+
986 func (s *StoreSuite) TestBlitzKey(c *C) {
987 server, _ := s.prepareServer(c)
988
989
990=== modified file 'store/store.go'
991--- store/store.go 2013-02-28 08:11:29 +0000
992+++ store/store.go 2013-02-28 12:30:29 +0000
993@@ -16,6 +16,7 @@
994 "launchpad.net/juju-core/log"
995 "sort"
996 "strconv"
997+ "strings"
998 "sync"
999 "time"
1000 )
1001@@ -45,8 +46,10 @@
1002
1003 // Cache for statistics key words (two generations).
1004 cacheMu sync.RWMutex
1005- statsTokenNew map[string]int
1006- statsTokenOld map[string]int
1007+ statsIdNew map[string]int
1008+ statsIdOld map[string]int
1009+ statsTokenNew map[int]string
1010+ statsTokenOld map[int]string
1011 }
1012
1013 // Open creates a new session with the store. It connects to the MongoDB
1014@@ -130,10 +133,7 @@
1015 err = nil
1016 id, found := s.statsTokenId(key[i])
1017 if !found {
1018- var t struct {
1019- Id int "_id"
1020- Token string "t"
1021- }
1022+ var t tokenId
1023 err = tokens.Find(bson.D{{"t", key[i]}}).One(&t)
1024 if err == mgo.ErrNotFound {
1025 if !write {
1026@@ -163,7 +163,12 @@
1027 return string(skey), nil
1028 }
1029
1030-const statsTokenCacheSize = 512
1031+const statsTokenCacheSize = 1024
1032+
1033+type tokenId struct {
1034+ Id int "_id"
1035+ Token string "t"
1036+}
1037
1038 // cacheStatsTokenId adds the id for token into the cache.
1039 // The cache has two generations so that the least frequently used
1040@@ -172,25 +177,45 @@
1041 s.cacheMu.Lock()
1042 defer s.cacheMu.Unlock()
1043 // Can't possibly be >, but reviews want it for defensiveness.
1044- if len(s.statsTokenNew) >= statsTokenCacheSize {
1045+ if len(s.statsIdNew) >= statsTokenCacheSize {
1046+ s.statsIdOld = s.statsIdNew
1047+ s.statsIdNew = nil
1048 s.statsTokenOld = s.statsTokenNew
1049 s.statsTokenNew = nil
1050 }
1051- if s.statsTokenNew == nil {
1052- s.statsTokenNew = make(map[string]int, statsTokenCacheSize)
1053+ if s.statsIdNew == nil {
1054+ s.statsIdNew = make(map[string]int, statsTokenCacheSize)
1055+ s.statsTokenNew = make(map[int]string, statsTokenCacheSize)
1056 }
1057- s.statsTokenNew[token] = id
1058+ s.statsIdNew[token] = id
1059+ s.statsTokenNew[id] = token
1060 }
1061
1062 // statsTokenId returns the id for token from the cache, if found.
1063 func (s *Store) statsTokenId(token string) (id int, found bool) {
1064 s.cacheMu.RLock()
1065- id, found = s.statsTokenNew[token]
1066- if found {
1067- s.cacheMu.RUnlock()
1068- return
1069- }
1070- id, found = s.statsTokenOld[token]
1071+ id, found = s.statsIdNew[token]
1072+ if found {
1073+ s.cacheMu.RUnlock()
1074+ return
1075+ }
1076+ id, found = s.statsIdOld[token]
1077+ s.cacheMu.RUnlock()
1078+ if found {
1079+ s.cacheStatsTokenId(token, id)
1080+ }
1081+ return
1082+}
1083+
1084+// statsIdToken returns the token for id from the cache, if found.
1085+func (s *Store) statsIdToken(id int) (token string, found bool) {
1086+ s.cacheMu.RLock()
1087+ token, found = s.statsTokenNew[id]
1088+ if found {
1089+ s.cacheMu.RUnlock()
1090+ return
1091+ }
1092+ token, found = s.statsTokenOld[id]
1093 s.cacheMu.RUnlock()
1094 if found {
1095 s.cacheStatsTokenId(token, id)
1096@@ -219,7 +244,8 @@
1097 }
1098
1099 // SumCounter returns the sum of all the counters that exactly match key,
1100-// or that are prefixed by it if prefix is true.
1101+// or that are prefixed by it if prefix is true. In the latter case, a key
1102+// that matches the prefix exactly won't be included in the sum.
1103 func (s *Store) SumCounter(key []string, prefix bool) (count int64, err error) {
1104 session := s.session.Copy()
1105 defer session.Close()
1106@@ -234,7 +260,7 @@
1107
1108 var regex string
1109 if prefix {
1110- regex = "^" + skey
1111+ regex = "^" + skey + ".+"
1112 } else {
1113 regex = "^" + skey + "$"
1114 }
1115@@ -252,6 +278,125 @@
1116 return 0, err
1117 }
1118
1119+type Counter struct {
1120+ Key []string
1121+ Count int64
1122+ Prefix bool
1123+}
1124+
1125+// ListCounters returns a list of all keys directly under the provided prefix,
1126+// and a prefix-aggregated view of deeper keys.
1127+//
1128+// For example, given the following counts:
1129+//
1130+// {"a", "b"}: 1,
1131+// {"a", "c"}: 3
1132+// {"a", "c", "d"}: 5
1133+// {"a", "c", "e"}: 7
1134+//
1135+// The following key prefixes will present the respective list results:
1136+//
1137+// {"a"} => {{"a", "b"}, 1, false}, {{"a", "c"}, 3, false}, {{"a", "c"}, 12, true}
1138+// {"a", "c"} => {{"a", "c", "d"}, 3, false}, {"a", "c", "e"}, 5, false}
1139+//
1140+func (s *Store) ListCounters(keyPrefix []string) ([]Counter, error) {
1141+ session := s.session.Copy()
1142+ defer session.Close()
1143+
1144+ tokensColl := session.StatTokens()
1145+ countersColl := session.StatCounters()
1146+
1147+ skey, err := s.statsKey(session, keyPrefix, false)
1148+ if err == ErrNotFound {
1149+ return nil, nil
1150+ }
1151+ if err != nil {
1152+ return nil, err
1153+ }
1154+ regex := "^" + skey + ".+"
1155+
1156+ // For a search key "a:b:" matching a key "a:b:c:d:e:", this map function emits "a:b:c:*".
1157+ // For a search key "a:b:" matching a key "a:b:c:", it emits "a:b:c:".
1158+ mapf := fmt.Sprintf(`
1159+ function() {
1160+ var k = this.k;
1161+ var i = k.indexOf(':', %d)+1;
1162+ if (k.length > i) { k = k.substr(0, i)+'*'; }
1163+ emit(k, this.c);
1164+ }`, len(skey))
1165+ reducef := "function(key, values) { return Array.sum(values); }"
1166+ job := mgo.MapReduce{Map: mapf, Reduce: reducef}
1167+
1168+ var result []struct {
1169+ Key string `bson:"_id"`
1170+ Value int64
1171+ }
1172+ _, err = countersColl.Find(bson.D{{"k", bson.D{{"$regex", regex}}}}).MapReduce(&job, &result)
1173+ if err != nil {
1174+ return nil, err
1175+ }
1176+ var counters []Counter
1177+ for i := range result {
1178+ ids := strings.Split(result[i].Key, ":")
1179+ tokens := make([]string, 0, len(ids))
1180+ for i := 0; i < len(ids)-1; i++ {
1181+ if ids[i] == "*" {
1182+ continue
1183+ }
1184+ id, err := strconv.ParseInt(ids[i], 32, 32)
1185+ if err != nil {
1186+ return nil, fmt.Errorf("store: invalid id: %q", ids[i])
1187+ }
1188+ token, found := s.statsIdToken(int(id))
1189+ if !found {
1190+ var t tokenId
1191+ err = tokensColl.FindId(id).One(&t)
1192+ if err == mgo.ErrNotFound {
1193+ return nil, fmt.Errorf("store: internal error; token id not found: %d", id)
1194+ }
1195+ s.cacheStatsTokenId(t.Token, t.Id)
1196+ token = t.Token
1197+ }
1198+ tokens = append(tokens, token)
1199+ }
1200+ counter := Counter{
1201+ Key: tokens,
1202+ Count: result[i].Value,
1203+ Prefix: len(ids) > 0 && ids[len(ids)-1] == "*",
1204+ }
1205+ counters = append(counters, counter)
1206+ }
1207+ sort.Sort(sortableCounters(counters))
1208+ return counters, nil
1209+}
1210+
1211+type sortableCounters []Counter
1212+
1213+func (s sortableCounters) Len() int { return len(s) }
1214+func (s sortableCounters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
1215+func (s sortableCounters) Less(i, j int) bool {
1216+ // Larger counts first.
1217+ if s[i].Count != s[j].Count {
1218+ return s[j].Count < s[i].Count
1219+ }
1220+ // Then smaller/shorter keys first.
1221+ ki := s[i].Key
1222+ kj := s[j].Key
1223+ for n := range ki {
1224+ if n >= len(kj) {
1225+ return false
1226+ }
1227+ if ki[n] != kj[n] {
1228+ return ki[n] < kj[n]
1229+ }
1230+ }
1231+ if len(ki) < len(kj) {
1232+ return true
1233+ }
1234+ // Then full keys first.
1235+ return !s[i].Prefix && s[j].Prefix
1236+}
1237+
1238 // A CharmPublisher is responsible for importing a charm dir onto the store.
1239 type CharmPublisher struct {
1240 revision int
1241
1242=== modified file 'store/store_test.go'
1243--- store/store_test.go 2013-02-28 08:11:29 +0000
1244+++ store/store_test.go 2013-02-28 12:30:29 +0000
1245@@ -464,8 +464,8 @@
1246 {[]string{"a", "b", "c"}, false, 10},
1247 {[]string{"a", "b"}, false, 7},
1248 {[]string{"a", "z", "b"}, false, 3},
1249- {[]string{"a", "b", "c"}, true, 10},
1250- {[]string{"a", "b"}, true, 17},
1251+ {[]string{"a", "b", "c"}, true, 0},
1252+ {[]string{"a", "b"}, true, 10},
1253 {[]string{"a"}, true, 20},
1254 {[]string{"b"}, true, 0},
1255 }
1256@@ -523,7 +523,7 @@
1257 c.Assert(err, IsNil)
1258 c.Assert(sum, Equals, int64(0))
1259
1260- const genSize = 512
1261+ const genSize = 1024
1262
1263 // All of these will be cached, as we have two generations
1264 // of genSize entries each.
1265@@ -597,6 +597,63 @@
1266 c.Assert(sum, Equals, int64(10))
1267 }
1268
1269+func (s *StoreSuite) TestCounterList(c *C) {
1270+ incs := [][]string{
1271+ {"c", "b", "a"}, // Assign internal id c < id b < id a, to make sorting slightly trickier.
1272+ {"a"},
1273+ {"a", "c"},
1274+ {"a", "b"},
1275+ {"a", "b", "c"},
1276+ {"a", "b", "c"},
1277+ {"a", "b", "e"},
1278+ {"a", "b", "d"},
1279+ {"a", "f", "g"},
1280+ {"a", "f", "h"},
1281+ {"a", "i"},
1282+ {"a", "i", "j"},
1283+ {"k", "l"},
1284+ }
1285+ for _, key := range incs {
1286+ err := s.store.IncCounter(key)
1287+ c.Assert(err, IsNil)
1288+ }
1289+
1290+ tests := []struct {
1291+ prefix []string
1292+ result []store.Counter
1293+ }{
1294+ {
1295+ []string{"a"},
1296+ []store.Counter{
1297+ {[]string{"a", "b"}, 4, true},
1298+ {[]string{"a", "f"}, 2, true},
1299+ {[]string{"a", "b"}, 1, false},
1300+ {[]string{"a", "c"}, 1, false},
1301+ {[]string{"a", "i"}, 1, false},
1302+ {[]string{"a", "i"}, 1, true},
1303+ },
1304+ }, {
1305+ []string{"a", "b"},
1306+ []store.Counter{
1307+ {[]string{"a", "b", "c"}, 2, false},
1308+ {[]string{"a", "b", "d"}, 1, false},
1309+ {[]string{"a", "b", "e"}, 1, false},
1310+ },
1311+ },
1312+ }
1313+
1314+ // Use a different store to exercise cache filling.
1315+ st, err := store.Open(s.Addr)
1316+ c.Assert(err, IsNil)
1317+ defer st.Close()
1318+
1319+ for i := range tests {
1320+ result, err := st.ListCounters(tests[i].prefix)
1321+ c.Assert(err, IsNil)
1322+ c.Assert(result, DeepEquals, tests[i].result)
1323+ }
1324+}
1325+
1326 func (s *TrivialSuite) TestEventString(c *C) {
1327 c.Assert(store.EventPublished, Matches, "published")
1328 c.Assert(store.EventPublishError, Matches, "publish-error")
1329
1330=== modified file 'testing/cmd.go'
1331--- testing/cmd.go 2013-02-28 08:11:29 +0000
1332+++ testing/cmd.go 2013-02-28 12:30:29 +0000
1333@@ -27,15 +27,38 @@
1334 return c.Init(f.Args())
1335 }
1336
1337+// Context creates a simple command execution context with the current
1338+// dir set to a newly created directory within the test directory.
1339+func Context(c *C) *cmd.Context {
1340+ return &cmd.Context{
1341+ Dir: c.MkDir(),
1342+ Stdin: &bytes.Buffer{},
1343+ Stdout: &bytes.Buffer{},
1344+ Stderr: &bytes.Buffer{},
1345+ }
1346+}
1347+
1348+// ContextForDir creates a simple command execution context with the current
1349+// dir set to the specified directory.
1350+func ContextForDir(c *C, dir string) *cmd.Context {
1351+ return &cmd.Context{
1352+ Dir: dir,
1353+ Stdin: &bytes.Buffer{},
1354+ Stdout: &bytes.Buffer{},
1355+ Stderr: &bytes.Buffer{},
1356+ }
1357+}
1358+
1359 // RunCommand will run a command with the specified args. The returned error
1360 // may come from either the parsing of the args, the command initialisation or
1361 // the actual running of the command. Access to the resulting output streams
1362-// is not provided with this function.
1363-func RunCommand(c *C, com cmd.Command, args []string) error {
1364+// is provided through the returned context instance.
1365+func RunCommand(c *C, com cmd.Command, args []string) (*cmd.Context, error) {
1366 if err := InitCommand(com, args); err != nil {
1367- return err
1368+ return nil, err
1369 }
1370- return com.Run(&cmd.Context{c.MkDir(), &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}})
1371+ var context = Context(c)
1372+ return context, com.Run(context)
1373 }
1374
1375 // TestInit checks that a command initialises correctly with the given set of
1376
1377=== modified file 'worker/uniter/jujuc/config-get_test.go'
1378--- worker/uniter/jujuc/config-get_test.go 2013-02-28 08:11:29 +0000
1379+++ worker/uniter/jujuc/config-get_test.go 2013-02-28 12:30:29 +0000
1380@@ -41,7 +41,7 @@
1381 hctx := s.GetHookContext(c, -1, "")
1382 com, err := jujuc.NewCommand(hctx, "config-get")
1383 c.Assert(err, IsNil)
1384- ctx := dummyContext(c)
1385+ ctx := testing.Context(c)
1386 code := cmd.Main(com, ctx, t.args)
1387 c.Assert(code, Equals, 0)
1388 c.Assert(bufferString(ctx.Stderr), Equals, "")
1389@@ -53,7 +53,7 @@
1390 hctx := s.GetHookContext(c, -1, "")
1391 com, err := jujuc.NewCommand(hctx, "config-get")
1392 c.Assert(err, IsNil)
1393- ctx := dummyContext(c)
1394+ ctx := testing.Context(c)
1395 code := cmd.Main(com, ctx, []string{"--help"})
1396 c.Assert(code, Equals, 0)
1397 c.Assert(bufferString(ctx.Stdout), Equals, "")
1398@@ -74,7 +74,7 @@
1399 hctx := s.GetHookContext(c, -1, "")
1400 com, err := jujuc.NewCommand(hctx, "config-get")
1401 c.Assert(err, IsNil)
1402- ctx := dummyContext(c)
1403+ ctx := testing.Context(c)
1404 code := cmd.Main(com, ctx, []string{"--output", "some-file", "monsters"})
1405 c.Assert(code, Equals, 0)
1406 c.Assert(bufferString(ctx.Stderr), Equals, "")
1407
1408=== modified file 'worker/uniter/jujuc/ports_test.go'
1409--- worker/uniter/jujuc/ports_test.go 2013-02-28 08:11:29 +0000
1410+++ worker/uniter/jujuc/ports_test.go 2013-02-28 12:30:29 +0000
1411@@ -29,7 +29,7 @@
1412 for _, t := range portsTests {
1413 com, err := jujuc.NewCommand(hctx, t.cmd[0])
1414 c.Assert(err, IsNil)
1415- ctx := dummyContext(c)
1416+ ctx := testing.Context(c)
1417 code := cmd.Main(com, ctx, t.cmd[1:])
1418 c.Assert(code, Equals, 0)
1419 c.Assert(bufferString(ctx.Stdout), Equals, "")
1420
1421=== modified file 'worker/uniter/jujuc/relation-get_test.go'
1422--- worker/uniter/jujuc/relation-get_test.go 2013-02-28 08:11:29 +0000
1423+++ worker/uniter/jujuc/relation-get_test.go 2013-02-28 12:30:29 +0000
1424@@ -5,6 +5,7 @@
1425 "io/ioutil"
1426 . "launchpad.net/gocheck"
1427 "launchpad.net/juju-core/cmd"
1428+ "launchpad.net/juju-core/testing"
1429 "launchpad.net/juju-core/worker/uniter/jujuc"
1430 "path/filepath"
1431 )
1432@@ -165,7 +166,7 @@
1433 hctx := s.GetHookContext(c, t.relid, t.unit)
1434 com, err := jujuc.NewCommand(hctx, "relation-get")
1435 c.Assert(err, IsNil)
1436- ctx := dummyContext(c)
1437+ ctx := testing.Context(c)
1438 code := cmd.Main(com, ctx, t.args)
1439 c.Assert(code, Equals, t.code)
1440 if code == 0 {
1441@@ -230,7 +231,7 @@
1442 hctx := s.GetHookContext(c, t.relid, t.unit)
1443 com, err := jujuc.NewCommand(hctx, "relation-get")
1444 c.Assert(err, IsNil)
1445- ctx := dummyContext(c)
1446+ ctx := testing.Context(c)
1447 code := cmd.Main(com, ctx, []string{"--help"})
1448 c.Assert(code, Equals, 0)
1449 c.Assert(bufferString(ctx.Stdout), Equals, "")
1450@@ -247,7 +248,7 @@
1451 hctx := s.GetHookContext(c, 1, "m/0")
1452 com, err := jujuc.NewCommand(hctx, "relation-get")
1453 c.Assert(err, IsNil)
1454- ctx := dummyContext(c)
1455+ ctx := testing.Context(c)
1456 code := cmd.Main(com, ctx, []string{"--output", "some-file", "pew"})
1457 c.Assert(code, Equals, 0)
1458 c.Assert(bufferString(ctx.Stderr), Equals, "")
1459
1460=== modified file 'worker/uniter/jujuc/relation-ids_test.go'
1461--- worker/uniter/jujuc/relation-ids_test.go 2013-02-28 08:11:29 +0000
1462+++ worker/uniter/jujuc/relation-ids_test.go 2013-02-28 12:30:29 +0000
1463@@ -4,6 +4,7 @@
1464 "fmt"
1465 . "launchpad.net/gocheck"
1466 "launchpad.net/juju-core/cmd"
1467+ "launchpad.net/juju-core/testing"
1468 "launchpad.net/juju-core/worker/uniter/jujuc"
1469 )
1470
1471@@ -101,7 +102,7 @@
1472 hctx := s.GetHookContext(c, t.relid, "")
1473 com, err := jujuc.NewCommand(hctx, "relation-ids")
1474 c.Assert(err, IsNil)
1475- ctx := dummyContext(c)
1476+ ctx := testing.Context(c)
1477 code := cmd.Main(com, ctx, t.args)
1478 c.Assert(code, Equals, t.code)
1479 if code == 0 {
1480@@ -141,7 +142,7 @@
1481 hctx := s.GetHookContext(c, relid, "")
1482 com, err := jujuc.NewCommand(hctx, "relation-ids")
1483 c.Assert(err, IsNil)
1484- ctx := dummyContext(c)
1485+ ctx := testing.Context(c)
1486 code := cmd.Main(com, ctx, []string{"--help"})
1487 c.Assert(code, Equals, 0)
1488 c.Assert(bufferString(ctx.Stdout), Equals, "")
1489
1490=== modified file 'worker/uniter/jujuc/relation-list_test.go'
1491--- worker/uniter/jujuc/relation-list_test.go 2013-02-28 08:11:29 +0000
1492+++ worker/uniter/jujuc/relation-list_test.go 2013-02-28 12:30:29 +0000
1493@@ -4,6 +4,7 @@
1494 "fmt"
1495 . "launchpad.net/gocheck"
1496 "launchpad.net/juju-core/cmd"
1497+ "launchpad.net/juju-core/testing"
1498 "launchpad.net/juju-core/worker/uniter/jujuc"
1499 )
1500
1501@@ -107,7 +108,7 @@
1502 setMembers(hctx.rels[1], t.members1)
1503 com, err := jujuc.NewCommand(hctx, "relation-list")
1504 c.Assert(err, IsNil)
1505- ctx := dummyContext(c)
1506+ ctx := testing.Context(c)
1507 code := cmd.Main(com, ctx, t.args)
1508 c.Logf(bufferString(ctx.Stderr))
1509 c.Assert(code, Equals, t.code)
1510@@ -148,7 +149,7 @@
1511 hctx := s.GetHookContext(c, relid, "")
1512 com, err := jujuc.NewCommand(hctx, "relation-list")
1513 c.Assert(err, IsNil)
1514- ctx := dummyContext(c)
1515+ ctx := testing.Context(c)
1516 code := cmd.Main(com, ctx, []string{"--help"})
1517 c.Assert(code, Equals, 0)
1518 c.Assert(bufferString(ctx.Stdout), Equals, "")
1519
1520=== modified file 'worker/uniter/jujuc/relation-set_test.go'
1521--- worker/uniter/jujuc/relation-set_test.go 2013-02-28 08:11:29 +0000
1522+++ worker/uniter/jujuc/relation-set_test.go 2013-02-28 12:30:29 +0000
1523@@ -25,7 +25,7 @@
1524 hctx := s.GetHookContext(c, t.relid, "")
1525 com, err := jujuc.NewCommand(hctx, "relation-set")
1526 c.Assert(err, IsNil)
1527- ctx := dummyContext(c)
1528+ ctx := testing.Context(c)
1529 code := cmd.Main(com, ctx, []string{"--help"})
1530 c.Assert(code, Equals, 0)
1531 c.Assert(bufferString(ctx.Stdout), Equals, "")
1532@@ -198,7 +198,7 @@
1533 rset := com.(*jujuc.RelationSetCommand)
1534 rset.RelationId = 1
1535 rset.Settings = t.change
1536- ctx := dummyContext(c)
1537+ ctx := testing.Context(c)
1538 err = com.Run(ctx)
1539 c.Assert(err, IsNil)
1540
1541
1542=== modified file 'worker/uniter/jujuc/server.go'
1543--- worker/uniter/jujuc/server.go 2013-02-28 08:11:29 +0000
1544+++ worker/uniter/jujuc/server.go 2013-02-28 12:30:29 +0000
1545@@ -91,7 +91,12 @@
1546 return badReqErrorf("%s", err)
1547 }
1548 var stdin, stdout, stderr bytes.Buffer
1549- ctx := &cmd.Context{req.Dir, &stdin, &stdout, &stderr}
1550+ ctx := &cmd.Context{
1551+ Dir: req.Dir,
1552+ Stdin: &stdin,
1553+ Stdout: &stdout,
1554+ Stderr: &stderr,
1555+ }
1556 j.mu.Lock()
1557 defer j.mu.Unlock()
1558 log.Printf("worker/uniter/jujuc: running hook %q %q", req.CommandName, req.Args)
1559
1560=== modified file 'worker/uniter/jujuc/unit-get_test.go'
1561--- worker/uniter/jujuc/unit-get_test.go 2013-02-28 08:11:29 +0000
1562+++ worker/uniter/jujuc/unit-get_test.go 2013-02-28 12:30:29 +0000
1563@@ -37,7 +37,7 @@
1564 func (s *UnitGetSuite) TestOutputFormat(c *C) {
1565 for _, t := range unitGetTests {
1566 com := s.createCommand(c)
1567- ctx := dummyContext(c)
1568+ ctx := testing.Context(c)
1569 code := cmd.Main(com, ctx, t.args)
1570 c.Assert(code, Equals, 0)
1571 c.Assert(bufferString(ctx.Stderr), Equals, "")
1572@@ -47,7 +47,7 @@
1573
1574 func (s *UnitGetSuite) TestHelp(c *C) {
1575 com := s.createCommand(c)
1576- ctx := dummyContext(c)
1577+ ctx := testing.Context(c)
1578 code := cmd.Main(com, ctx, []string{"--help"})
1579 c.Assert(code, Equals, 0)
1580 c.Assert(bufferString(ctx.Stdout), Equals, "")
1581@@ -64,7 +64,7 @@
1582
1583 func (s *UnitGetSuite) TestOutputPath(c *C) {
1584 com := s.createCommand(c)
1585- ctx := dummyContext(c)
1586+ ctx := testing.Context(c)
1587 code := cmd.Main(com, ctx, []string{"--output", "some-file", "private-address"})
1588 c.Assert(code, Equals, 0)
1589 c.Assert(bufferString(ctx.Stderr), Equals, "")
1590
1591=== modified file 'worker/uniter/jujuc/util_test.go'
1592--- worker/uniter/jujuc/util_test.go 2013-02-28 08:11:29 +0000
1593+++ worker/uniter/jujuc/util_test.go 2013-02-28 12:30:29 +0000
1594@@ -5,7 +5,6 @@
1595 "fmt"
1596 "io"
1597 . "launchpad.net/gocheck"
1598- "launchpad.net/juju-core/cmd"
1599 "launchpad.net/juju-core/state"
1600 "launchpad.net/juju-core/worker/uniter/jujuc"
1601 "sort"
1602@@ -14,10 +13,6 @@
1603
1604 func TestPackage(t *testing.T) { TestingT(t) }
1605
1606-func dummyContext(c *C) *cmd.Context {
1607- return &cmd.Context{c.MkDir(), nil, &bytes.Buffer{}, &bytes.Buffer{}}
1608-}
1609-
1610 func bufferString(w io.Writer) string {
1611 return w.(*bytes.Buffer).String()
1612 }

Subscribers

People subscribed via source and target branches