示例#1
0
func commonTestOrderedCalls(t *testing.T) (reporter *ErrorReporter, ctrl *gomock.Controller, subjectOne, subjectTwo *Subject) {
	reporter, ctrl = createFixtures(t)

	subjectOne = new(Subject)
	subjectTwo = new(Subject)

	gomock.InOrder(
		ctrl.RecordCall(subjectOne, "FooMethod", "1").AnyTimes(),
		ctrl.RecordCall(subjectTwo, "FooMethod", "2"),
		ctrl.RecordCall(subjectTwo, "BarMethod", "3"),
	)

	return
}
示例#2
0
func TestSocketListenConfig(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mckLogger := NewMockLogger(mockCtrl)
	mckLogger.EXPECT().ShouldLog(gomock.Any()).Return(true).AnyTimes()
	mckLogger.EXPECT().Log(gomock.Any(), gomock.Any(),
		gomock.Any(), gomock.Any()).AnyTimes()
	mckStat := NewMockStatistician(mockCtrl)
	mckListenerConfig := NewMockListenerConfig(mockCtrl)

	app := NewApplication()
	app.hostname = "example.org"
	app.SetLogger(mckLogger)
	app.SetMetrics(mckStat)

	sh := NewSocketHandler()
	sh.setApp(app)

	// Should forward Listen errors.
	listenErr := errors.New("splines not reticulated")
	mckListenerConfig.EXPECT().Listen().Return(nil, listenErr)
	if err := sh.listenWithConfig(mckListenerConfig); err != listenErr {
		t.Errorf("Wrong error: got %#v; want %#v", err, listenErr)
	}

	// Should use the wss:// scheme if UseTLS returns true.
	ml := newMockListener(netAddr{"test", "[::1]:8080"})
	gomock.InOrder(
		mckListenerConfig.EXPECT().Listen().Return(ml, nil),
		mckListenerConfig.EXPECT().UseTLS().Return(true),
		mckListenerConfig.EXPECT().GetMaxConns().Return(1),
	)
	if err := sh.listenWithConfig(mckListenerConfig); err != nil {
		t.Errorf("Error setting listener: %s", err)
	}
	if maxConns := sh.MaxConns(); maxConns != 1 {
		t.Errorf("Mismatched maximum connection count: got %d; want 1",
			maxConns)
	}
	expectedURL := "wss://example.org:8080"
	if url := sh.URL(); url != expectedURL {
		t.Errorf("Mismatched handler URL: got %q; want %q",
			url, expectedURL)
	}
}
示例#3
0
func TestCallAfterLoopPanic(t *testing.T) {
	_, ctrl := createFixtures(t)

	subject := new(Subject)

	firstCall := ctrl.RecordCall(subject, "Foo", "1")
	secondCall := ctrl.RecordCall(subject, "Foo", "2")
	thirdCall := ctrl.RecordCall(subject, "Foo", "3")

	gomock.InOrder(firstCall, secondCall, thirdCall)

	defer func() {
		err := recover()
		if err == nil {
			t.Error("Call.After creation of dependency loop did not panic.")
		}
	}()

	// This should panic due to dependency loop.
	firstCall.After(thirdCall)
}
示例#4
0
func DecoderSpec(c gs.Context) {
	t := new(ts.SimpleT)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	pConfig := pipeline.NewPipelineConfig(nil)

	c.Specify("A SandboxDecoder", func() {

		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)

		c.Specify("that uses lpeg and inject_message", func() {
			dRunner.EXPECT().Name().Return("serialize")
			conf.ScriptFilename = "../lua/testsupport/decoder.lua"
			err := decoder.Init(conf)
			c.Assume(err, gs.IsNil)

			c.Specify("decodes simple messages", func() {
				data := "1376389920 debug id=2321 url=example.com item=1"
				decoder.SetDecoderRunner(dRunner)
				pack.Message.SetPayload(data)
				_, err = decoder.Decode(pack)
				c.Assume(err, gs.IsNil)

				c.Expect(pack.Message.GetTimestamp(),
					gs.Equals,
					int64(1376389920000000000))

				c.Expect(pack.Message.GetSeverity(), gs.Equals, int32(7))

				var ok bool
				var value interface{}
				value, ok = pack.Message.GetFieldValue("id")
				c.Expect(ok, gs.Equals, true)
				c.Expect(value, gs.Equals, "2321")

				value, ok = pack.Message.GetFieldValue("url")
				c.Expect(ok, gs.Equals, true)
				c.Expect(value, gs.Equals, "example.com")

				value, ok = pack.Message.GetFieldValue("item")
				c.Expect(ok, gs.Equals, true)
				c.Expect(value, gs.Equals, "1")
				decoder.Shutdown()
			})

			c.Specify("decodes an invalid messages", func() {
				data := "1376389920 bogus id=2321 url=example.com item=1"
				decoder.SetDecoderRunner(dRunner)
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(len(packs), gs.Equals, 0)
				c.Expect(err.Error(), gs.Equals, "Failed parsing: "+data)
				c.Expect(decoder.processMessageFailures, gs.Equals, int64(1))
				decoder.Shutdown()
			})

			c.Specify("Preserves data", func() {
				conf.ScriptFilename = "../lua/testsupport/serialize.lua"
				conf.PreserveData = true
				err := decoder.Init(conf)
				c.Assume(err, gs.IsNil)
				decoder.SetDecoderRunner(dRunner)
				decoder.Shutdown()
				_, err = os.Stat("sandbox_preservation/serialize.data")
				c.Expect(err, gs.IsNil)
				err = os.Remove("sandbox_preservation/serialize.data")
				c.Expect(err, gs.IsNil)
			})
		})

		c.Specify("that only uses write_message", func() {
			conf.ScriptFilename = "../lua/testsupport/write_message_decoder.lua"
			dRunner.EXPECT().Name().Return("write_message")
			err := decoder.Init(conf)
			decoder.SetDecoderRunner(dRunner)
			c.Assume(err, gs.IsNil)

			c.Specify("adds a string field to the message", func() {
				data := "string field scribble"
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(err, gs.IsNil)
				c.Expect(len(packs), gs.Equals, 1)
				c.Expect(packs[0], gs.Equals, pack)
				value, ok := pack.Message.GetFieldValue("scribble")
				c.Expect(ok, gs.IsTrue)
				c.Expect(value.(string), gs.Equals, "foo")
			})

			c.Specify("adds a numeric field to the message", func() {
				data := "num field scribble"
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(err, gs.IsNil)
				c.Expect(len(packs), gs.Equals, 1)
				c.Expect(packs[0], gs.Equals, pack)
				value, ok := pack.Message.GetFieldValue("scribble")
				c.Expect(ok, gs.IsTrue)
				c.Expect(value.(float64), gs.Equals, float64(1))
			})

			c.Specify("adds a boolean field to the message", func() {
				data := "bool field scribble"
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(err, gs.IsNil)
				c.Expect(len(packs), gs.Equals, 1)
				c.Expect(packs[0], gs.Equals, pack)
				value, ok := pack.Message.GetFieldValue("scribble")
				c.Expect(ok, gs.IsTrue)
				c.Expect(value.(bool), gs.Equals, true)
			})

			c.Specify("sets type and payload", func() {
				data := "set type and payload"
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(err, gs.IsNil)
				c.Expect(len(packs), gs.Equals, 1)
				c.Expect(packs[0], gs.Equals, pack)
				c.Expect(pack.Message.GetType(), gs.Equals, "my_type")
				c.Expect(pack.Message.GetPayload(), gs.Equals, "my_payload")
			})

			c.Specify("sets field value with representation", func() {
				data := "set field value with representation"
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(err, gs.IsNil)
				c.Expect(len(packs), gs.Equals, 1)
				c.Expect(packs[0], gs.Equals, pack)
				fields := pack.Message.FindAllFields("rep")
				c.Expect(len(fields), gs.Equals, 1)
				field := fields[0]
				values := field.GetValueString()
				c.Expect(len(values), gs.Equals, 1)
				c.Expect(values[0], gs.Equals, "foo")
				c.Expect(field.GetRepresentation(), gs.Equals, "representation")
			})

			c.Specify("sets multiple field string values", func() {
				data := "set multiple field string values"
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(err, gs.IsNil)
				c.Expect(len(packs), gs.Equals, 1)
				c.Expect(packs[0], gs.Equals, pack)
				fields := pack.Message.FindAllFields("multi")
				c.Expect(len(fields), gs.Equals, 2)
				values := fields[0].GetValueString()
				c.Expect(len(values), gs.Equals, 1)
				c.Expect(values[0], gs.Equals, "first")
				values = fields[1].GetValueString()
				c.Expect(len(values), gs.Equals, 1)
				c.Expect(values[0], gs.Equals, "second")
			})

			c.Specify("sets field string array value", func() {
				data := "set field string array value"
				pack.Message.SetPayload(data)
				packs, err := decoder.Decode(pack)
				c.Expect(err, gs.IsNil)
				c.Expect(len(packs), gs.Equals, 1)
				c.Expect(packs[0], gs.Equals, pack)
				fields := pack.Message.FindAllFields("array")
				c.Expect(len(fields), gs.Equals, 1)
				values := fields[0].GetValueString()
				c.Expect(len(values), gs.Equals, 2)
				c.Expect(values[0], gs.Equals, "first")
				c.Expect(values[1], gs.Equals, "second")
			})
		})
	})

	c.Specify("A Multipack SandboxDecoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/testsupport/multipack_decoder.lua"
		supply := make(chan *pipeline.PipelinePack, 3)
		pack := pipeline.NewPipelinePack(supply)
		pack.Message = getTestMessage()

		pack1 := pipeline.NewPipelinePack(supply)
		pack2 := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")

		c.Specify("decodes into multiple packs", func() {
			err := decoder.Init(conf)
			c.Assume(err, gs.IsNil)
			decoder.SetDecoderRunner(dRunner)
			gomock.InOrder(
				dRunner.EXPECT().NewPack().Return(pack1),
				dRunner.EXPECT().NewPack().Return(pack2),
			)
			packs, err := decoder.Decode(pack)
			c.Expect(len(packs), gs.Equals, 3)
			c.Expect(packs[0].Message.GetPayload(), gs.Equals, "message one")
			c.Expect(packs[1].Message.GetPayload(), gs.Equals, "message two")
			c.Expect(packs[2].Message.GetPayload(), gs.Equals, "message three")

			for i := 0; i < 1; i++ {
				c.Expect(packs[i].Message.GetType(), gs.Equals, "TEST")
				c.Expect(packs[i].Message.GetHostname(), gs.Equals, "my.host.name")
				c.Expect(packs[i].Message.GetLogger(), gs.Equals, "GoSpec")
				c.Expect(packs[i].Message.GetSeverity(), gs.Equals, int32(6))

			}
			decoder.Shutdown()
		})
	})

	c.Specify("Linux Cpu Stats decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/linux_loadavg.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decodes a message", func() {
			payload := "0.00 0.01 0.05 3/153 660\n"
			pack.Message.SetPayload(payload)
			f, err := message.NewField("FilePath", "/proc/loadavg", "")
			c.Assume(err, gs.IsNil)
			pack.Message.AddField(f)

			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)
			c.Expect(pack.Message.GetSeverity(), gs.Equals, int32(7))

			var ok bool
			var value interface{}
			value, ok = pack.Message.GetFieldValue("1MinAvg")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, 0.00)

			value, ok = pack.Message.GetFieldValue("5MinAvg")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, 0.01)

			value, ok = pack.Message.GetFieldValue("15MinAvg")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, 0.05)

			value, ok = pack.Message.GetFieldValue("NumProcesses")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(3))

			value, ok = pack.Message.GetFieldValue("FilePath")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, "/proc/loadavg")
		})

		c.Specify("decodes an invalid message", func() {
			data := "bogus message"
			pack.Message.SetPayload(data)
			packs, err := decoder.Decode(pack)
			c.Expect(len(packs), gs.Equals, 0)
			c.Expect(err.Error(), gs.Equals, "Failed parsing: "+data)
			c.Expect(decoder.processMessageFailures, gs.Equals, int64(1))
			decoder.Shutdown()
		})
	})

	c.Specify("Linux Mem Stats decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/linux_memstats.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decodes a message", func() {
			payload := `MemTotal:        4047616 kB
MemFree:         3135780 kB
HugePages_Free:        0
`
			pack.Message.SetPayload(payload)
			f, err := message.NewField("FilePath", "/proc/meminfo", "")
			c.Assume(err, gs.IsNil)
			pack.Message.AddField(f)

			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)
			c.Expect(pack.Message.GetSeverity(), gs.Equals, int32(7))

			var ok bool
			var value interface{}
			value, ok = pack.Message.GetFieldValue("MemTotal")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, 4.047616e+06)

			value, ok = pack.Message.GetFieldValue("MemFree")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, 3.13578e+06)

			value, ok = pack.Message.GetFieldValue("HugePages_Free")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(0))

			value, ok = pack.Message.GetFieldValue("FilePath")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, "/proc/meminfo")
		})

		c.Specify("decodes an invalid message", func() {
			data := "bogus message"
			pack.Message.SetPayload(data)
			packs, err := decoder.Decode(pack)
			c.Expect(len(packs), gs.Equals, 0)
			c.Expect(err.Error(), gs.Equals, "Failed parsing: "+data)
			c.Expect(decoder.processMessageFailures, gs.Equals, int64(1))
			decoder.Shutdown()
		})
	})

	c.Specify("Linux Disk Stats decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/linux_diskstats.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decodes a message", func() {
			payload := "   13903    11393   969224    49444    10780    10161  1511920     4104        0     5064    53468\n"
			pack.Message.SetPayload(payload)
			f, err := message.NewField("FilePath", "/sys/block/sda/stat", "")
			c.Assume(err, gs.IsNil)
			pack.Message.AddField(f)

			f, err = message.NewField("TickerInterval", int64(2), "")
			c.Assume(err, gs.IsNil)
			pack.Message.AddField(f)

			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)
			c.Expect(pack.Message.GetSeverity(), gs.Equals, int32(7))

			var ok bool
			var value interface{}
			// These are in the same order the payload should be
			value, ok = pack.Message.GetFieldValue("ReadsCompleted")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(13903))

			value, ok = pack.Message.GetFieldValue("ReadsMerged")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(11393))

			value, ok = pack.Message.GetFieldValue("SectorsRead")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(969224))

			value, ok = pack.Message.GetFieldValue("TimeReading")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(49444))

			value, ok = pack.Message.GetFieldValue("WritesCompleted")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(10780))

			value, ok = pack.Message.GetFieldValue("WritesMerged")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(10161))

			value, ok = pack.Message.GetFieldValue("SectorsWritten")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(1511920))

			value, ok = pack.Message.GetFieldValue("TimeWriting")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(4104))

			value, ok = pack.Message.GetFieldValue("NumIOInProgress")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(0))

			value, ok = pack.Message.GetFieldValue("TimeDoingIO")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(5064))

			value, ok = pack.Message.GetFieldValue("WeightedTimeDoingIO")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(53468))

			value, ok = pack.Message.GetFieldValue("TickerInterval")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(2))

			value, ok = pack.Message.GetFieldValue("FilePath")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, "/sys/block/sda/stat")
		})

		c.Specify("decodes a message with no leading space", func() {
			payload := "19092852        0 510563170 15817012 46452019        0 1546950712 262535124        0 23823976 278362684\n"
			pack.Message.SetPayload(payload)

			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)

			value, ok := pack.Message.GetFieldValue("ReadsCompleted")
			c.Expect(ok, gs.IsTrue)
			c.Expect(value, gs.Equals, float64(19092852))
		})

		c.Specify("decodes an invalid message", func() {
			data := "bogus message"
			pack.Message.SetPayload(data)
			packs, err := decoder.Decode(pack)
			c.Expect(len(packs), gs.Equals, 0)
			c.Expect(err.Error(), gs.Equals, "Failed parsing: "+data)
			c.Expect(decoder.processMessageFailures, gs.Equals, int64(1))
			decoder.Shutdown()
		})
	})

	c.Specify("Nginx access log decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/nginx_access.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		conf.Config["log_format"] = "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\""
		conf.Config["user_agent_transform"] = true
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decodes simple messages", func() {
			data := "127.0.0.1 - - [10/Feb/2014:08:46:41 -0800] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0\""
			pack.Message.SetPayload(data)
			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)

			c.Expect(pack.Message.GetTimestamp(),
				gs.Equals,
				int64(1392050801000000000))

			c.Expect(pack.Message.GetSeverity(), gs.Equals, int32(7))

			var ok bool
			var value interface{}
			value, ok = pack.Message.GetFieldValue("remote_addr")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, "127.0.0.1")

			value, ok = pack.Message.GetFieldValue("user_agent_browser")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, "Firefox")
			value, ok = pack.Message.GetFieldValue("user_agent_version")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(26))
			value, ok = pack.Message.GetFieldValue("user_agent_os")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, "Linux")
			_, ok = pack.Message.GetFieldValue("http_user_agent")
			c.Expect(ok, gs.Equals, false)

			value, ok = pack.Message.GetFieldValue("body_bytes_sent")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(0))

			value, ok = pack.Message.GetFieldValue("status")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(304))
			decoder.Shutdown()
		})

		c.Specify("decodes an invalid messages", func() {
			data := "bogus message"
			pack.Message.SetPayload(data)
			packs, err := decoder.Decode(pack)
			c.Expect(len(packs), gs.Equals, 0)
			c.Expect(err.Error(), gs.Equals, "Failed parsing: "+data)
			c.Expect(decoder.processMessageFailures, gs.Equals, int64(1))
			decoder.Shutdown()
		})
	})

	c.Specify("Apache access log decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/apache_access.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		conf.Config["log_format"] = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
		conf.Config["user_agent_transform"] = true
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decodes simple messages", func() {
			data := "127.0.0.1 - - [10/Feb/2014:08:46:41 -0800] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0\""
			pack.Message.SetPayload(data)
			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)

			c.Expect(pack.Message.GetTimestamp(),
				gs.Equals,
				int64(1392050801000000000))

			c.Expect(pack.Message.GetSeverity(), gs.Equals, int32(7))

			var ok bool
			var value interface{}
			value, ok = pack.Message.GetFieldValue("remote_addr")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, "127.0.0.1")

			value, ok = pack.Message.GetFieldValue("user_agent_browser")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, "Firefox")
			value, ok = pack.Message.GetFieldValue("user_agent_version")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(26))
			value, ok = pack.Message.GetFieldValue("user_agent_os")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, "Linux")
			_, ok = pack.Message.GetFieldValue("http_user_agent")
			c.Expect(ok, gs.Equals, false)

			value, ok = pack.Message.GetFieldValue("body_bytes_sent")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(0))

			value, ok = pack.Message.GetFieldValue("status")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(304))
			decoder.Shutdown()
		})

		c.Specify("decodes an invalid messages", func() {
			data := "bogus message"
			pack.Message.SetPayload(data)
			packs, err := decoder.Decode(pack)
			c.Expect(len(packs), gs.Equals, 0)
			c.Expect(err.Error(), gs.Equals, "Failed parsing: "+data)
			c.Expect(decoder.processMessageFailures, gs.Equals, int64(1))
			decoder.Shutdown()
		})
	})

	c.Specify("rsyslog decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/rsyslog.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		conf.Config["type"] = "MyTestFormat"
		conf.Config["template"] = "%pri% %TIMESTAMP% %TIMEGENERATED:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"
		conf.Config["tz"] = "America/Los_Angeles"
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decodes simple messages", func() {
			data := "28 Feb 10 12:58:58 2014-02-10T12:58:59-08:00 testhost widget[4322]: test message.\n"
			pack.Message.SetPayload(data)
			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)

			// Syslog timestamp doesn't support year, so we have to calculate
			// it for the current year or else this test will fail every
			// January.
			year := time.Now().Year()
			tStr := fmt.Sprintf("%d Feb 10 12:58:58 -0800", year)
			t, err := time.Parse("2006 Jan 02 15:04:05 -0700", tStr)
			c.Assume(err, gs.IsNil)
			unixT := t.UnixNano()

			c.Expect(pack.Message.GetTimestamp(), gs.Equals, unixT)

			c.Expect(pack.Message.GetSeverity(), gs.Equals, int32(4))
			c.Expect(pack.Message.GetHostname(), gs.Equals, "testhost")
			c.Expect(pack.Message.GetPid(), gs.Equals, int32(4322))
			c.Expect(pack.Message.GetPayload(), gs.Equals, "test message.")
			c.Expect(pack.Message.GetType(), gs.Equals, conf.Config["type"])

			var ok bool
			var value interface{}
			value, ok = pack.Message.GetFieldValue("programname")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, "widget")

			value, ok = pack.Message.GetFieldValue("syslogfacility")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(3))

			value, ok = pack.Message.GetFieldValue("timegenerated")
			c.Expect(ok, gs.Equals, true)
			c.Expect(value, gs.Equals, float64(1392065939000000000))

			decoder.Shutdown()
		})

		c.Specify("decodes an invalid messages", func() {
			data := "bogus message"
			pack.Message.SetPayload(data)
			packs, err := decoder.Decode(pack)
			c.Expect(len(packs), gs.Equals, 0)
			c.Expect(err.Error(), gs.Equals, "Failed parsing: "+data)
			c.Expect(decoder.processMessageFailures, gs.Equals, int64(1))
			decoder.Shutdown()
		})
	})

	c.Specify("mysql decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/mysql_slow_query.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		conf.Config["truncate_sql"] = int64(5)
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decode standard slow query log", func() {
			data := `# User@Host: syncrw[syncrw] @  [127.0.0.1]
# Query_time: 2.964652  Lock_time: 0.000050 Rows_sent: 251  Rows_examined: 9773
use widget;
SET last_insert_id=999,insert_id=1000,timestamp=1399500744;
# administrator command: do something
/* [queryName=FIND_ITEMS] */ SELECT *
FROM widget
WHERE id = 10;`
			pack.Message.SetPayload(data)
			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)

			c.Expect(pack.Message.GetTimestamp(),
				gs.Equals,
				int64(1399500744000000000))
			c.Expect(pack.Message.GetPayload(), gs.Equals, "/* [q...")
			c.Expect(pack.Message.GetType(), gs.Equals, "mysql.slow-query")

			decoder.Shutdown()
		})
	})

	c.Specify("mariadb decoder", func() {
		decoder := new(SandboxDecoder)
		decoder.SetPipelineConfig(pConfig)
		conf := decoder.ConfigStruct().(*sandbox.SandboxConfig)
		conf.ScriptFilename = "../lua/decoders/mariadb_slow_query.lua"
		conf.ModuleDirectory = "../../../../../../modules"
		conf.MemoryLimit = 8e6
		conf.Config = make(map[string]interface{})
		conf.Config["truncate_sql"] = int64(5)
		supply := make(chan *pipeline.PipelinePack, 1)
		pack := pipeline.NewPipelinePack(supply)
		dRunner := pm.NewMockDecoderRunner(ctrl)
		dRunner.EXPECT().Name().Return("SandboxDecoder")
		err := decoder.Init(conf)
		c.Assume(err, gs.IsNil)
		decoder.SetDecoderRunner(dRunner)

		c.Specify("decode standard slow query log", func() {
			data := `# User@Host: syncrw[syncrw] @  [127.0.0.1]
# Thread_id: 110804  Schema: weave0  QC_hit: No
# Query_time: 1.178108  Lock_time: 0.000053  Rows_sent: 198  Rows_examined: 198
SET timestamp=1399500744;
/* [queryName=FIND_ITEMS] */ SELECT *
FROM widget
WHERE id = 10;`
			pack.Message.SetPayload(data)
			_, err = decoder.Decode(pack)
			c.Assume(err, gs.IsNil)

			c.Expect(pack.Message.GetTimestamp(),
				gs.Equals,
				int64(1399500744000000000))
			c.Expect(pack.Message.GetPayload(), gs.Equals, "/* [q...")
			c.Expect(pack.Message.GetType(), gs.Equals, "mariadb.slow-query")

			decoder.Shutdown()
		})
	})
}
示例#5
0
func TestLocatorReadyNotify(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	uaid := "fce61180716a40ed8e79bf5ff0ba34bc"

	mckLogger := NewMockLogger(mockCtrl)
	mckLogger.EXPECT().ShouldLog(gomock.Any()).Return(true).AnyTimes()
	mckLogger.EXPECT().Log(gomock.Any(), gomock.Any(), gomock.Any(),
		gomock.Any()).AnyTimes()

	var (
		// routerPipes maps fake peer addresses to their respective pipes. Used
		// by dialRouter to connect to peers.
		routerPipes = make(map[netAddr]*pipeListener)

		// contacts is a list of peer URLs for the locator.
		contacts []string
	)

	// Fake listener for the sender's router, used to test self-routing.
	sndRouterAddr := netAddr{"tcp", "snd-router.example.com:3000"}
	sndRouterPipe := newPipeListener()
	defer sndRouterPipe.Close()
	routerPipes[sndRouterAddr] = sndRouterPipe
	contacts = append(contacts, "http://snd-router.example.com:3000")

	// Fake listener for the receiver's router, used to test routing updates
	// to different hosts.
	recvRouterAddr := netAddr{"tcp", "recv-router.example.com:3000"}
	recvRouterPipe := newPipeListener()
	defer recvRouterPipe.Close()
	routerPipes[recvRouterAddr] = recvRouterPipe
	contacts = append(contacts, "http://recv-router.example.com:3000")

	// Fake listener for the receiver's WebSocket handler, used to accept a
	// WebSocket client connection.
	socketHandlerPipe := newPipeListener()
	defer socketHandlerPipe.Close()

	// Fake locator.
	mckLocator := NewMockLocator(mockCtrl)
	mckLocator.EXPECT().Contacts(uaid).Return(contacts, nil).Times(2)

	// Fake dialer to connect to each peer's routing listener.
	dialRouter := func(network, address string) (net.Conn, error) {
		if pipe, ok := routerPipes[netAddr{network, address}]; ok {
			return pipe.Dial(network, address)
		}
		return nil, &netErr{temporary: false, timeout: false}
	}
	// Configures a fake router for the app.
	setRouter := func(app *Application, listener net.Listener) {
		r := NewBroadcastRouter()
		r.setApp(app)
		r.setClientOptions(10, 3*time.Second, 3*time.Second) // Defaults.
		r.setClientTransport(&http.Transport{Dial: dialRouter})
		r.listenWithConfig(listenerConfig{listener: listener})
		r.maxDataLen = 4096
		r.server = newServeWaiter(&http.Server{Handler: r.ServeMux()})
		app.SetRouter(r)
	}

	// sndApp is the server broadcasting the update. The locator returns the
	// addresses of the sender and receiver to test self-routing.
	sndApp := NewApplication()
	sndApp.SetLogger(mckLogger)
	sndStat := NewMockStatistician(mockCtrl)
	sndApp.SetMetrics(sndStat)
	sndStore := NewMockStore(mockCtrl)
	sndApp.SetStore(sndStore)
	sndApp.SetLocator(mckLocator)
	// Set up a fake router for the sender.
	setRouter(sndApp, sndRouterPipe)

	// recvApp is the server receiving the update.
	recvApp := NewApplication()
	recvApp.SetLogger(mckLogger)
	recvStat := NewMockStatistician(mockCtrl)
	recvApp.SetMetrics(recvStat)
	recvStore := NewMockStore(mockCtrl)
	recvApp.SetStore(recvStore)
	// Wrap the fake locator in a type that implements ReadyNotifier.
	recvLocator := newMockReadyNotifier(mckLocator)
	recvApp.SetLocator(recvLocator)
	// Set up a fake WebSocket handler for the receiver.
	recvSocketHandler := NewSocketHandler()
	recvSocketHandler.setApp(recvApp)
	recvSocketHandler.listenWithConfig(listenerConfig{
		listener: socketHandlerPipe})
	recvSocketHandler.server = newServeWaiter(&http.Server{Handler: recvSocketHandler.ServeMux()})
	recvApp.SetSocketHandler(recvSocketHandler)
	// Set up a fake router for the receiver.
	setRouter(recvApp, recvRouterPipe)

	chid := "2b7c5c27d6224bfeaf1c158c3c57fca3"
	version := int64(2)
	data := "I'm a little teapot, short and stout."

	var wg sync.WaitGroup // Waits for the client to close.
	wg.Add(1)
	dialChan := make(chan bool) // Signals when the client connects.
	timeout := closeAfter(2 * time.Second)

	go func() {
		defer wg.Done()
		origin := &url.URL{Scheme: "ws", Host: "recv-conn.example.com"}
		ws, err := dialSocketListener(socketHandlerPipe, &websocket.Config{
			Location: origin,
			Origin:   origin,
			Version:  websocket.ProtocolVersionHybi13,
		})
		if err != nil {
			t.Errorf("Error dialing host: %s", err)
			return
		}
		defer ws.Close()
		err = websocket.JSON.Send(ws, struct {
			Type       string   `json:"messageType"`
			DeviceID   string   `json:"uaid"`
			ChannelIDs []string `json:"channelIDs"`
		}{"hello", uaid, []string{}})
		if err != nil {
			t.Errorf("Error writing handshake request: %s", err)
			return
		}
		helloReply := new(HelloReply)
		if err = websocket.JSON.Receive(ws, helloReply); err != nil {
			t.Errorf("Error reading handshake reply: %s", err)
			return
		}
		select {
		case dialChan <- true:
		case <-timeout:
			t.Errorf("Timed out waiting for router")
			return
		}
		flushReply := new(FlushReply)
		if err = websocket.JSON.Receive(ws, flushReply); err != nil {
			t.Errorf("Error reading routed update: %s", err)
			return
		}
		ok := false
		expected := Update{chid, uint64(version), data}
		for _, update := range flushReply.Updates {
			if ok = update == expected; ok {
				break
			}
		}
		if !ok {
			t.Errorf("Missing update %#v in %#v", expected, flushReply.Updates)
			return
		}
	}()

	// Start the handlers.
	errChan := make(chan error, 3)
	go sndApp.Router().Start(errChan)
	go recvApp.SocketHandler().Start(errChan)
	go recvApp.Router().Start(errChan)

	// First and second routing attempts to self.
	sndStat.EXPECT().Increment("updates.routed.unknown").Times(2)

	// Initial routing attempt to peer.
	recvStat.EXPECT().Increment("updates.routed.unknown")
	// Client connects to peer.
	recvStat.EXPECT().Increment("client.socket.connect")
	recvStore.EXPECT().CanStore(0).Return(true)
	recvStat.EXPECT().Increment("updates.client.hello")
	recvStore.EXPECT().FetchAll(uaid, gomock.Any()).Return(nil, nil, nil)
	recvStat.EXPECT().Timer("client.flush", gomock.Any())
	// Second routing attempt to peer.
	recvStat.EXPECT().Increment("updates.routed.incoming")
	recvStat.EXPECT().Increment("updates.sent")
	recvStat.EXPECT().Timer("client.flush", gomock.Any())
	recvStat.EXPECT().Increment("updates.routed.received")
	recvStat.EXPECT().Timer("client.socket.lifespan", gomock.Any())
	recvStat.EXPECT().Increment("client.socket.disconnect")

	// Initial routing attempt should fail; the WebSocket listener shouldn't
	// accept client connections before the locator is ready.
	delivered, err := sndApp.Router().Route(nil, uaid, chid, version, timeNow(),
		"disconnected", data)
	if err != nil {
		t.Errorf("Error routing to disconnected client: %s", err)
	} else if delivered {
		t.Error("Should not route to disconnected client")
	}
	// Signal the locator is ready, then wait for the client to connect.
	recvLocator.SignalReady()
	select {
	case <-dialChan:
	case <-time.After(5 * time.Second):
		t.Fatalf("Timed out waiting for the client to connect")
	}
	// Routing should succeed once the client is connected.
	delivered, err = sndApp.Router().Route(nil, uaid, chid, version, timeNow(),
		"connected", data)
	if err != nil {
		t.Errorf("Error routing to connected client: %s", err)
	} else if !delivered {
		t.Error("Should route to connected client")
	}

	gomock.InOrder(
		mckLocator.EXPECT().Close(),
		recvStore.EXPECT().Close(),
	)
	if err := recvApp.Close(); err != nil {
		t.Errorf("Error closing peer: %s", err)
	}
	wg.Wait()
	gomock.InOrder(
		mckLocator.EXPECT().Close(),
		sndStore.EXPECT().Close(),
	)
	if err := sndApp.Close(); err != nil {
		t.Errorf("Error closing self: %s", err)
	}
	// Wait for the handlers to stop.
	for i := 0; i < 3; i++ {
		<-errChan
	}
}
示例#6
0
func TestEndpointResolveKey(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mckLogger := NewMockLogger(mockCtrl)
	mckLogger.EXPECT().ShouldLog(gomock.Any()).Return(true).AnyTimes()
	mckLogger.EXPECT().Log(gomock.Any(), gomock.Any(),
		gomock.Any(), gomock.Any()).AnyTimes()

	mckStat := NewMockStatistician(mockCtrl)
	mckStore := NewMockStore(mockCtrl)

	Convey("Endpoint tokens", t, func() {
		app := NewApplication()
		app.SetLogger(mckLogger)
		app.SetMetrics(mckStat)
		app.SetStore(mckStore)

		Convey("Should return a 404 for invalid tokens", func() {
			app.SetTokenKey("c3v0AlmmxXu_LSfdZY3l3eayLsIwkX48")
			eh := NewEndpointHandler()
			eh.setApp(app)
			app.SetEndpointHandler(eh)

			resp := httptest.NewRecorder()
			req := &http.Request{
				Method: "PUT",
				Header: http.Header{},
				URL: &url.URL{
					Path: "/update/j1bqzFq9WiwFZbqay-y7xVlfSvtO1eY="}, // "123.456"
			}
			gomock.InOrder(
				mckStore.EXPECT().KeyToIDs("123.456").Return("", "", ErrInvalidKey),
				mckStat.EXPECT().Increment("updates.appserver.invalid"),
			)
			eh.ServeMux().ServeHTTP(resp, req)

			So(resp.Code, ShouldEqual, 404)
			body, isJSON := getJSON(resp.HeaderMap, resp.Body)
			So(isJSON, ShouldBeTrue)
			So(body.String(), ShouldEqual, `"Invalid Token"`)
		})

		Convey("Should not decode plaintext tokens without a key", func() {
			var err error

			app.SetTokenKey("")
			eh := NewEndpointHandler()
			eh.setApp(app)
			app.SetEndpointHandler(eh)

			_, err = eh.decodePK("")
			So(err, ShouldNotBeNil)

			pk, err := eh.decodePK("123.456")
			So(pk, ShouldEqual, "123.456")
		})

		Convey("Should normalize decoded tokens", func() {
			app.SetTokenKey("LM1xDImCx0rB46LCnx-3v4-Iyfk1LeKJbx9wuvx_z3U=")
			eh := NewEndpointHandler()
			eh.setApp(app)
			app.SetEndpointHandler(eh)

			// Hyphenated IDs should be normalized.
			uaid := "dbda2ba2-004c-491f-9e3d-c5950aee93de"
			chid := "848cd568-3f2a-4108-9ce4-bd0d928ecad4"
			// " \t%s.%s\r\n" % (uaid, chid)
			encodedKey := "qfGSdZzwf20GXiYubmZfIXj11Rx4RGJujFsjSQGdF4LRBhHbB_vt3hdW7cRvL9Fq_t_guMBGkDgebOoa5gRd1GGLN-Cv6h5hkpRTbdju8Tk-hMyC91BP4CEres_8"

			// decodePK should trim whitespace from encoded keys.
			mckStore.EXPECT().KeyToIDs(
				fmt.Sprintf("%s.%s", uaid, chid)).Return(uaid, chid, nil)
			actualUAID, actualCHID, err := eh.resolvePK(encodedKey)
			So(err, ShouldBeNil)
			So(actualUAID, ShouldEqual, uaid)
			So(actualCHID, ShouldEqual, chid)
		})

		Convey("Should reject invalid tokens", func() {
			var err error

			app.SetTokenKey("IhnNwMNbsFWiafTXSgF4Ag==")
			eh := NewEndpointHandler()
			eh.setApp(app)
			app.SetEndpointHandler(eh)

			invalidKey := "b54QOw2omSWBiEq0IuyfBGxHBIR7AI9YhCMA0lP9" // "_=!@#$%^&*()[]"
			uaid := "82398a648c834f8b838cb3945eceaf29"
			chid := "af445ad07e5f46b7a6c858150fc5aa92"
			validKey := fmt.Sprintf("%s.%s", uaid, chid)
			encodedKey := "swKSH8P2qprRt5y0J4Wi7ybl-qzFv1j09WPOfuabpEJmVUqwUpxjprXc2R3Yw0ITbqc_Swntw9_EpCgo_XuRTn7Q7opQYoQUgMPhCgT0EGbK"

			_, _, err = eh.resolvePK(invalidKey[:8])
			So(err, ShouldNotBeNil)

			_, _, err = eh.resolvePK(invalidKey)
			So(err, ShouldNotBeNil)

			// Reject plaintext tokens if a key is specified.
			_, _, err = eh.resolvePK(validKey)
			So(err, ShouldNotBeNil)

			mckStore.EXPECT().KeyToIDs(validKey).Return("", "", ErrInvalidKey)
			_, _, err = eh.resolvePK(encodedKey)
			So(err, ShouldNotBeNil)

			mckStore.EXPECT().KeyToIDs(validKey).Return(uaid, chid, nil)
			actualUAID, actualCHID, err := eh.resolvePK(encodedKey)
			So(err, ShouldBeNil)
			So(actualUAID, ShouldEqual, uaid)
			So(actualCHID, ShouldEqual, chid)
		})
	})
}
示例#7
0
func TestEndpointDelivery(t *testing.T) {
	useMockFuncs()
	defer useStdFuncs()

	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mckLogger := NewMockLogger(mockCtrl)
	mckLogger.EXPECT().ShouldLog(gomock.Any()).Return(true).AnyTimes()
	mckLogger.EXPECT().Log(gomock.Any(), gomock.Any(),
		gomock.Any(), gomock.Any()).AnyTimes()
	mckStat := NewMockStatistician(mockCtrl)
	mckStore := NewMockStore(mockCtrl)
	mckRouter := NewMockRouter(mockCtrl)
	mckWorker := NewMockWorker(mockCtrl)

	Convey("Update delivery", t, func() {
		app := NewApplication()
		app.SetLogger(mckLogger)
		app.SetMetrics(mckStat)
		app.SetStore(mckStore)
		app.SetRouter(mckRouter)

		Convey("Should attempt local delivery if `AlwaysRoute` is disabled", func() {
			eh := NewEndpointHandler()
			eh.setApp(app)
			app.SetEndpointHandler(eh)

			Convey("Should route updates if the device is not connected", func() {
				uaid := "f7e9fc483f7344c398701b6fa0e85e4f"
				chid := "737b7a0d25674be4bb184f015fce02cf"
				gomock.InOrder(
					mckStat.EXPECT().Increment("updates.routed.outgoing"),
					mckRouter.EXPECT().Route(nil, uaid, chid, int64(3), timeNow().UTC(),
						"", "").Return(true, nil),
					mckStat.EXPECT().Increment("router.broadcast.hit"),
					mckStat.EXPECT().Timer("updates.routed.hits", gomock.Any()),
					mckStat.EXPECT().Increment("updates.appserver.received"),
				)
				ok := eh.deliver(nil, uaid, chid, 3, "", "")
				So(ok, ShouldBeTrue)
			})

			// A routing failure still stores the alert for potential
			// client delivery. We should only return 404 for absolute
			// failures (where the endpoint is no longer valid
			Convey("Should return a 202 if routing fails", func() {
				resp := httptest.NewRecorder()
				req := &http.Request{
					Method: "PUT",
					Header: http.Header{HeaderID: {"reqID"}},
					URL:    &url.URL{Path: "/update/123"},
					Body:   formReader(url.Values{"version": {"1"}}),
				}
				gomock.InOrder(
					mckStore.EXPECT().KeyToIDs("123").Return("123", "456", nil),
					mckStat.EXPECT().Increment("updates.appserver.incoming"),
					mckStore.EXPECT().Update("123", "456", int64(1)).Return(nil),
					mckStat.EXPECT().Increment("updates.routed.outgoing"),
					mckRouter.EXPECT().Route(nil, "123", "456", int64(1),
						gomock.Any(), "reqID", "").Return(false, nil),
					mckStat.EXPECT().Increment("router.broadcast.miss"),
					mckStat.EXPECT().Timer("updates.routed.misses", gomock.Any()),
					mckStat.EXPECT().Increment("updates.appserver.rejected"),
				)
				eh.ServeMux().ServeHTTP(resp, req)

				So(resp.Code, ShouldEqual, 202)
				body, isJSON := getJSON(resp.HeaderMap, resp.Body)
				So(isJSON, ShouldBeTrue)
				So(body.String(), ShouldEqual, "{}")
			})

			Convey("Should return a 404 if local delivery fails", func() {
				uaid := "9e98d6415d8e4fd099ab1bad7178f750"
				chid := "0eecf572e99f4d508666d8da6c0b15a9"
				app.AddWorker(uaid, mckWorker)

				gomock.InOrder(
					mckWorker.EXPECT().Send(chid, int64(3), "").Return(
						errors.New("client gone")),
					mckStat.EXPECT().Increment("updates.appserver.rejected"),
				)
				ok := eh.deliver(nil, uaid, chid, int64(3), "", "")
				So(ok, ShouldBeFalse)
			})

			Convey("Should return an error if storage is unavailable", func() {
				resp := httptest.NewRecorder()
				req := &http.Request{
					Method: "PUT",
					Header: http.Header{},
					URL:    &url.URL{Path: "/update/123"},
					Body:   formReader(url.Values{"version": {"2"}}),
				}
				updateErr := ErrInvalidChannel
				gomock.InOrder(
					mckStore.EXPECT().KeyToIDs("123").Return("123", "456", nil),
					mckStat.EXPECT().Increment("updates.appserver.incoming"),
					mckStore.EXPECT().Update("123", "456", int64(2)).Return(updateErr),
					mckStat.EXPECT().Increment("updates.appserver.error"),
				)
				eh.ServeMux().ServeHTTP(resp, req)

				So(resp.Code, ShouldEqual, updateErr.Status())
				body, isJSON := getJSON(resp.HeaderMap, resp.Body)
				So(isJSON, ShouldBeTrue)
				So(body.String(), ShouldEqual, `"Could not update channel version"`)
			})
		})

		Convey("Should always route updates if `AlwaysRoute` is enabled", func() {
			eh := NewEndpointHandler()
			eh.setApp(app)
			eh.alwaysRoute = true
			app.SetEndpointHandler(eh)

			uaid := "6952a68ee0e7444ebc54f935c4444b13"
			app.AddWorker(uaid, mckWorker)

			chid := "b7ede546585f4cc9b95e9340e3406951"
			version := int64(1)
			data := "Happy, happy, joy, joy!"

			Convey("And router delivery fails, local succeeds", func() {
				gomock.InOrder(
					mckStat.EXPECT().Increment("updates.routed.outgoing"),
					mckRouter.EXPECT().Route(nil, uaid, chid, version,
						gomock.Any(), "", data).Return(false, nil),
					mckStat.EXPECT().Increment("router.broadcast.miss"),
					mckStat.EXPECT().Timer("updates.routed.misses", gomock.Any()),
					mckWorker.EXPECT().Send(chid, version, data).Return(nil),
					mckStat.EXPECT().Increment("updates.appserver.received"),
				)

				ok := eh.deliver(nil, uaid, chid, version, "", data)
				So(ok, ShouldBeTrue)
			})

			Convey("And router delivery succeeds, local succeeds", func() {
				gomock.InOrder(
					mckStat.EXPECT().Increment("updates.routed.outgoing"),
					mckRouter.EXPECT().Route(nil, uaid, chid, version,
						gomock.Any(), "", data).Return(true, nil),
					mckStat.EXPECT().Increment("router.broadcast.hit"),
					mckStat.EXPECT().Timer("updates.routed.hits", gomock.Any()),
					mckWorker.EXPECT().Send(chid, version, data).Return(nil),
					mckStat.EXPECT().Increment("updates.appserver.received"),
				)

				ok := eh.deliver(nil, uaid, chid, version, "", data)
				So(ok, ShouldBeTrue)
			})

			Convey("And router delivery succeeds, local fails", func() {
				gomock.InOrder(
					mckStat.EXPECT().Increment("updates.routed.outgoing"),
					mckRouter.EXPECT().Route(nil, uaid, chid, version,
						gomock.Any(), "", data).Return(true, nil),
					mckStat.EXPECT().Increment("router.broadcast.hit"),
					mckStat.EXPECT().Timer("updates.routed.hits", gomock.Any()),
					mckWorker.EXPECT().Send(chid, version, data).Return(
						errors.New("client gone")),
					mckStat.EXPECT().Increment("updates.appserver.received"),
				)

				ok := eh.deliver(nil, uaid, chid, version, "", data)
				So(ok, ShouldBeTrue)
			})

			Convey("And router/local delivery fails", func() {
				gomock.InOrder(
					mckStat.EXPECT().Increment("updates.routed.outgoing"),
					mckRouter.EXPECT().Route(nil, uaid, chid, version,
						gomock.Any(), "", data).Return(false, nil),
					mckStat.EXPECT().Increment("router.broadcast.miss"),
					mckStat.EXPECT().Timer("updates.routed.misses", gomock.Any()),
					mckWorker.EXPECT().Send(chid, version, data).Return(
						errors.New("client gone")),
					mckStat.EXPECT().Increment("updates.appserver.rejected"),
				)

				ok := eh.deliver(nil, uaid, chid, version, "", data)
				So(ok, ShouldBeFalse)
			})

		})
	})
}
示例#8
0
func TestEndpointPinger(t *testing.T) {
	useMockFuncs()
	defer useStdFuncs()

	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mckLogger := NewMockLogger(mockCtrl)
	mckLogger.EXPECT().ShouldLog(gomock.Any()).Return(true).AnyTimes()
	mckLogger.EXPECT().Log(gomock.Any(), gomock.Any(),
		gomock.Any(), gomock.Any()).AnyTimes()

	mckStat := NewMockStatistician(mockCtrl)
	mckPinger := NewMockPropPinger(mockCtrl)
	mckStore := NewMockStore(mockCtrl)
	mckWorker := NewMockWorker(mockCtrl)

	Convey("Proprietary pings", t, func() {
		app := NewApplication()
		app.SetLogger(mckLogger)
		app.SetMetrics(mckStat)
		app.SetPropPinger(mckPinger)
		app.SetStore(mckStore)

		eh := NewEndpointHandler()
		eh.setApp(app)
		eh.setMaxDataLen(4096)
		app.SetEndpointHandler(eh)

		Convey("Should return early if the pinger can bypass the WebSocket", func() {
			uaid := "91357e1a34714cadacb3f13cf47a2736"
			app.AddWorker(uaid, mckWorker)

			resp := httptest.NewRecorder()
			req := &http.Request{
				Method: "PUT",
				Header: http.Header{},
				URL:    &url.URL{Path: "/update/123"},
				Body:   nil,
			}
			gomock.InOrder(
				mckStore.EXPECT().KeyToIDs("123").Return(uaid, "456", nil),
				mckStat.EXPECT().Increment("updates.appserver.incoming"),
				mckPinger.EXPECT().Send(uaid, int64(1257894000), "").Return(true, nil),
				mckPinger.EXPECT().CanBypassWebsocket().Return(true),
				mckStat.EXPECT().Increment("updates.appserver.received"),
				mckStat.EXPECT().Timer("updates.handled", gomock.Any()),
			)
			eh.ServeMux().ServeHTTP(resp, req)

			So(resp.Code, ShouldEqual, 200)
			body, isJSON := getJSON(resp.HeaderMap, resp.Body)
			So(isJSON, ShouldBeTrue)
			So(body.String(), ShouldEqual, "{}")
		})

		Convey("Should continue if the pinger cannot bypass the WebSocket", func() {
			uaid := "e3fc2cf1dc44424685010148b076d08b"
			app.AddWorker(uaid, mckWorker)

			data := randomText(eh.maxDataLen)
			vals := make(url.Values)
			vals.Set("data", data)

			resp := httptest.NewRecorder()
			req := &http.Request{
				Method: "PUT",
				Header: http.Header{},
				URL:    &url.URL{Path: "/update/123"},
				Body:   formReader(vals),
			}
			gomock.InOrder(
				mckStore.EXPECT().KeyToIDs("123").Return(uaid, "456", nil),
				mckStat.EXPECT().Increment("updates.appserver.incoming"),
				mckPinger.EXPECT().Send(uaid, int64(1257894000), data).Return(true, nil),
				mckPinger.EXPECT().CanBypassWebsocket().Return(false),
				mckStore.EXPECT().Update(uaid, "456", int64(1257894000)),
				mckWorker.EXPECT().Send("456", int64(1257894000), data),
				mckStat.EXPECT().Increment("updates.appserver.received"),
				mckStat.EXPECT().Timer("updates.handled", gomock.Any()),
			)
			eh.ServeMux().ServeHTTP(resp, req)

			So(resp.Code, ShouldEqual, 200)
			body, isJSON := getJSON(resp.HeaderMap, resp.Body)
			So(isJSON, ShouldBeTrue)
			So(body.String(), ShouldEqual, "{}")
		})

		Convey("Should continue if the pinger fails", func() {
			uaid := "8f412f5cb2384183bf60f7da26737271"
			app.AddWorker(uaid, mckWorker)

			vals := make(url.Values)
			vals.Set("version", "7")
			resp := httptest.NewRecorder()
			req := &http.Request{
				Method: "PUT",
				Header: http.Header{},
				URL:    &url.URL{Path: "/update/123"},
				Body:   formReader(vals),
			}
			gomock.InOrder(
				mckStore.EXPECT().KeyToIDs("123").Return(uaid, "456", nil),
				mckStat.EXPECT().Increment("updates.appserver.incoming"),
				mckPinger.EXPECT().Send(uaid, int64(7), "").Return(
					true, errors.New("oops")),
				mckStore.EXPECT().Update(uaid, "456", int64(7)),
				mckWorker.EXPECT().Send("456", int64(7), ""),
				mckStat.EXPECT().Increment("updates.appserver.received"),
				mckStat.EXPECT().Timer("updates.handled", gomock.Any()),
			)
			eh.ServeMux().ServeHTTP(resp, req)

			So(resp.Code, ShouldEqual, 200)
			body, isJSON := getJSON(resp.HeaderMap, resp.Body)
			So(isJSON, ShouldBeTrue)
			So(body.String(), ShouldEqual, "{}")
		})
	})
}
示例#9
0
func TestSocketOrigin(t *testing.T) {
	var err error

	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mckLogger := NewMockLogger(mockCtrl)
	mckLogger.EXPECT().ShouldLog(gomock.Any()).Return(true).AnyTimes()
	mckLogger.EXPECT().Log(gomock.Any(), gomock.Any(),
		gomock.Any(), gomock.Any()).AnyTimes()
	mckStat := NewMockStatistician(mockCtrl)
	mckStore := NewMockStore(mockCtrl)
	mckRouter := NewMockRouter(mockCtrl)

	app := NewApplication()
	app.SetLogger(mckLogger)
	app.SetMetrics(mckStat)
	app.SetStore(mckStore)
	app.SetRouter(mckRouter)

	sh := NewSocketHandler()
	defer sh.Close()
	sh.setApp(app)

	pipe := newPipeListener()
	defer pipe.Close()
	if err := sh.listenWithConfig(listenerConfig{listener: pipe}); err != nil {
		t.Fatalf("Error setting listener: %s", err)
	}
	sh.server = newServeWaiter(&http.Server{Handler: sh.ServeMux()})
	app.SetSocketHandler(sh)

	errChan := make(chan error, 1)
	go sh.Start(errChan)

	uaid := "5e1e5984569c4f00bf4bea47754a6403"
	gomock.InOrder(
		mckStat.EXPECT().Increment("client.socket.connect"),
		mckStore.EXPECT().CanStore(0).Return(true),
		mckRouter.EXPECT().Register(uaid),
		mckStat.EXPECT().Increment("updates.client.hello"),
		mckStore.EXPECT().FetchAll(uaid, gomock.Any()).Return(nil, nil, nil),
		mckStat.EXPECT().Timer("client.flush", gomock.Any()),
		mckRouter.EXPECT().Unregister(uaid),
		mckStat.EXPECT().Timer("client.socket.lifespan", gomock.Any()),
		mckStat.EXPECT().Increment("client.socket.disconnect"),
	)

	origin := &url.URL{Scheme: "https", Host: "example.com"}
	conn, err := dialSocketListener(pipe, &websocket.Config{
		Location: origin,
		Origin:   origin,
		Version:  websocket.ProtocolVersionHybi13,
	})
	if err != nil {
		t.Fatalf("Error dialing origin: %s", err)
	}
	defer conn.Close()
	err = websocket.JSON.Send(conn, struct {
		Type       string   `json:"messageType"`
		DeviceID   string   `json:"uaid"`
		ChannelIDs []string `json:"channelIDs"`
	}{"hello", uaid, []string{}})
	if err != nil {
		t.Fatalf("Error writing client handshake: %s", err)
	}
	reply := new(HelloReply)
	if err = websocket.JSON.Receive(conn, reply); err != nil {
		t.Fatalf("Error reading server handshake: %s", err)
	}
	if reply.DeviceID != uaid {
		t.Fatalf("Mismatched device ID: got %q; want %q", reply.DeviceID, uaid)
	}

	worker, workerConnected := app.GetWorker(uaid)
	if !workerConnected {
		t.Fatalf("Missing worker for device ID %q", uaid)
	}
	workerOrigin := worker.Origin()
	if expectedOrigin := origin.String(); workerOrigin != expectedOrigin {
		t.Errorf("Mismatched origins: got %q; want %q",
			workerOrigin, expectedOrigin)
	}
}
示例#10
0
func IrcOutputSpec(c gs.Context) {
	t := new(pipeline_ts.SimpleT)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	pipelineConfig := NewPipelineConfig(nil)
	outTestHelper := plugins_ts.NewOutputTestHelper(ctrl)

	var wg sync.WaitGroup

	errChan := make(chan error, 1)
	tickChan := make(chan time.Time)
	inChan := make(chan *PipelinePack, 5)

	c.Specify("An IrcOutput", func() {
		ircOutput := new(IrcOutput)
		ircOutput.InitIrcCon = NewMockIrcConn
		encoder := new(plugins.PayloadEncoder)
		encoder.Init(&plugins.PayloadEncoderConfig{})
		config := ircOutput.ConfigStruct().(*IrcOutputConfig)
		config.Server = "irc.example.org"
		config.Nick = "heka_bot"
		config.Ident = "heka"
		config.Channels = []string{"#test_channel"}

		msg := pipeline_ts.GetTestMessage()
		pack := NewPipelinePack(pipelineConfig.InputRecycleChan())
		pack.Message = msg
		pack.Decoded = true

		c.Specify("requires an encoder", func() {
			err := ircOutput.Init(config)
			c.Assume(err, gs.IsNil)

			outTestHelper.MockOutputRunner.EXPECT().Encoder().Return(nil)
			err = ircOutput.Run(outTestHelper.MockOutputRunner, outTestHelper.MockHelper)
			c.Expect(err, gs.Not(gs.IsNil))

		})

		c.Specify("that is started", func() {

			outTestHelper.MockOutputRunner.EXPECT().Ticker().Return(tickChan)
			outTestHelper.MockOutputRunner.EXPECT().Encoder().Return(encoder)
			outTestHelper.MockOutputRunner.EXPECT().InChan().Return(inChan)
			outTestHelper.MockOutputRunner.EXPECT().Encode(pack).Return(encoder.Encode(pack)).AnyTimes()

			startOutput := func() {
				wg.Add(1)
				go func() {
					err := ircOutput.Run(outTestHelper.MockOutputRunner, outTestHelper.MockHelper)
					errChan <- err
					wg.Done()
				}()
				<-mockIrcConn.connected
			}

			c.Specify("sends incoming messages to an irc server", func() {
				err := ircOutput.Init(config)
				c.Assume(err, gs.IsNil)

				c.Assume(len(ircOutput.Channels), gs.Equals, 1)
				ircChan := ircOutput.Channels[0]

				startOutput()

				// Send the data
				inChan <- pack
				// wait for it to arrive
				p := <-ircOutput.OutQueue
				ircOutput.OutQueue <- p
				// once it's arrived send a tick so it gets processed
				tickChan <- time.Now()

				close(inChan)
				wg.Wait()

				// Verify we've exited
				c.Expect(mockIrcConn.quit, gs.IsTrue)

				// verify the message was sent
				msgs := mockIrcConn.msgs[ircChan]
				c.Expect(len(msgs), gs.Equals, 1)
				c.Expect(msgs[0], gs.Equals, string(*msg.Payload))
			})

			c.Specify("drops messages when outqueue is full", func() {
				config.QueueSize = 1
				err := ircOutput.Init(config)
				c.Assume(err, gs.IsNil)

				c.Assume(len(ircOutput.Channels), gs.Equals, 1)
				ircChan := ircOutput.Channels[0]

				outTestHelper.MockOutputRunner.EXPECT().LogError(
					ErrOutQueueFull)
				outTestHelper.MockOutputRunner.EXPECT().LogError(
					fmt.Errorf("%s Channel: %s.", ErrBacklogQueueFull, ircChan))

				startOutput()

				// Need to wait for callbacks to get registered before calling part.
				// If we've called connected, then callbacks have been registered.

				// Leave the irc channel so we can fill up the backlog
				ircOutput.Conn.Part(ircChan)
				c.Expect(ircOutput.JoinedChannels[0], gs.Equals, NOTJOINED)

				// Put some data into the inChan
				inChan <- pack

				// Wait for it to arrive in the OutQueue and put it back
				// We *must* do this before sending the tick to avoid data races
				p := <-ircOutput.OutQueue
				ircOutput.OutQueue <- p

				// One tick so that it goes from the OutQueue into the BacklogQueue
				tickChan <- time.Now()

				// Verify the item is in the backlog queue and put it back
				// (It will never leave this loop if the msg never arrives)
				queue := ircOutput.BacklogQueues[0]
				msg := <-queue
				queue <- msg
				c.Expect(len(queue), gs.Equals, 1)

				// Send another message to fill the OutQueue
				inChan <- pack

				// Wait for it to arrive, again
				p = <-ircOutput.OutQueue
				ircOutput.OutQueue <- p

				// Send the tick after it's in the OutQueue so we can try processing
				// it. This is where we drop it since we cant send, and the
				// BacklogQueue is already full.
				tickChan <- time.Now()

				// Now we want to also cause the OutQueue to drop a message.
				// We don't have to wait for it to arrive in the OutQueue like we do above
				// since it shouldn't make it there.
				inChan <- pack
				inChan <- pack

				close(inChan)
				wg.Wait()

				// Verify we've exited
				c.Expect(mockIrcConn.quit, gs.IsTrue)

				// verify the backlog queue is empty
				c.Expect(len(queue), gs.Equals, 0)

				// verify that no messages were sent.
				msgs := mockIrcConn.msgs[ircChan]
				c.Expect(len(msgs), gs.Equals, 0)
			})

			c.Specify("automatically reconnects on disconnect", func() {
				config.TimeBeforeReconnect = 0
				err := ircOutput.Init(config)
				c.Assume(err, gs.IsNil)

				outTestHelper.MockOutputRunner.EXPECT().LogMessage(DisconnectMsg)
				outTestHelper.MockOutputRunner.EXPECT().LogMessage(ReconnectedMsg)

				startOutput()

				ircOutput.Conn.Disconnect()
				// verify we've been disconnected
				c.Expect(<-mockIrcConn.connected, gs.IsFalse)

				// Wait to become reconnected.
				c.Expect(<-mockIrcConn.connected, gs.IsTrue)

				close(inChan)
				wg.Wait()

				c.Expect(mockIrcConn.quit, gs.IsTrue)
			})

			c.Specify("logs an error and exits from Run() when it cannot reconnect", func() {
				config.TimeBeforeReconnect = 0

				err := ircOutput.Init(config)
				c.Assume(err, gs.IsNil)

				// Explicitly fail to reconnect
				mockIrcConn.failReconnect = true

				outTestHelper.MockOutputRunner.EXPECT().LogMessage(DisconnectMsg)
				outTestHelper.MockOutputRunner.EXPECT().LogError(fmt.Errorf(ErrReconnecting, mockErrFailReconnect))

				startOutput()

				// Need to wait for callbacks to get registered before we disconnect

				ircOutput.Conn.Disconnect()
				// verify we've been disconnected

				// Run() should return without closing the inChan because the plugin
				// automatically cleans up and returns if it fails to reconnect
				wg.Wait()

				c.Expect(mockIrcConn.quit, gs.IsTrue)
				// do this at the end for test cleanup purposes
				close(inChan)
			})

			c.Specify("when kicked from an irc channel", func() {

				c.Specify("rejoins when configured to do so", func() {
					config.RejoinOnKick = true
					err := ircOutput.Init(config)
					c.Assume(err, gs.IsNil)

					c.Assume(len(ircOutput.Channels), gs.Equals, 1)
					ircChan := ircOutput.Channels[0]

					startOutput()

					// trigger a kick event for the irc channel
					kick(mockIrcConn, ircChan)
					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, JOINED)
					// quit
					close(inChan)
				})

				c.Specify("doesnt rejoin when it isnt configured to", func() {
					config.RejoinOnKick = false // this is already the default
					err := ircOutput.Init(config)
					c.Assume(err, gs.IsNil)

					c.Assume(len(ircOutput.Channels), gs.Equals, 1)
					ircChan := ircOutput.Channels[0]

					startOutput()

					// Since this is the only channel we're in, we should get an
					// error that there are no channels to join left after being
					// kicked
					outTestHelper.MockOutputRunner.EXPECT().LogError(ErrNoJoinableChannels)
					// trigger a kick for the irc channel
					kick(mockIrcConn, ircChan)
					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, CANNOTJOIN)
					// We shouldnt need to close the inChan, since we have no
					// joinable channels, we should be cleaning up already.
				})

				wg.Wait()
			})

			c.Specify("when trying to join an unjoinable channel", func() {
				c.Specify("it logs an error", func() {
					config.Channels = []string{"foo", "baz"}
					err := ircOutput.Init(config)
					c.Assume(err, gs.IsNil)

					c.Assume(len(ircOutput.Channels), gs.Equals, 2)
					ircChan := ircOutput.Channels[0]

					startOutput()

					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, JOINED)

					reason := "foo"

					err = IrcCannotJoinError{ircChan, reason}
					outTestHelper.MockOutputRunner.EXPECT().LogError(err)

					args := []string{"", ircChan, reason}
					event := irc.Event{Arguments: args}

					c.Specify("when the channel key is wrong", func() {
						event.Code = IRC_ERR_BADCHANNELKEY
					})

					c.Specify("when banned from the channel", func() {
						event.Code = IRC_ERR_BANNEDFROMCHAN
					})

					c.Specify("when there is no such channel", func() {
						event.Code = IRC_NOSUCHCHANNEL
					})

					c.Specify("when the channel is invite only", func() {
						event.Code = IRC_ERR_INVITEONLYCHAN
					})

					ircOutput.Conn.RunCallbacks(&event)

					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, CANNOTJOIN)

					// should still be unable to join
					ircOutput.Join(ircChan)
					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, CANNOTJOIN)

					close(inChan)
				})

				c.Specify("it exits if there are no joinable channels", func() {
					err := ircOutput.Init(config)
					c.Assume(err, gs.IsNil)

					c.Assume(len(ircOutput.Channels), gs.Equals, 1)
					ircChan := ircOutput.Channels[0]

					startOutput()

					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, JOINED)

					reason := "foo"

					err = IrcCannotJoinError{ircChan, reason}
					outTestHelper.MockOutputRunner.EXPECT().LogError(err)
					outTestHelper.MockOutputRunner.EXPECT().LogError(ErrNoJoinableChannels)

					args := []string{"", ircChan, reason}
					event := irc.Event{Arguments: args, Code: IRC_ERR_BADCHANNELKEY}

					ircOutput.Conn.RunCallbacks(&event)

					// No close since we should exit without.
				})

				wg.Wait()
			})

			c.Specify("when trying to join a full channel", func() {
				config.TimeBeforeRejoin = 0
				err := ircOutput.Init(config)
				c.Assume(err, gs.IsNil)

				c.Assume(len(ircOutput.Channels), gs.Equals, 1)
				ircChan := ircOutput.Channels[0]

				startOutput()

				c.Expect(ircOutput.JoinedChannels[0], gs.Equals, JOINED)

				// We should be able to join before this
				ircOutput.Join(ircChan)

				reason := "full channel"
				err = fmt.Errorf("%s. Retrying in %d seconds.",
					IrcCannotJoinError{ircChan, reason}.Error(),
					ircOutput.TimeBeforeRejoin)

				args := []string{"", ircChan, reason}
				event := irc.Event{Code: IRC_ERR_CHANNELISFULL, Arguments: args}

				c.Specify("logs an error after using up its attempts", func() {
					mockIrcConn.failJoin = true
					// We should leave the channel so that we aren't just
					// causing a failure to join even when we're already in the
					// channel
					ircOutput.Conn.Part(ircChan)

					maxRetries := int(ircOutput.MaxJoinRetries)
					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, NOTJOINED)

					gomock.InOrder(
						outTestHelper.MockOutputRunner.EXPECT().LogError(
							err).Times(maxRetries),
						outTestHelper.MockOutputRunner.EXPECT().LogError(
							fmt.Errorf(ErrUsedUpRetryAttempts, ircChan)),
						outTestHelper.MockOutputRunner.EXPECT().LogError(
							ErrNoJoinableChannels),
					)

					for i := 1; i <= maxRetries; i++ {
						// Trigger the event the number of times
						ircOutput.Conn.RunCallbacks(&event)
						c.Expect(int(ircOutput.numRetries[ircChan]), gs.Equals, i)

					}
					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, NOTJOINED)
					ircOutput.Conn.RunCallbacks(&event)
					// We've used up our attempts,
					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, CANNOTJOIN)
					ircOutput.Conn.RunCallbacks(&event)
					c.Expect(ircOutput.JoinedChannels[0], gs.Equals, CANNOTJOIN)
				})

				c.Specify("resets its attempts when it successful", func() {
					mockIrcConn.failJoin = true
					outTestHelper.MockOutputRunner.EXPECT().LogError(err).Times(2)
					ircOutput.Conn.RunCallbacks(&event)
					c.Expect(int(ircOutput.numRetries[ircChan]), gs.Equals, 1)
					mockIrcConn.failJoin = false
					ircOutput.Conn.RunCallbacks(&event)
					c.Expect(int(ircOutput.numRetries[ircChan]), gs.Equals, 0)
				})

				close(inChan)
				wg.Wait()
			})

			c.Specify("when banned from a server logs an error and quits", func() {
				err := ircOutput.Init(config)
				c.Assume(err, gs.IsNil)

				startOutput()

				event := irc.Event{Code: IRC_ERR_YOUREBANNEDCREEP}
				outTestHelper.MockOutputRunner.EXPECT().LogError(ErrBannedFromServer)
				ircOutput.Conn.RunCallbacks(&event)

				// We should be able to exit without closing the channel first
				wg.Wait()

				// Clean this up
				close(inChan)
			})

			c.Expect(<-errChan, gs.IsNil)
			c.Expect(<-mockIrcConn.connected, gs.IsFalse)

		})

		// Cleanup which should happen each run
		close(mockIrcConn.connected)
		close(mockIrcConn.delivered)
		close(mockIrcConn.Error)
	})

}