func (s *statusSuite) TestCheck(c *gc.C) { results := debugstatus.Check( makeCheckerFunc("check1", "check1 name", "value1", true), makeCheckerFunc("check2", "check2 name", "value2", false), makeCheckerFunc("check3", "check3 name", "value3", true), ) for key, r := range results { if r.Duration < time.Microsecond { c.Errorf("got %v want >1µs", r.Duration) } r.Duration = 0 results[key] = r } c.Assert(results, jc.DeepEquals, map[string]debugstatus.CheckResult{ "check1": { Name: "check1 name", Value: "value1", Passed: true, }, "check2": { Name: "check2 name", Value: "value2", Passed: false, }, "check3": { Name: "check3 name", Value: "value3", Passed: true, }, }) }
func (s *workerSuite) TestWorkerPublishesInstanceIds(c *gc.C) { DoTestForIPv4AndIPv6(func(ipVersion TestIPVersion) { s.PatchValue(&pollInterval, coretesting.LongWait+time.Second) s.PatchValue(&initialRetryInterval, 5*time.Millisecond) s.PatchValue(&maxRetryInterval, initialRetryInterval) publishCh := make(chan []instance.Id, 100) publish := func(apiServers [][]network.HostPort, instanceIds []instance.Id) error { publishCh <- instanceIds return nil } st := NewFakeState() InitState(c, st, 3, ipVersion) w := newWorker(st, PublisherFunc(publish)) defer func() { c.Check(worker.Stop(w), gc.IsNil) }() select { case instanceIds := <-publishCh: c.Assert(instanceIds, jc.SameContents, []instance.Id{"id-10", "id-11", "id-12"}) case <-time.After(coretesting.LongWait): c.Errorf("timed out waiting for publish") } }) }
func (s *LogReaderSuite) TestNextError(c *gc.C) { cUUID := "feebdaed-2f18-4fd2-967d-db9663db7bea" stub := &testing.Stub{} conn := &mockConnector{stub: stub} jsonReader := mockStream{stub: stub} conn.ReturnConnectStream = jsonReader failure := errors.New("an error") stub.SetErrors(nil, failure) var cfg params.LogStreamConfig stream, err := logstream.Open(conn, cfg, cUUID) c.Assert(err, gc.IsNil) var nextErr error done := make(chan struct{}) go func() { _, nextErr = stream.Next() c.Check(errors.Cause(nextErr), gc.Equals, failure) close(done) }() select { case <-done: case <-time.After(coretesting.LongWait): c.Errorf("timed out waiting for record") } stub.CheckCallNames(c, "ConnectStream", "ReadJSON") }
func (s *RunHookSuite) TestRunHook(c *gc.C) { for i, t := range runHookTests { c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm) ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) c.Assert(err, jc.ErrorIsNil) paths := runnertesting.NewRealPaths(c) rnr := runner.NewRunner(ctx, paths) var hookExists bool if t.spec.perm != 0 { spec := t.spec spec.dir = "hooks" spec.name = hookName c.Logf("makeCharm %#v", spec) makeCharm(c, spec, paths.GetCharmDir()) hookExists = true } t0 := time.Now() err = rnr.RunHook("something-happened") if t.err == "" && hookExists { c.Assert(err, jc.ErrorIsNil) } else if !hookExists { c.Assert(context.IsMissingHookError(err), jc.IsTrue) } else { c.Assert(err, gc.ErrorMatches, t.err) } if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second { c.Errorf("background process holding up hook execution") } } }
func (t *LiveTests) TestStopInstances(c *gc.C) { t.PrepareOnce(c) // It would be nice if this test was in jujutest, but // there's no way for jujutest to fabricate a valid-looking // instance id. inst0, _ := testing.AssertStartInstance(c, t.Env, "40") inst1 := ec2.FabricateInstance(inst0, "i-aaaaaaaa") inst2, _ := testing.AssertStartInstance(c, t.Env, "41") err := t.Env.StopInstances(inst0.Id(), inst1.Id(), inst2.Id()) c.Check(err, jc.ErrorIsNil) var insts []instance.Instance // We need the retry logic here because we are waiting // for Instances to return an error, and it will not retry // if it succeeds. gone := false for a := ec2.ShortAttempt.Start(); a.Next(); { insts, err = t.Env.Instances([]instance.Id{inst0.Id(), inst2.Id()}) if err == environs.ErrPartialInstances { // instances not gone yet. continue } if err == environs.ErrNoInstances { gone = true break } c.Fatalf("error getting instances: %v", err) } if !gone { c.Errorf("after termination, instances remaining: %v", insts) } }
func (s *RethinkSuite) TestGeospatialDecodeGeometryPseudoType(c *test.C) { var response types.Geometry // setup coordinates coords := [][][]float64{ { {-122.423246, 37.779388}, {-122.423246, 37.329898}, {-121.88642, 37.329898}, {-121.88642, 37.329898}, {-122.423246, 37.779388}, }, } gt := "Polygon" res, err := Expr(map[string]interface{}{ "$reql_type$": "GEOMETRY", "type": "Polygon", "coordinates": coords, }).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) // test shape if response.Type != gt { c.Errorf("expected [%v], instead [%v]", gt, response.Type) } // assert points are within threshold c.Assert(response, geometryEquals, "Polygon", coords) }
func (tnw *testNotifyWatcher) TriggerChange(c *gc.C) { select { case tnw.changes <- struct{}{}: case <-time.After(coretesting.LongWait): c.Errorf("timed out trying to trigger a change") } }
func (s *leadershipSuite) TestClaimLeadership(c *gc.C) { client := leadership.NewClient(s.apiState) err := client.ClaimLeadership(s.serviceId, s.unitId, 10*time.Second) c.Assert(err, jc.ErrorIsNil) tokens, err := s.State.LeasePersistor.PersistedTokens() c.Assert(err, jc.ErrorIsNil) c.Assert(tokens, gc.HasLen, 1) c.Assert(tokens[0].Namespace, gc.Equals, "mysql-leadership") c.Assert(tokens[0].Id, gc.Equals, "mysql/0") unblocked := make(chan struct{}) go func() { err := client.BlockUntilLeadershipReleased(s.serviceId) c.Check(err, jc.ErrorIsNil) unblocked <- struct{}{} }() time.Sleep(coretesting.ShortWait) select { case <-time.After(15 * time.Second): c.Errorf("Timed out waiting for leadership to release.") case <-unblocked: } }
func (s *leadershipSuite) TestUnblock(c *gc.C) { client := leadership.NewClient(s.apiState) err := client.ClaimLeadership(s.serviceId, s.unitId, 10*time.Second) c.Assert(err, jc.ErrorIsNil) unblocked := make(chan struct{}) go func() { err := client.BlockUntilLeadershipReleased(s.serviceId) c.Check(err, jc.ErrorIsNil) unblocked <- struct{}{} }() time.Sleep(coretesting.ShortWait) err = client.ReleaseLeadership(s.serviceId, s.unitId) c.Assert(err, jc.ErrorIsNil) select { case <-time.After(coretesting.LongWait): c.Errorf("Timed out waiting for leadership to release.") case <-unblocked: } }
func (s *migrateSuite) TestNoNameClashes(c *gc.C) { nameCounts := make(map[string]int) doAllBundles(c, func(c *gc.C, id string, data []byte) { nameCounts[id]++ }) // There are actually two name clashes in the real // in-the-wild bundles: // cs:~charmers/bundle/mediawiki-scalable // cs:~charmers/bundle/mongodb-cluster // Both of these actually fit with our proposed scheme, // because they're (almost) identical with the bundles // within mediawiki and mongodb respectively. // // So we discount them from our example bundles. delete(nameCounts, "cs:~charmers/bundle/mongodb-cluster") delete(nameCounts, "cs:~charmers/bundle/mediawiki-scalable") doAllBundles(c, func(c *gc.C, id string, data []byte) { var bundles map[string]*legacyBundle err := yaml.Unmarshal(data, &bundles) c.Assert(err, gc.IsNil) if len(bundles) == 1 { return } for name := range bundles { subId := id + "-" + name nameCounts[subId]++ } }) for name, count := range nameCounts { if count != 1 { c.Errorf("%d clashes at %s", count-1, name) } } }
func (s *environSuite) TestStop(c *gc.C) { s.st.SetErrors( nil, // WatchForEnvironConfigChanges errors.New("err1"), // Changes (closing the channel) ) s.st.SetConfig(c, coretesting.Attrs{ "type": "invalid", }) w, err := s.st.WatchForEnvironConfigChanges() c.Assert(err, jc.ErrorIsNil) defer stopWatcher(c, w) stop := make(chan struct{}) close(stop) // close immediately so the loop exits. done := make(chan error) go func() { env, err := worker.WaitForEnviron(w, s.st, stop) c.Check(env, gc.IsNil) done <- err }() select { case <-worker.LoadedInvalid: c.Errorf("expected changes watcher to be closed") case err := <-done: c.Assert(err, gc.Equals, tomb.ErrDying) case <-time.After(coretesting.LongWait): c.Fatalf("timeout waiting for the WaitForEnviron to stop") } s.st.CheckCallNames(c, "WatchForEnvironConfigChanges", "Changes") }
func (s *EngineSuite) TestStartAbortOnEngineKill(c *gc.C) { s.fix.run(c, func(engine *dependency.Engine) { starts := make(chan struct{}, 1000) manifold := dependency.Manifold{ Start: func(context dependency.Context) (worker.Worker, error) { starts <- struct{}{} select { case <-context.Abort(): case <-time.After(coretesting.LongWait): c.Errorf("timed out") } return nil, errors.New("whatever") }, } err := engine.Install("task", manifold) c.Assert(err, jc.ErrorIsNil) select { case <-starts: case <-time.After(coretesting.LongWait): c.Fatalf("timed out") } workertest.CleanKill(c, engine) select { case <-starts: c.Fatalf("unexpected start") default: } }) }
// NewSeqPrompter returns a prompter that can be used to check a sequence of // IO interactions. Expected input from the user is marked with the // given user input marker (for example a distinctive unicode character // that will not occur in the rest of the text) and runs to the end of a // line. // // All output text in between user input is treated as regular expressions. // // As a special case, if an input marker is followed only by a single input // marker on that line, the checker will cause io.EOF to be returned for // that prompt. // // The returned SeqPrompter wraps a Prompter and checks that each // read and write corresponds to the expected action in the sequence. // // After all interaction is done, CheckDone or AssertDone should be called to // check that no more interactions are expected. // // Any failures will result in the test failing. // // For example given the prompter created with: // // checker := NewSeqPrompter(c, "»", `What is your name: »Bob // And your age: »148 // You're .* old, Bob! // `) // // The following code will pass the checker: // // fmt.Fprintf(checker, "What is your name: ") // buf := make([]byte, 100) // n, _ := checker.Read(buf) // name := strings.TrimSpace(string(buf[0:n])) // fmt.Fprintf(checker, "And your age: ") // n, _ = checker.Read(buf) // age, err := strconv.Atoi(strings.TrimSpace(string(buf[0:n]))) // c.Assert(err, gc.IsNil) // if age > 90 { // fmt.Fprintf(checker, "You're very old, %s!\n", name) // } // checker.CheckDone() func NewSeqPrompter(c *gc.C, userInputMarker, text string) *SeqPrompter { p := &SeqPrompter{ c: c, } for { i := strings.Index(text, userInputMarker) if i == -1 { p.finalText = text break } prompt := text[0:i] text = text[i+len(userInputMarker):] endLine := strings.Index(text, "\n") if endLine == -1 { c.Errorf("no newline found after expected input %q", text) } reply := text[0 : endLine+1] if reply[0:len(reply)-1] == userInputMarker { // EOF line. reply = "" } text = text[endLine+1:] if prompt == "" && len(p.ios) > 0 { // Combine multiple contiguous inputs together. p.ios[len(p.ios)-1].reply += reply } else { p.ios = append(p.ios, ioInteraction{ prompt: prompt, reply: reply, }) } } p.Prompter = NewPrompter(p.prompt) return p }
func (s *HeaderSuite) TestHeaderResponseProtectedKey(c *chk.C) { res, err := headerClient.ResponseProtectedKey() c.Assert(err, chk.IsNil) if !(strings.Contains(res.Response.Header["Content-Type"][0], "text/html")) { c.Errorf("Expected to contain '%v', got %v\n", "text/html", res.Response.Header["Content-Type"][0]) } }
func (s *watcherSuite) TestWatchInitialEventConsumed(c *gc.C) { // Machiner.Watch should send the initial event as part of the Watch // call (for NotifyWatchers there is no state to be transmitted). So a // call to Next() should not have anything to return. var results params.NotifyWatchResults args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results) c.Assert(err, jc.ErrorIsNil) c.Assert(results.Results, gc.HasLen, 1) result := results.Results[0] c.Assert(result.Error, gc.IsNil) // We expect the Call() to "Next" to block, so run it in a goroutine. done := make(chan error) go func() { ignored := struct{}{} done <- s.stateAPI.APICall("NotifyWatcher", s.stateAPI.BestFacadeVersion("NotifyWatcher"), result.NotifyWatcherId, "Next", nil, &ignored) }() select { case err := <-done: c.Errorf("Call(Next) did not block immediately after Watch(): err %v", err) case <-time.After(coretesting.ShortWait): } }
func (*HousingSuite) TestOccupyLocked(c *gc.C) { manifold := util.Housing{ Occupy: "fortress", }.Decorate(dependency.Manifold{}) abort := make(chan struct{}) context := dt.StubContext(abort, map[string]interface{}{ "fortress": newGuest(false), }) // start the start func started := make(chan struct{}) go func() { defer close(started) worker, err := manifold.Start(context) c.Check(worker, gc.IsNil) c.Check(errors.Cause(err), gc.Equals, fortress.ErrAborted) }() // check it's blocked... select { case <-time.After(coretesting.ShortWait): case <-started: c.Errorf("Start finished early") } // ...until the context is aborted. close(abort) select { case <-started: case <-time.After(coretesting.LongWait): c.Fatalf("timed out") } }
// installFakeSSH creates a fake "ssh" command in a new $PATH, // updates $PATH, and returns a function to reset $PATH to its // original value when called. // // input may be: // - nil (ignore input) // - a string (match input exactly) // output may be: // - nil (no output) // - a string (stdout) // - a slice of strings, of length two (stdout, stderr) func installFakeSSH(c *gc.C, input, output interface{}, rc int) testing.Restorer { fakebin := c.MkDir() ssh := filepath.Join(fakebin, "ssh") switch input := input.(type) { case nil: case string: sshexpectedinput := ssh + ".expected-input" err := ioutil.WriteFile(sshexpectedinput, []byte(input), 0644) c.Assert(err, jc.ErrorIsNil) default: c.Errorf("input has invalid type: %T", input) } var stdout, stderr string switch output := output.(type) { case nil: case string: stdout = fmt.Sprintf("cat<<EOF\n%s\nEOF", output) case []string: c.Assert(output, gc.HasLen, 2) stdout = fmt.Sprintf("cat<<EOF\n%s\nEOF", output[0]) stderr = fmt.Sprintf("cat>&2<<EOF\n%s\nEOF", output[1]) } script := fmt.Sprintf(sshscript, stdout, stderr, rc) err := ioutil.WriteFile(ssh, []byte(script), 0777) c.Assert(err, jc.ErrorIsNil) return testing.PatchEnvPathPrepend(fakebin) }
func (s *ByteGroupSuite) TestGetNonASCII(c *chk.C) { res, err := byteClient.GetNonASCII() c.Assert(err, chk.IsNil) if !bytes.Equal(*res.Value, []byte{255, 254, 253, 252, 251, 250, 249, 248, 247, 246}) { c.Errorf("%v\n", *res.Value) } }
func (s *AssignSuite) TestAssignMachinePrincipalsChange(c *gc.C) { machine, err := s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, jc.ErrorIsNil) unit, err := s.wordpress.AddUnit() c.Assert(err, jc.ErrorIsNil) err = unit.AssignToMachine(machine) c.Assert(err, jc.ErrorIsNil) unit, err = s.wordpress.AddUnit() c.Assert(err, jc.ErrorIsNil) err = unit.AssignToMachine(machine) c.Assert(err, jc.ErrorIsNil) subUnit := s.addSubordinate(c, unit) checkPrincipals := func() []string { docID := state.DocID(s.State, machine.Id()) doc := make(map[string][]string) s.machines.FindId(docID).One(&doc) principals, ok := doc["principals"] if !ok { c.Errorf(`machine document does not have a "principals" field`) } return principals } c.Assert(checkPrincipals(), gc.DeepEquals, []string{"wordpress/0", "wordpress/1"}) err = subUnit.EnsureDead() c.Assert(err, jc.ErrorIsNil) err = subUnit.Remove() c.Assert(err, jc.ErrorIsNil) err = unit.EnsureDead() c.Assert(err, jc.ErrorIsNil) err = unit.Remove() c.Assert(err, jc.ErrorIsNil) c.Assert(checkPrincipals(), gc.DeepEquals, []string{"wordpress/0"}) }
func (s *ByteGroupSuite) TestGetEmptyByte(c *chk.C) { res, err := byteClient.GetEmpty() c.Assert(err, chk.IsNil) if !bytes.Equal(*res.Value, nil) { c.Errorf("%v\n", *res.Value) } }
func waitForHandledNotify(c *gc.C, handled chan struct{}) { select { case <-handled: case <-time.After(coretesting.LongWait): c.Errorf("handled failed to signal after %s", coretesting.LongWait) } }
func (s *RestoreInfoSuite) TestGetSetterRace(c *gc.C) { trigger := make(chan struct{}) test := func() { select { case <-trigger: setter, err := s.State.RestoreInfoSetter() if c.Check(err, jc.ErrorIsNil) { checkStatus(c, setter, state.UnknownRestoreStatus) } case <-time.After(coretesting.LongWait): c.Errorf("test invoked but not triggered") } } const count = 100 wg := sync.WaitGroup{} wg.Add(count) for i := 0; i < count; i++ { go func() { defer wg.Done() test() }() } close(trigger) wg.Wait() }
func checkSecurityGroupAllowed(c *gc.C, perms []amzec2.IPPerm, g amzec2.SecurityGroup) { protos := map[string]struct { fromPort int toPort int }{ "tcp": {0, 65535}, "udp": {0, 65535}, "icmp": {-1, -1}, } for _, perm := range perms { if len(perm.SourceGroups) > 0 { c.Check(perm.SourceGroups, gc.HasLen, 1) c.Check(perm.SourceGroups[0].Id, gc.Equals, g.Id) ports, ok := protos[perm.Protocol] if !ok { c.Errorf("unexpected protocol in security group: %q", perm.Protocol) continue } delete(protos, perm.Protocol) c.Check(perm.FromPort, gc.Equals, ports.fromPort) c.Check(perm.ToPort, gc.Equals, ports.toPort) } } if len(protos) > 0 { c.Errorf("%d security group permission not found for %#v in %#v", len(protos), g, perms) } }
func (s *RunHookSuite) TestRunHook(c *gc.C) { uuid, err := utils.NewUUID() c.Assert(err, jc.ErrorIsNil) for i, t := range runHookTests { c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm) ctx := s.getHookContext(c, uuid.String(), t.relid, t.remote, noProxies) paths := NewRealPaths(c) rnr := runner.NewRunner(ctx, paths) var hookExists bool if t.spec.perm != 0 { spec := t.spec spec.dir = "hooks" spec.name = hookName c.Logf("makeCharm %#v", spec) makeCharm(c, spec, paths.charm) hookExists = true } t0 := time.Now() err := rnr.RunHook("something-happened") if t.err == "" && hookExists { c.Assert(err, jc.ErrorIsNil) } else if !hookExists { c.Assert(runner.IsMissingHookError(err), jc.IsTrue) } else { c.Assert(err, gc.ErrorMatches, t.err) } if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second { c.Errorf("background process holding up hook execution") } } }
func (tsw *testStringsWatcher) TriggerChange(c *gc.C, changes []string) { select { case tsw.changes <- changes: case <-time.After(coretesting.LongWait): c.Errorf("timed out trying to trigger a change") } }
func (*suite) TestRendezvousWaitBeforeDone(c *gc.C) { m := meeting.New() id, err := m.NewRendezvous([]byte("first data")) c.Assert(err, gc.IsNil) c.Assert(id, gc.Not(gc.Equals), "") waitDone := make(chan struct{}) go func() { data0, data1, err := m.Wait(id) c.Check(err, gc.IsNil) c.Check(string(data0), gc.Equals, "first data") c.Check(string(data1), gc.Equals, "second data") close(waitDone) }() time.Sleep(10 * time.Millisecond) err = m.Done(id, []byte("second data")) c.Assert(err, gc.IsNil) select { case <-waitDone: case <-time.After(2 * time.Second): c.Errorf("timed out waiting for rendezvous") } // Check that item has now been deleted. data0, data1, err := m.Wait(id) c.Assert(data0, gc.IsNil) c.Assert(data1, gc.IsNil) c.Assert(err, gc.ErrorMatches, `rendezvous ".*" not found`) }
func (s *WaitSuite) TestConfigError(c *gc.C) { fix := &fixture{ observerErrs: []error{ errors.New("biff zonk"), }, } fix.Run(c, func(context *runContext) { abort := make(chan struct{}) defer close(abort) done := make(chan struct{}) go func() { defer close(done) env, err := environ.WaitForEnviron(context.watcher, context, nil, abort) c.Check(env, gc.IsNil) c.Check(err, gc.ErrorMatches, "cannot read environ config: biff zonk") }() context.SendNotify() select { case <-done: case <-time.After(coretesting.LongWait): c.Errorf("timed out waiting for failure") } workertest.CheckAlive(c, context.watcher) }) }
func (dt discoveryTest) checkService(c *gc.C, svc service.Service, err error, name string, conf common.Conf) { if dt.expected == "" { c.Check(err, jc.Satisfies, errors.IsNotFound) return } // Check the success case. if !c.Check(err, jc.ErrorIsNil) { return } switch dt.expected { case service.InitSystemUpstart: c.Check(svc, gc.FitsTypeOf, &upstart.Service{}) case service.InitSystemSystemd: c.Check(svc, gc.FitsTypeOf, &systemd.Service{}) case service.InitSystemWindows: c.Check(svc, gc.FitsTypeOf, &windows.Service{}) default: c.Errorf("unknown expected init system %q", dt.expected) return } if svc == nil { return } c.Check(svc.Name(), gc.Equals, name) c.Check(svc.Conf(), jc.DeepEquals, conf) }
func (s *ExecHelperSuite) TestExecHelperError(c *gc.C) { argChan := make(chan []string, 1) cfg := testing.PatchExecConfig{ Stdout: "Hellooooo stdout!", Stderr: "Hellooooo stderr!", ExitCode: 55, Args: argChan, } f := s.GetExecCommand(cfg) stderr := &bytes.Buffer{} stdout := &bytes.Buffer{} cmd := f("echo", "hello world!") cmd.Stderr = stderr cmd.Stdout = stdout err := cmd.Run() c.Assert(err, gc.NotNil) _, ok := err.(*exec.ExitError) if !ok { c.Errorf("Expected *exec.ExitError, but got %T", err) } else { c.Check(err.Error(), gc.Equals, "exit status 55") } c.Check(stderr.String(), gc.Equals, cfg.Stderr+"\n") c.Check(stdout.String(), gc.Equals, cfg.Stdout+"\n") select { case args := <-argChan: c.Assert(args, gc.DeepEquals, []string{"echo", "hello world!"}) default: c.Fatalf("No arguments passed to output channel") } }
func (s *storageSuite) TestWriteFailure(c *gc.C) { // Invocations: // 1: first "install" // 2: touch, Put // 3: second "install" // 4: touch var invocations int badSshCommand := func(host string, command ...string) *ssh.Cmd { invocations++ switch invocations { case 1, 3: return s.sshCommand(c, host, "head -n 1 > /dev/null") case 2: // Note: must close stdin before responding the first time, or // the second command will race with closing stdin, and may // flush first. return s.sshCommand(c, host, "head -n 1 > /dev/null; exec 0<&-; echo JUJU-RC: 0; echo blah blah; echo more") case 4: return s.sshCommand(c, host, `head -n 1 > /dev/null; echo "Hey it's JUJU-RC: , but not at the beginning of the line"; echo more`) default: c.Errorf("unexpected invocation: #%d, %s", invocations, command) return nil } } s.PatchValue(&sshCommand, badSshCommand) stor, err := newSSHStorage("example.com", c.MkDir(), c.MkDir()) c.Assert(err, jc.ErrorIsNil) defer stor.Close() err = stor.Put("whatever", bytes.NewBuffer(nil), 0) c.Assert(err, gc.ErrorMatches, `failed to write input: write \|1: broken pipe \(output: "blah blah\\nmore"\)`) _, err = newSSHStorage("example.com", c.MkDir(), c.MkDir()) c.Assert(err, gc.ErrorMatches, `failed to locate "JUJU-RC: " \(output: "Hey it's JUJU-RC: , but not at the beginning of the line\\nmore"\)`) }