wabbit-master/0000755000000000000000000000000013163221607012316 5ustar rootrootwabbit-master/amqp/0000755000000000000000000000000013163221607013254 5ustar rootrootwabbit-master/amqp/confirmation.go0000644000000000000000000000036613163221607016300 0ustar rootrootpackage amqp import "github.com/streadway/amqp" type Confirmation struct { amqp.Confirmation } func (c Confirmation) Ack() bool { return c.Confirmation.Ack } func (c Confirmation) DeliveryTag() uint64 { return c.Confirmation.DeliveryTag } wabbit-master/amqp/dial.go0000644000000000000000000000546113163221607014522 0ustar rootrootpackage amqp import ( "time" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/utils" "github.com/streadway/amqp" ) // Conn is the amqp connection type Conn struct { *amqp.Connection // closure info of connection dialFn func() error attempts uint8 } // Dial to AMQP broker func Dial(uri string) (*Conn, error) { conn := &Conn{} // closure the uri for handle reconnects conn.dialFn = func() error { var err error conn.Connection, err = amqp.Dial(uri) if err != nil { return err } return nil } err := conn.dialFn() if err != nil { return nil, err } return conn, nil } // NotifyClose registers a listener for close events. // For more information see: https://godoc.org/github.com/streadway/amqp#Connection.NotifyClose func (conn *Conn) NotifyClose(c chan wabbit.Error) chan wabbit.Error { var ( amqpErr2 chan *amqp.Error ) amqpErr2 = make(chan *amqp.Error) amqpErr := conn.Connection.NotifyClose(amqpErr2) go func() { for { err := <-amqpErr var ne wabbit.Error if err != nil { ne = utils.NewError( err.Code, err.Reason, err.Server, err.Recover, ) } else { ne = nil } c <- ne } }() return c } // AutoRedial manages the automatic redial of connection when unexpected closed. // outChan is an unbuffered channel required to receive the errors that results from // attempts of reconnect. On successfully reconnected, the true value is sent to done channel // // The outChan parameter can receive *amqp.Error for AMQP connection errors // or errors.Error for any other net/tcp internal error. // // Redial strategy: // If the connection is closed in an unexpected way (opposite of conn.Close()), then // AutoRedial will try to automatically reconnect waiting for N seconds before each // attempt, where N is the number of attempts of reconnecting. If the number of // attempts reach 60, it will be zero'ed. func (conn *Conn) AutoRedial(outChan chan wabbit.Error, done chan bool) { errChan2 := make(chan wabbit.Error) errChan := conn.NotifyClose(errChan2) go func() { var err wabbit.Error select { case amqpErr := <-errChan: err = amqpErr if amqpErr == nil { // Gracefull connection close return } attempts: outChan <- err if conn.attempts > 60 { conn.attempts = 0 } // Wait n Seconds where n == conn.attempts... time.Sleep(time.Duration(conn.attempts) * time.Second) connErr := conn.dialFn() if connErr != nil { conn.attempts++ goto attempts } // enabled AutoRedial on the new connection conn.AutoRedial(outChan, done) done <- true return } }() } // Channel returns a new channel ready to be used func (conn *Conn) Channel() (wabbit.Channel, error) { ch, err := conn.Connection.Channel() if err != nil { return nil, err } return &Channel{ch}, nil } wabbit-master/amqp/queue.go0000644000000000000000000000100013163221607014716 0ustar rootrootpackage amqp import "github.com/streadway/amqp" // Queue is a wrapper for "streadway/amqp".Queue but implementing // the wabbit.Queue interface. type Queue struct { *amqp.Queue } // Messages returns the count of messages not awaiting acknowledgment func (q *Queue) Messages() int { return q.Queue.Messages } // Name of the queue func (q *Queue) Name() string { return q.Queue.Name } // Consumers returns the amount of consumers of this queue func (q *Queue) Consumers() int { return q.Queue.Consumers } wabbit-master/amqp/channel.go0000644000000000000000000001313213163221607015213 0ustar rootrootpackage amqp import ( "errors" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/utils" "github.com/streadway/amqp" ) // Channel is a wrapper channel structure for amqp.Channel type Channel struct { *amqp.Channel } func (ch *Channel) Publish(exc, route string, msg []byte, opt wabbit.Option) error { amqpOpt, err := utils.ConvertOpt(opt) if err != nil { return err } amqpOpt.Body = msg return ch.Channel.Publish( exc, // publish to an exchange route, // routing to 0 or more queues false, // mandatory false, // immediate amqpOpt, ) } func (ch *Channel) Confirm(noWait bool) error { return ch.Channel.Confirm(noWait) } func (ch *Channel) NotifyPublish(confirm chan wabbit.Confirmation) chan wabbit.Confirmation { amqpConfirms := ch.Channel.NotifyPublish(make(chan amqp.Confirmation, cap(confirm))) go func() { for c := range amqpConfirms { confirm <- Confirmation{c} } close(confirm) }() return confirm } func (ch *Channel) Consume(queue, consumer string, opt wabbit.Option) (<-chan wabbit.Delivery, error) { var ( autoAck, exclusive, noLocal, noWait bool args amqp.Table ) if v, ok := opt["autoAck"]; ok { autoAck, ok = v.(bool) if !ok { return nil, errors.New("durable option is of type bool") } } if v, ok := opt["exclusive"]; ok { exclusive, ok = v.(bool) if !ok { return nil, errors.New("exclusive option is of type bool") } } if v, ok := opt["noLocal"]; ok { noLocal, ok = v.(bool) if !ok { return nil, errors.New("noLocal option is of type bool") } } if v, ok := opt["noWait"]; ok { noWait, ok = v.(bool) if !ok { return nil, errors.New("noWait option is of type bool") } } if v, ok := opt["args"]; ok { args, ok = v.(amqp.Table) if !ok { return nil, errors.New("args is of type amqp.Table") } } amqpd, err := ch.Channel.Consume(queue, consumer, autoAck, exclusive, noLocal, noWait, args) if err != nil { return nil, err } deliveries := make(chan wabbit.Delivery) go func() { for d := range amqpd { delivery := d deliveries <- &Delivery{&delivery} } close(deliveries) }() return deliveries, nil } func (ch *Channel) ExchangeDeclare(name, kind string, opt wabbit.Option) error { var ( durable, autoDelete, internal, noWait bool args amqp.Table ) if v, ok := opt["durable"]; ok { durable, ok = v.(bool) if !ok { return errors.New("durable option is of type bool") } } if v, ok := opt["autoDelete"]; ok { autoDelete, ok = v.(bool) if !ok { return errors.New("autoDelete option is of type bool") } } if v, ok := opt["internal"]; ok { internal, ok = v.(bool) if !ok { return errors.New("internal option is of type bool") } } if v, ok := opt["noWait"]; ok { noWait, ok = v.(bool) if !ok { return errors.New("noWait option is of type bool") } } if v, ok := opt["args"]; ok { args, ok = v.(amqp.Table) if !ok { return errors.New("args is of type amqp.Table") } } return ch.Channel.ExchangeDeclare(name, kind, durable, autoDelete, internal, noWait, args) } func (ch *Channel) QueueUnbind(name, route, exchange string, _ wabbit.Option) error { return ch.Channel.QueueUnbind(name, route, exchange, nil) } // QueueBind binds the route key to queue func (ch *Channel) QueueBind(name, key, exchange string, opt wabbit.Option) error { var ( noWait bool args amqp.Table ) if v, ok := opt["noWait"]; ok { noWait, ok = v.(bool) if !ok { return errors.New("noWait option is of type bool") } } if v, ok := opt["args"]; ok { args, ok = v.(amqp.Table) if !ok { return errors.New("args is of type amqp.Table") } } return ch.Channel.QueueBind(name, key, exchange, noWait, args) } // QueueDeclare declares a new AMQP queue func (ch *Channel) QueueDeclare(name string, opt wabbit.Option) (wabbit.Queue, error) { var ( durable, autoDelete, exclusive, noWait bool args amqp.Table ) if v, ok := opt["durable"]; ok { durable, ok = v.(bool) if !ok { return nil, errors.New("durable option is of type bool") } } if v, ok := opt["autoDelete"]; ok { autoDelete, ok = v.(bool) if !ok { return nil, errors.New("autoDelete option is of type bool") } } if v, ok := opt["exclusive"]; ok { exclusive, ok = v.(bool) if !ok { return nil, errors.New("Exclusive option is of type bool") } } if v, ok := opt["noWait"]; ok { noWait, ok = v.(bool) if !ok { return nil, errors.New("noWait option is of type bool") } } if v, ok := opt["args"]; ok { args, ok = v.(amqp.Table) if !ok { return nil, errors.New("args is of type amqp.Table") } } q, err := ch.Channel.QueueDeclare(name, durable, autoDelete, exclusive, noWait, args) if err != nil { return nil, err } return &Queue{&q}, nil } func (ch *Channel) QueueDelete(name string, opt wabbit.Option) (int, error) { var ( ifUnused, ifEmpty, noWait bool ) if v, ok := opt["ifUnused"]; ok { ifUnused, ok = v.(bool) if !ok { return 0, errors.New("ifUnused option is of type bool") } } if v, ok := opt["ifEmpty"]; ok { ifEmpty, ok = v.(bool) if !ok { return 0, errors.New("ifEmpty option is of type bool") } } if v, ok := opt["noWait"]; ok { noWait, ok = v.(bool) if !ok { return 0, errors.New("noWait option is of type bool") } } return ch.Channel.QueueDelete(name, ifUnused, ifEmpty, noWait) } // Qos controls how many bytes or messages will be handled by channel or connection. func (ch *Channel) Qos(prefetchCount, prefetchSize int, global bool) error { return ch.Channel.Qos(prefetchCount, prefetchSize, global) } wabbit-master/amqp/publisher.go0000644000000000000000000000120013163221607015571 0ustar rootrootpackage amqp import "github.com/NeowayLabs/wabbit" type Publisher struct { conn wabbit.Conn channel wabbit.Publisher } func NewPublisher(conn wabbit.Conn, channel wabbit.Channel) (*Publisher, error) { var err error pb := Publisher{ conn: conn, } if channel == nil { channel, err = conn.Channel() if err != nil { return nil, err } } pb.channel = channel return &pb, nil } func (pb *Publisher) Publish(exc string, route string, message []byte, opt wabbit.Option) error { err := pb.channel.Publish( exc, // publish to an exchange route, // routing to 0 or more queues message, opt, ) return err } wabbit-master/amqp/dial_test.go0000644000000000000000000001031013163221607015546 0ustar rootroot// +build integration package amqp import ( "errors" "fmt" "os" "testing" "time" "github.com/NeowayLabs/wabbit" "github.com/fsouza/go-dockerclient" "github.com/tiago4orion/conjure" ) var ( dockerClient *conjure.Client rabbitmqCtn *docker.Container rabbitmqCtnName1 = "test-rabbitmq1" rabbitmqPort1 = "35672" rabbitmqSpec1 = `{ "Name": "` + rabbitmqCtnName1 + `", "Config": { "Image": "rabbitmq", "ExposedPorts": { "5672/tcp": {} } }, "HostConfig": { "PortBindings": { "5672/tcp": [ { "HostPort": "` + rabbitmqPort1 + `" } ] }, "PublishAllPorts": true, "Privileged": false } }` rabbitmqCtnName2 = "test-rabbitmq1" rabbitmqPort2 = "35673" rabbitmqSpec2 = `{ "Name": "` + rabbitmqCtnName2 + `", "Config": { "Image": "rabbitmq", "ExposedPorts": { "5672/tcp": {} } }, "HostConfig": { "PortBindings": { "5672/tcp": [ { "HostPort": "` + rabbitmqPort2 + `" } ] }, "PublishAllPorts": true, "Privileged": false } }` ) func TestMain(m *testing.M) { var err error dockerClient, err = conjure.NewClient() if err != nil || dockerClient == nil { fmt.Printf("You need docker >= 1.6 installed to enable testing rabbitmq backend\n") os.Exit(1) } dockerClient.Remove(rabbitmqCtnName1) dockerClient.Remove(rabbitmqCtnName2) // Execute the tests status := m.Run() // remove the backend container dockerClient.Remove(rabbitmqCtnName1) dockerClient.Remove(rabbitmqCtnName2) os.Exit(status) } // WaitOK blocks until rabbitmq can accept connections on // :5672 func waitRabbitOK(host string, port string) error { var err error var counter uint dial: _, err = Dial("amqp://guest:guest@" + host + ":" + port + "/%2f") if err != nil { if counter >= 120 { panic("isn't possible to connect on rabbitmq") } counter++ time.Sleep(500 * time.Millisecond) goto dial } return nil } // TestDial test a simple connection to rabbitmq. // If the rabbitmq disconnects will not be tested here! func TestDial(t *testing.T) { // Should fail _, err := Dial("amqp://guest:guest@localhost:35672/%2f") if err == nil { t.Error("No backend started... Should fail") return } rabbitmqCtn, err = dockerClient.Run(rabbitmqSpec1) if err != nil { t.Error(err) } err = waitRabbitOK("localhost", rabbitmqPort1) if err != nil { t.Error(err) return } _, err = Dial("amqp://guest:guest@localhost:35672/%2f") if err != nil { t.Error(err) return } dockerClient.Remove(rabbitmqCtnName1) } func TestAutoRedial(t *testing.T) { var err error dockerClient.Remove(rabbitmqCtnName2) rabbitmqCtn, err = dockerClient.Run(rabbitmqSpec2) if err != nil { t.Errorf("Failed to start rabbitmq: %s", err.Error()) return } err = waitRabbitOK("localhost", rabbitmqPort2) if err != nil { t.Error(err) return } conn, err := Dial("amqp://guest:guest@localhost:35673/%2f") if err != nil { t.Error(err) return } redialErrors := make(chan wabbit.Error) done := make(chan bool) conn.AutoRedial(redialErrors, done) // required goroutine to consume connection error messages go func() { for { // discards the connection errors <-redialErrors } }() dockerClient.StopContainer(rabbitmqCtnName2, 3) // concurrently starts the rabbitmq after 1 second go func() { time.Sleep(1 * time.Second) err := dockerClient.StartContainer(rabbitmqCtnName2, nil) if err != nil { t.Error(err) return } }() select { case <-time.After(10 * time.Second): err = errors.New("Failed to reconnect. Timeout exceeded") case <-done: err = nil } if err != nil { t.Errorf("Client doesn't reconnect in 10 seconds: %s", err.Error()) return } conn.Close() dockerClient.Remove(rabbitmqCtnName2) } func TestChannelMock(t *testing.T) { var channel wabbit.Channel // rabbitmq.Channel satisfies wabbit.Channel interface channel = new(Channel) if channel == nil { t.Error("Maybe wabbit.Channel interface does not mock amqp.Channel correctly") } } func TestConnMock(t *testing.T) { var conn wabbit.Conn // rabbitmq.Conn satisfies wabbit.Conn interface conn = &Conn{} if conn == nil { t.Error("Maybe wabbit.Conn interface does not mock amqp.Conn correctly") } } wabbit-master/amqp/delivery.go0000644000000000000000000000066013163221607015430 0ustar rootrootpackage amqp import ( "github.com/NeowayLabs/wabbit" "github.com/streadway/amqp" ) type Delivery struct { *amqp.Delivery } func (d *Delivery) Body() []byte { return d.Delivery.Body } func (d *Delivery) Headers() wabbit.Option { return wabbit.Option(d.Delivery.Headers) } func (d *Delivery) DeliveryTag() uint64 { return d.Delivery.DeliveryTag } func (d *Delivery) ConsumerTag() string { return d.Delivery.ConsumerTag } wabbit-master/amqp/receiver.go0000644000000000000000000000001513163221607015403 0ustar rootrootpackage amqp wabbit-master/hack/0000755000000000000000000000000013163221607013224 5ustar rootrootwabbit-master/hack/check.rc0000755000000000000000000000217713163221607014641 0ustar rootroot#!/bin/rc # The script does automatic checking on a Go package and its sub-packages, including: # 1. gofmt (http://golang.org/cmd/gofmt/) # 2. goimports (https://github.com/bradfitz/goimports) # 3. golint (https://github.com/golang/lint) # 4. go vet (http://golang.org/cmd/vet) # 5. race detector (http://blog.golang.org/race-detector) # 6. test coverage (http://blog.golang.org/cover) GO=go # Automatic checks test -z '`{gofmt -l -w . | tee /dev/stderr}' test -z '`{golint . | tee /dev/stderr}' #$GO vet ./... # Run test coverage on each subdirectories and merge the coverage profile. echo 'mode: count' > coverage.txt # Standard $GO tooling behavior is to ignore dirs with leading underscors for(dir in `{find . -maxdepth 10 -not -path './.git*' -not -path './Godeps/*' -type d}) { if(ls $dir^/*.go >/dev/null) { $GO test -v -race '-covermode=atomic' '-coverprofile='^$dir^'/profile.tmp' $dir if(ls $dir^/profile.tmp) { cat $dir^/profile.tmp | tail -n +2 >> coverage.txt rm $dir^/profile.tmp } # Stress # hack/stress-test.sh } } $GO tool cover -func coverage.txt wabbit-master/hack/check.sh0000755000000000000000000000322113163221607014636 0ustar rootroot#!/bin/bash # The script does automatic checking on a Go package and its sub-packages, including: # 1. gofmt (http://golang.org/cmd/gofmt/) # 2. goimports (https://github.com/bradfitz/goimports) # 3. golint (https://github.com/golang/lint) # 4. go vet (http://golang.org/cmd/vet) # 5. race detector (http://blog.golang.org/race-detector) # 6. test coverage (http://blog.golang.org/cover) set -e GO="go" # Automatic checks test -z "$(gofmt -l -w . | tee /dev/stderr)" #test -z "$(goimports -l -w . | tee /dev/stderr)" #test -z "$(golint . | tee /dev/stderr)" #$GO vet ./... # Run test coverage on each subdirectories and merge the coverage profile. echo "mode: count" > coverage.txt if [ "x${TEST_DIRECTORY:0:1}" != "x." ]; then TEST_DIRECTORY="./$TEST_DIRECTORY" fi DOCKERPATH="$(which docker 2>/dev/null 1>/dev/null|| echo 'not found')" TAGS="" if [ "${DOCKERPATH}" != "not found" ]; then TAGS="-tags integration" fi # Standard $GO tooling behavior is to ignore dirs with leading underscors for dir in $(find "$TEST_DIRECTORY" -maxdepth 10 -not -path './_examples*' -not -path './.git*' -not -path './Godeps/*' -type d); do if ls $dir/*.go &> /dev/null; then $GO test ${TAGS} -v -race -covermode=atomic -coverprofile="$dir/profile.tmp" "$dir" if [ -f $dir/profile.tmp ] then cat $dir/profile.tmp | tail -n +2 >> coverage.txt rm $dir/profile.tmp fi # Stress # hack/stress-test.sh fi done $GO tool cover -func coverage.txt # To submit the test coverage result to coveralls.io, # use goveralls (https://github.com/mattn/goveralls) # goveralls -coverprofile=profile.cov -service=travis-ci wabbit-master/LICENSE0000644000000000000000000000243013163221607013322 0ustar rootrootCopyright (c) 2015, Tiago Natel de Moura All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. wabbit-master/amqp_test.go0000644000000000000000000001160613163221607014646 0ustar rootrootpackage wabbit_test import ( "fmt" "testing" "time" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest" "github.com/NeowayLabs/wabbit/amqptest/server" ) func TestBasicUsage(t *testing.T) { mockConn, err := amqptest.Dial("amqp://localhost:5672/%2f") // will fail if err == nil { t.Errorf("First Dial must fail because no fake server is running...") return } fakeServer := server.NewServer("amqp://localhost:5672/%2f") if fakeServer == nil { t.Errorf("Failed to instantiate fake server") return } err = fakeServer.Start() if err != nil { t.Errorf("Failed to start fake server") } mockConn, err = amqptest.Dial("amqp://localhost:5672/%2f") // now it works =D if err != nil { t.Error(err) return } if mockConn == nil { t.Error("Invalid mockConn") return } } func pub(conn wabbit.Conn, t *testing.T, done chan bool) { var ( publisher wabbit.Publisher confirm chan wabbit.Confirmation ) // helper function to verify publisher confirms checkConfirm := func(expected uint64) error { c := <-confirm if !c.Ack() { return fmt.Errorf("confirmation ack should be true") } if c.DeliveryTag() != expected { return fmt.Errorf("confirmation delivery tag should be %d (got: %d)", expected, c.DeliveryTag()) } return nil } channel, err := conn.Channel() if err != nil { t.Error(err) return } err = channel.ExchangeDeclare( "test", // name of the exchange "topic", // type wabbit.Option{ "durable": true, "delete": false, "internal": false, "noWait": false, }, ) if err != nil { goto PubError } err = channel.Confirm(false) if err != nil { goto PubError } confirm = channel.NotifyPublish(make(chan wabbit.Confirmation, 1)) publisher, err = amqptest.NewPublisher(conn, channel) if err != nil { goto PubError } err = publisher.Publish("test", "wabbit-test-route", []byte("msg1"), nil) if err != nil { goto PubError } err = checkConfirm(1) if err != nil { goto PubError } err = publisher.Publish("test", "wabbit-test-route", []byte("msg2"), nil) if err != nil { goto PubError } err = checkConfirm(2) if err != nil { goto PubError } err = publisher.Publish("test", "wabbit-test-route", []byte("msg3"), nil) if err != nil { goto PubError } err = checkConfirm(3) if err != nil { goto PubError } goto PubSuccess PubError: t.Error(err) PubSuccess: done <- true } func sub(conn wabbit.Conn, t *testing.T, done chan bool, bindDone chan bool) { var ( err error queue wabbit.Queue deliveries <-chan wabbit.Delivery timer <-chan time.Time deliveryDone chan bool ) channel, err := conn.Channel() if err != nil { goto SubError } err = channel.ExchangeDeclare( "test", // name of the exchange "topic", // type wabbit.Option{ "durable": true, "delete": false, "internal": false, "noWait": false, }, ) if err != nil { goto SubError } queue, err = channel.QueueDeclare( "sub-queue", // name of the queue wabbit.Option{ "durable": true, "delete": false, "exclusive": false, "noWait": false, }, ) if err != nil { goto SubError } err = channel.QueueBind( queue.Name(), // name of the queue "wabbit-test-route", // bindingKey "test", // sourceExchange wabbit.Option{ "noWait": false, }, ) if err != nil { goto SubError } bindDone <- true deliveries, err = channel.Consume( queue.Name(), // name "anyname", // consumerTag, wabbit.Option{ "noAck": false, "exclusive": false, "noLocal": false, "noWait": false, }, ) if err != nil { goto SubError } timer = time.After(5 * time.Second) deliveryDone = make(chan bool) go func() { msg1 := <-deliveries if string(msg1.Body()) != "msg1" { t.Errorf("Unexpected message: %s", string(msg1.Body())) deliveryDone <- true return } msg2 := <-deliveries if string(msg2.Body()) != "msg2" { t.Errorf("Unexpected message: %s", string(msg2.Body())) deliveryDone <- true return } msg3 := <-deliveries if string(msg3.Body()) != "msg3" { t.Errorf("Unexpected message: %s", string(msg3.Body())) deliveryDone <- true return } deliveryDone <- true }() select { case <-deliveryDone: goto SubSuccess case <-timer: err = fmt.Errorf("No data received in sub") goto SubError } SubError: t.Error(err) SubSuccess: done <- true } func TestPubSub(t *testing.T) { var err error connString := "amqp://anyhost:anyport/%2fanyVHost" fakeServer := server.NewServer(connString) err = fakeServer.Start() if err != nil { t.Error(err) return } conn, err := amqptest.Dial(connString) if err != nil { t.Error(err) return } done := make(chan bool) bindingDone := make(chan bool) go sub(conn, t, done, bindingDone) <-bindingDone // AMQP will silently discards messages with no route binding. go pub(conn, t, done) <-done <-done } wabbit-master/amqptest/0000755000000000000000000000000013163221607014154 5ustar rootrootwabbit-master/amqptest/server/0000755000000000000000000000000013163221607015462 5ustar rootrootwabbit-master/amqptest/server/confirmation.go0000644000000000000000000000031513163221607020500 0ustar rootrootpackage server type Confirmation struct { deliveryTag uint64 ack bool } func (c Confirmation) Ack() bool { return c.ack } func (c Confirmation) DeliveryTag() uint64 { return c.deliveryTag } wabbit-master/amqptest/server/server.go0000644000000000000000000000576113163221607017330 0ustar rootrootpackage server import ( "errors" "fmt" "sync" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/utils" ) const ( MaxChannels int = 2 << 10 ) var ( servers map[string]*AMQPServer mu *sync.Mutex ) func init() { servers = make(map[string]*AMQPServer) mu = &sync.Mutex{} } // AMQPServer is a fake AMQP server. It handle the fake TCP connection type AMQPServer struct { running bool amqpuri string notifyChans map[string]*utils.ErrBroadcast channels map[string][]*Channel vhost *VHost } // NewServer returns a new fake amqp server func newServer(amqpuri string) *AMQPServer { return &AMQPServer{ amqpuri: amqpuri, notifyChans: make(map[string]*utils.ErrBroadcast), channels: make(map[string][]*Channel), vhost: NewVHost("/"), } } // CreateChannel returns a new fresh channel func (s *AMQPServer) CreateChannel(connid string) (wabbit.Channel, error) { if _, ok := s.channels[connid]; !ok { s.channels[connid] = make([]*Channel, 0, MaxChannels) } channels := s.channels[connid] if len(channels) >= MaxChannels { return nil, fmt.Errorf("Channel quota exceeded, Wabbit"+ " supports only %d fake channels for tests.", MaxChannels) } ch := NewChannel(s.vhost) channels = append(channels, ch) s.channels[connid] = channels return ch, nil } // Start a new AMQP server fake-listening on host:port func (s *AMQPServer) Start() error { mu.Lock() defer mu.Unlock() s.running = true return nil } // Stop the fake server func (s *AMQPServer) Stop() error { mu.Lock() defer mu.Unlock() s.running = false for _, c := range s.notifyChans { c.Write(utils.NewError( utils.ChannelError, "channel/connection is not open", false, false, )) } s.notifyChans = make(map[string]*utils.ErrBroadcast) return nil } // NewServer starts a new fake server func NewServer(amqpuri string) *AMQPServer { var amqpServer *AMQPServer mu.Lock() defer mu.Unlock() amqpServer = servers[amqpuri] if amqpServer == nil { amqpServer = newServer(amqpuri) servers[amqpuri] = amqpServer } return amqpServer } func (s *AMQPServer) addNotify(connID string, nchan *utils.ErrBroadcast) { mu.Lock() defer mu.Unlock() s.notifyChans[connID] = nchan } func (s *AMQPServer) delNotify(connID string) { mu.Lock() defer mu.Unlock() delete(s.notifyChans, connID) } func getServer(amqpuri string) (*AMQPServer, error) { mu.Lock() defer mu.Unlock() amqpServer := servers[amqpuri] if amqpServer == nil || amqpServer.running == false { return nil, errors.New("Network unreachable") } return amqpServer, nil } func Connect(amqpuri string, connID string, errBroadcast *utils.ErrBroadcast) (*AMQPServer, error) { amqpServer, err := getServer(amqpuri) if err != nil { return nil, err } amqpServer.addNotify(connID, errBroadcast) return amqpServer, nil } func Close(amqpuri string, connID string) error { amqpServer, err := getServer(amqpuri) if err != nil { return errors.New("Failed to close connection") } amqpServer.delNotify(connID) return nil } wabbit-master/amqptest/server/queue.go0000644000000000000000000000063613163221607017142 0ustar rootrootpackage server import "github.com/NeowayLabs/wabbit" const ( QueueMaxLen = 2 << 8 ) type Queue struct { name string data chan wabbit.Delivery } func NewQueue(name string) *Queue { return &Queue{ name: name, data: make(chan wabbit.Delivery, QueueMaxLen), } } func (q *Queue) Consumers() int { return 0 } func (q *Queue) Name() string { return q.name } func (q *Queue) Messages() int { return 0 } wabbit-master/amqptest/server/exchange.go0000644000000000000000000000265113163221607017577 0ustar rootrootpackage server import "fmt" type Exchange interface { route(route string, d *Delivery) error addBinding(route string, q *Queue) delBinding(route string) } type TopicExchange struct { name string bindings map[string]*Queue } func NewTopicExchange(name string) *TopicExchange { return &TopicExchange{ name: name, bindings: make(map[string]*Queue), } } func (t *TopicExchange) addBinding(route string, q *Queue) { t.bindings[route] = q } func (t *TopicExchange) delBinding(route string) { delete(t.bindings, route) } func (t *TopicExchange) route(route string, d *Delivery) error { for bname, q := range t.bindings { if topicMatch(bname, route) { q.data <- d return nil } } // The route doesnt match any binding, then will be discarded return nil } type DirectExchange struct { name string bindings map[string]*Queue } func NewDirectExchange(name string) *DirectExchange { return &DirectExchange{ name: name, bindings: make(map[string]*Queue), } } func (d *DirectExchange) addBinding(route string, q *Queue) { if d.bindings == nil { d.bindings = make(map[string]*Queue) } d.bindings[route] = q } func (d *DirectExchange) delBinding(route string) { delete(d.bindings, route) } func (d *DirectExchange) route(route string, delivery *Delivery) error { if q, ok := d.bindings[route]; ok { q.data <- delivery return nil } return fmt.Errorf("No bindings to route: %s", route) } wabbit-master/amqptest/server/channel.go0000644000000000000000000001427013163221607017425 0ustar rootrootpackage server import ( "fmt" "os" "sync" "sync/atomic" "github.com/NeowayLabs/wabbit" "github.com/streadway/amqp" ) type ( Channel struct { *VHost unacked []unackData muUnacked *sync.RWMutex consumers map[string]consumer muConsumer *sync.RWMutex _ uint32 deliveryTagCounter uint64 confirm bool publishListeners []chan wabbit.Confirmation muPublishListeners *sync.RWMutex } unackData struct { d wabbit.Delivery q *Queue } consumer struct { tag string deliveries chan wabbit.Delivery done chan bool } ) var consumerSeq uint64 func uniqueConsumerTag() string { return fmt.Sprintf("ctag-%s-%d", os.Args[0], atomic.AddUint64(&consumerSeq, 1)) } func NewChannel(vhost *VHost) *Channel { c := Channel{ VHost: vhost, unacked: make([]unackData, 0, QueueMaxLen), muUnacked: &sync.RWMutex{}, muConsumer: &sync.RWMutex{}, consumers: make(map[string]consumer), muPublishListeners: &sync.RWMutex{}, } return &c } func (ch *Channel) Confirm(noWait bool) error { ch.confirm = true return nil } func (ch *Channel) NotifyPublish(confirm chan wabbit.Confirmation) chan wabbit.Confirmation { aux := make(chan wabbit.Confirmation, 2<<8) ch.muPublishListeners.Lock() ch.publishListeners = append(ch.publishListeners, aux) ch.muPublishListeners.Unlock() // aux is set to the maximum size of a queue: 512. // In theory it is possible that a publisher could deliver >512 messages // on a channel by sending to multiple queues, in which case Publish() // will block trying to send confirmations. However this is a pathological // case which can be ignored since there should be an attached listener // on the other end of the confirm channel. In any case, a buffered queue, // while seemingly inefficient is a good enough solution to make sure // Publish() doesn't block. go func() { for c := range aux { confirm <- c } close(confirm) }() return confirm } func (ch *Channel) Publish(exc, route string, msg []byte, opt wabbit.Option) error { hdrs, _ := opt["headers"].(amqp.Table) d := NewDelivery(ch, msg, atomic.AddUint64(&ch.deliveryTagCounter, 1), wabbit.Option(hdrs)) err := ch.VHost.Publish(exc, route, d, nil) if err != nil { return err } if ch.confirm { confirm := Confirmation{ch.deliveryTagCounter, true} for _, l := range ch.publishListeners { l <- confirm } } return nil } // Consume starts a fake consumer of queue func (ch *Channel) Consume(queue, consumerName string, _ wabbit.Option) (<-chan wabbit.Delivery, error) { var ( c consumer ) if consumerName == "" { consumerName = uniqueConsumerTag() } c = consumer{ tag: consumerName, deliveries: make(chan wabbit.Delivery), done: make(chan bool), } ch.muConsumer.RLock() if c2, found := ch.consumers[consumerName]; found { c2.done <- true } ch.consumers[consumerName] = c ch.muConsumer.RUnlock() q, ok := ch.queues[queue] if !ok { return nil, fmt.Errorf("Unknown queue '%s'", queue) } go func() { for { select { case <-c.done: close(c.deliveries) return case d := <-q.data: // since we keep track of unacked messages for // the channel, we need to rebind the delivery // to the consumer channel. d = NewDelivery(ch, d.Body(), d.DeliveryTag(), d.Headers()) ch.addUnacked(d, q) // sub-select required for cases when // client attempts to close the channel // concurrently with re-enqueues of messages select { case c.deliveries <- d: case <-c.done: close(c.deliveries) return } } } }() return c.deliveries, nil } func (ch *Channel) addUnacked(d wabbit.Delivery, q *Queue) { ch.muUnacked.Lock() defer ch.muUnacked.Unlock() ch.unacked = append(ch.unacked, unackData{d, q}) } func (ch *Channel) enqueueUnacked() { ch.muUnacked.Lock() defer ch.muUnacked.Unlock() for _, ud := range ch.unacked { ud.q.data <- ud.d } ch.unacked = make([]unackData, 0, QueueMaxLen) } func (ch *Channel) Ack(tag uint64, multiple bool) error { var ( pos int ud unackData ) if !multiple { ch.muUnacked.Lock() defer ch.muUnacked.Unlock() found := false for pos, ud = range ch.unacked { if ud.d.DeliveryTag() == tag { found = true break } } if !found { return fmt.Errorf("Delivery tag %d not found", tag) } ch.unacked = ch.unacked[:pos+copy(ch.unacked[pos:], ch.unacked[pos+1:])] } else { ackMessages := make([]uint64, 0, QueueMaxLen) ch.muUnacked.Lock() found := false for _, ud = range ch.unacked { udTag := ud.d.DeliveryTag() if udTag <= tag { found = true ackMessages = append(ackMessages, udTag) } } ch.muUnacked.Unlock() if !found { return fmt.Errorf("Delivery tag %d not found", tag) } for _, udTag := range ackMessages { ch.Ack(udTag, false) } } return nil } func (ch *Channel) Nack(tag uint64, multiple bool, requeue bool) error { var ( pos int ud unackData ) if !multiple { found := false for pos, ud = range ch.unacked { if ud.d.DeliveryTag() == tag { found = true break } } if !found { return fmt.Errorf("Delivery tag %d not found", tag) } if requeue { ud.q.data <- ud.d } ch.muUnacked.Lock() ch.unacked = ch.unacked[:pos+copy(ch.unacked[pos:], ch.unacked[pos+1:])] ch.muUnacked.Unlock() } else { nackMessages := make([]uint64, 0, QueueMaxLen) for _, ud = range ch.unacked { udTag := ud.d.DeliveryTag() if udTag <= tag { nackMessages = append(nackMessages, udTag) } } for _, udTag := range nackMessages { ch.Nack(udTag, false, requeue) } } return nil } func (ch *Channel) Reject(tag uint64, requeue bool) error { return ch.Nack(tag, false, requeue) } func (ch *Channel) Close() error { ch.muConsumer.Lock() defer ch.muConsumer.Unlock() for _, consumer := range ch.consumers { consumer.done <- true } ch.consumers = make(map[string]consumer) // enqueue shall happens only after every consumer of this channel // has stopped. ch.enqueueUnacked() ch.muPublishListeners.Lock() defer ch.muPublishListeners.Unlock() for _, c := range ch.publishListeners { close(c) } ch.publishListeners = []chan wabbit.Confirmation{} return nil } wabbit-master/amqptest/server/utils_test.go0000644000000000000000000000165013163221607020212 0ustar rootrootpackage server import "testing" func matchSuccess(t *testing.T, b, r string, expected bool) { res := topicMatch(b, r) if res != expected { t.Errorf("Route '%s' and bind '%s' returns %v", b, r, res) } } func TestTopicMatch(t *testing.T) { for _, pair := range []struct { b, r string ok bool }{ // OK {"a", "a", true}, {"ab", "ab", true}, {"#", "a", true}, {"#", "aa", true}, {"#", "aaaaaaaaaaaaaaaaaaaaaaaaa", true}, {"#.a", "bbbb.a", true}, {"teste#", "testeb", true}, {"teste#", "testebbbbbbbbbbb", true}, {"*.*", "a.b", true}, {"*a", "aa", true}, {"*aa", "baa", true}, {"a*.b*", "ab.ba", true}, {"maps.layer.stored", "maps.layer.stored", true}, {"maps.layer.#", "maps.layer.bleh", true}, // FAIL {"", "a", false}, {"a", "b", false}, {"*", "aa", false}, {"a*", "aaa", false}, {"maps.layer.*", "maps.layer.stored", false}, } { matchSuccess(t, pair.b, pair.r, pair.ok) } } wabbit-master/amqptest/server/vhost.go0000644000000000000000000000650113163221607017156 0ustar rootrootpackage server import ( "fmt" "github.com/NeowayLabs/wabbit" ) // VHost is a fake AMQP virtual host type VHost struct { name string exchanges map[string]Exchange queues map[string]*Queue } // NewVHost create a new fake AMQP Virtual Host func NewVHost(name string) *VHost { vh := VHost{ name: name, queues: make(map[string]*Queue), exchanges: make(map[string]Exchange), } vh.createDefaultExchanges() return &vh } func (v *VHost) createDefaultExchanges() { exchs := make(map[string]Exchange) exchs["amq.topic"] = &TopicExchange{} exchs["amq.direct"] = &DirectExchange{} exchs["topic"] = &TopicExchange{} exchs["direct"] = &DirectExchange{} exchs[""] = &DirectExchange{ name: "amq.direct", } v.exchanges = exchs } func (v *VHost) Cancel(consumer string, noWait bool) error { return nil } // Qos isn't implemented in the fake server func (v *VHost) Qos(prefetchCount, prefetchSize int, global bool) error { // do nothing. It's a implementation-specific tuning return nil } func (v *VHost) ExchangeDeclare(name, kind string, opt wabbit.Option) error { if _, ok := v.exchanges[name]; ok { // TODO: We need review this. If the application is trying to re-create an exchange // using other options we shall not return NIL because this indicates success, // but we didn't declared anything. // The AMQP 0.9.1 spec says nothing about that. It only says that AMQP uses the // "declare" concept instead of the "create" concept. If something is already // declared it's no problem... return nil } switch kind { case "topic": v.exchanges[name] = NewTopicExchange(name) case "direct": v.exchanges[name] = NewDirectExchange(name) default: return fmt.Errorf("Invalid exchange type: %s", kind) } return nil } func (v *VHost) QueueDeclare(name string, args wabbit.Option) (wabbit.Queue, error) { if q, ok := v.queues[name]; ok { return q, nil } q := NewQueue(name) v.queues[name] = q err := v.QueueBind(name, name, "", nil) if err != nil { return nil, err } return q, nil } func (v *VHost) QueueDelete(name string, args wabbit.Option) (int, error) { delete(v.queues, name) return 0, nil } func (v *VHost) QueueBind(name, key, exchange string, _ wabbit.Option) error { var ( exch Exchange q *Queue ok bool ) if exch, ok = v.exchanges[exchange]; !ok { return fmt.Errorf("Unknown exchange '%s'", exchange) } if q, ok = v.queues[name]; !ok { return fmt.Errorf("Unknown queue '%s'", name) } exch.addBinding(key, q) return nil } func (v *VHost) QueueUnbind(name, key, exchange string, _ wabbit.Option) error { var ( exch Exchange ok bool ) if exch, ok = v.exchanges[exchange]; !ok { return fmt.Errorf("Unknown exchange '%s'", exchange) } if _, ok = v.queues[name]; !ok { return fmt.Errorf("Unknown queue '%s'", name) } exch.delBinding(key) return nil } // Publish push a new message to queue data channel. // The queue data channel is a buffered channel of length `QueueMaxLen`. If // the queue is full, this method will block until some messages are consumed. func (v *VHost) Publish(exc, route string, d *Delivery, _ wabbit.Option) error { var ( exch Exchange ok bool err error ) if exch, ok = v.exchanges[exc]; !ok { return fmt.Errorf("Unknow exchange '%s'", exc) } err = exch.route(route, d) if err != nil { return err } return nil } wabbit-master/amqptest/server/delivery.go0000644000000000000000000000174713163221607017645 0ustar rootrootpackage server import "github.com/NeowayLabs/wabbit" type ( // Delivery is an interface to delivered messages Delivery struct { data []byte headers wabbit.Option tag uint64 consumerTag string originalRoute string channel *Channel } ) func NewDelivery(ch *Channel, data []byte, tag uint64, hdrs wabbit.Option) *Delivery { return &Delivery{ data: data, headers: hdrs, channel: ch, tag: tag, } } func (d *Delivery) Ack(multiple bool) error { return d.channel.Ack(d.tag, multiple) } func (d *Delivery) Nack(multiple, requeue bool) error { return d.channel.Nack(d.tag, multiple, requeue) } func (d *Delivery) Reject(requeue bool) error { return d.channel.Nack(d.tag, false, requeue) } func (d *Delivery) Body() []byte { return d.data } func (d *Delivery) Headers() wabbit.Option { return d.headers } func (d *Delivery) DeliveryTag() uint64 { return d.tag } func (d *Delivery) ConsumerTag() string { return d.consumerTag } wabbit-master/amqptest/server/utils.go0000644000000000000000000000267413163221607017162 0ustar rootrootpackage server import "strings" // matchs r2 against r1 following the AMQP rules for topic routing keys func topicMatch(r1, r2 string) bool { var match bool bparts := strings.Split(r1, ".") rparts := strings.Split(r2, ".") if len(rparts) > len(bparts) { return false } outer: for i := 0; i < len(bparts); i++ { bp := bparts[i] rp := rparts[i] if len(bp) == 0 { return false } var bsi, rsi int for rsi < len(rp) { // fmt.Printf("Testing '%c' and '%c'\n", bp[bsi], rp[rsi]) // The char '#' matchs none or more chars (everything that is on rp[rsi]) // next char, move on if bp[bsi] == '#' { match = true continue outer } else if bp[bsi] == '*' { // The '*' matchs only one character, then if it's the last char of binding part // and isn't the last char of rp, then surely it don't match. if bsi == len(bp)-1 && rsi < len(rp)-1 { match = false break outer } match = true if bsi < len(bp)-1 { bsi++ } rsi++ } else if bp[bsi] == rp[rsi] { // if it's the last char of binding part and it isn't an '*' or '#', // and it isn't the last char of rp, then we can stop here // because sure that route don't match the binding if bsi == len(bp)-1 && rsi < len(rp)-1 { match = false break outer } if bsi < len(bp)-1 { bsi++ } rsi++ match = true } else { match = false break outer } } } return match } wabbit-master/amqptest/server/vhost_test.go0000644000000000000000000000601413163221607020214 0ustar rootrootpackage server import ( "testing" "github.com/NeowayLabs/wabbit" ) func TestVHostWithDefaults(t *testing.T) { vh := NewVHost("/") if vh.name != "/" { t.Errorf("Invalid broker name: %s", vh.name) } if len(vh.exchanges) < 5 || vh.exchanges[""] == nil || vh.exchanges["amq.direct"] == nil || vh.exchanges["amq.topic"] == nil { t.Errorf("VHost created without the required exchanges specified by amqp 0.9.1") } } func TestQueueDeclare(t *testing.T) { vh := NewVHost("/") q, err := vh.QueueDeclare("test-queue", nil) if err != nil { t.Error(err) } if q.Name() != "test-queue" { t.Errorf("Invalid queue name") } if len(vh.queues) != 1 || vh.queues["test-queue"] != q { t.Errorf("Failed to declare queue") } if q.Messages() != 0 || q.Consumers() != 0 { t.Errorf("Invalid number of messages or consumers") } nwExchange, ok := vh.exchanges[""].(*DirectExchange) if !ok { t.Errorf("Exchange neoway not created") return } if len(nwExchange.bindings) != 1 { t.Errorf("Binding not created") return } } func TestBasicExchangeDeclare(t *testing.T) { vh := NewVHost("/") err := vh.ExchangeDeclare("neoway", "amq.direct", nil) if err == nil { t.Errorf("The exchange type correct name is direct") return } err = vh.ExchangeDeclare("neoway", "direct", nil) if err != nil { t.Error(err) return } if len(vh.exchanges) != 6 { t.Errorf("Exchange not properly created: %d", len(vh.exchanges)) return } } func TestQueueBind(t *testing.T) { vh := NewVHost("/") err := vh.ExchangeDeclare("neoway", "direct", nil) if err != nil { t.Error(err) return } q, err := vh.QueueDeclare("queue-test", nil) if err != nil { t.Error(err) return } if q.Name() != "queue-test" { t.Errorf("Something wrong declaring queue") return } err = vh.QueueBind("queue-test", "process.data", "neoway", nil) if err != nil { t.Error(err) return } nwExchange, ok := vh.exchanges["neoway"].(*DirectExchange) if !ok { t.Errorf("Exchange neoway not created") return } if len(nwExchange.bindings) != 1 { t.Errorf("Binding not created") return } err = nwExchange.route("process.data", NewDelivery(&Channel{}, []byte{}, 1, wabbit.Option{})) if err != nil { t.Error(err) return } } func TestBasicPublish(t *testing.T) { vh := NewVHost("/") err := vh.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := vh.QueueDeclare("data", nil) if err != nil { t.Error(err) return } if q.Name() != "data" { t.Errorf("Invalid queue name") return } err = vh.QueueBind("data", "process.data", "neoway", nil) if err != nil { t.Error(err) return } err = vh.Publish("neoway", "process.data", NewDelivery(&Channel{}, []byte("teste"), 1, wabbit.Option{}), nil) if err != nil { t.Error(err) return } serverQueue, ok := q.(*Queue) if !ok { t.Errorf("Queue isn't of type *server.Queue") return } data := <-serverQueue.data if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } } wabbit-master/amqptest/server/channel_test.go0000644000000000000000000003043713163221607020467 0ustar rootrootpackage server import ( "testing" "time" ) func TestBasicConsumer(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } } func TestWorkerQueue(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("", q.Name(), []byte("teste"), nil) if err != nil { t.Error(err) return } data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified queue") return } } func TestUnackedMessagesArentLost(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } done := make(chan bool) go func() { data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } // Closing the channel without ack'ing // The message MUST be enqueued by vhost ch.Close() done <- true }() select { case <-done: } // create another channel and get the same data back ch2 := NewChannel(vh) err = ch2.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q2, err := ch2.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch2.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries2, err := ch2.Consume( q2.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } done = make(chan bool) go func() { data := <-deliveries2 if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } done <- true }() select { case <-done: } } func TestAckedMessagesAreCommited(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } done := make(chan bool) go func() { data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } data.Ack(false) done <- true }() select { case <-done: } // Closing the old channel, this should reenqueue everything not ack'ed ch.Close() // create another channel and get the same data back ch2 := NewChannel(vh) err = ch2.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q2, err := ch2.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch2.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries2, err := ch2.Consume( q2.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } timer := time.After(2 * time.Second) go func() { data := <-deliveries2 t.Errorf("Data ack'ed delivered again: %s", string(data.Body())) panic("never reach here. Message was ack'ed") }() select { case <-timer: } } func TestPublishThenConsumeAck(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } err = ch.Close() if err != nil { t.Error(err) return } // start consumer ch2 := NewChannel(vh) q, err = ch2.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } deliveries, err := ch2.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } done := make(chan bool) go func() { data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to receive message from published specified route") return } data.Ack(false) done <- true }() select { case <-done: } err = ch2.Close() if err != nil { t.Error(err) return } } func TestNAckedMessagesAreRequeued(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } done := make(chan bool) go func() { data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } data.Nack(false, true) done <- true }() select { case <-done: } // Closing the old channel, this should reenqueue everything not ack'ed ch.Close() // create another channel and get the same data back ch2 := NewChannel(vh) err = ch2.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q2, err := ch2.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch2.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries2, err := ch2.Consume( q2.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } timer := time.After(2 * time.Second) go func() { data := <-deliveries2 if string(data.Body()) != "teste" { t.Error("wrong nacked message") } done <- true }() select { case <-done: case <-timer: t.Errorf("Data nack'ed but not redelivered") } } func TestNAckedMessagesAreRejectedWhenRequested(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } done := make(chan bool) go func() { data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } data.Nack(false, false) done <- true }() select { case <-done: } // Closing the old channel, this should reenqueue everything not ack'ed ch.Close() // create another channel and get the same data back ch2 := NewChannel(vh) err = ch2.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q2, err := ch2.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch2.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries2, err := ch2.Consume( q2.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } timer := time.After(2 * time.Second) go func() { <-deliveries2 t.Error("Message shall not be redelivered") done <- true }() select { case <-done: case <-timer: } } // Reject func TestRejectedMessagesAreRequeuedWhenRequested(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } done := make(chan bool) go func() { data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } data.Reject(true) done <- true }() select { case <-done: } // Closing the old channel, this should reenqueue everything not ack'ed ch.Close() // create another channel and get the same data back ch2 := NewChannel(vh) err = ch2.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q2, err := ch2.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch2.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries2, err := ch2.Consume( q2.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } timer := time.After(2 * time.Second) go func() { data := <-deliveries2 if string(data.Body()) != "teste" { t.Error("wrong nacked message") } done <- true }() select { case <-done: case <-timer: t.Errorf("Data nack'ed but not redelivered") } } func TestRejectedMessagesAreRejectedWhenRequested(t *testing.T) { vh := NewVHost("/") ch := NewChannel(vh) err := ch.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q, err := ch.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries, err := ch.Consume( q.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } err = ch.Publish("neoway", "process.data", []byte("teste"), nil) if err != nil { t.Error(err) return } done := make(chan bool) go func() { data := <-deliveries if string(data.Body()) != "teste" { t.Errorf("Failed to publish message to specified route") return } data.Reject(false) done <- true }() select { case <-done: } // Closing the old channel, this should reenqueue everything not ack'ed ch.Close() // create another channel and get the same data back ch2 := NewChannel(vh) err = ch2.ExchangeDeclare("neoway", "topic", nil) if err != nil { t.Error(err) return } q2, err := ch2.QueueDeclare("data-queue", nil) if err != nil { t.Error(err) return } err = ch2.QueueBind("data-queue", "process.data", "neoway", nil) if err != nil { t.Error(err) return } deliveries2, err := ch2.Consume( q2.Name(), "tag-teste", nil, ) if err != nil { t.Error(err) return } timer := time.After(2 * time.Second) go func() { <-deliveries2 t.Error("Message shall not be redelivered") done <- true }() select { case <-done: case <-timer: } } wabbit-master/amqptest/dial.go0000644000000000000000000000705613163221607015424 0ustar rootrootpackage amqptest import ( "sync" "time" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest/server" "github.com/NeowayLabs/wabbit/utils" "github.com/pborman/uuid" ) const ( // 1 second defaultReconnectDelay = 1 ) // Conn is the fake AMQP connection type Conn struct { amqpuri string isConnected bool connID string errSpread *utils.ErrBroadcast errChan chan wabbit.Error defErrDone chan bool mu *sync.Mutex hasAutoRedial bool amqpServer *server.AMQPServer dialFn func() error } // Dial mock the connection dialing to rabbitmq and // returns the established connection or error if something goes wrong func Dial(amqpuri string) (*Conn, error) { conn := &Conn{ amqpuri: amqpuri, errSpread: utils.NewErrBroadcast(), errChan: make(chan wabbit.Error), defErrDone: make(chan bool), mu: &sync.Mutex{}, } conn.errSpread.Add(conn.errChan) conn.dialFn = func() error { var err error conn.connID = uuid.New() conn.amqpServer, err = server.Connect(amqpuri, conn.connID, conn.errSpread) if err != nil { return err } // concurrent access with Close method conn.mu.Lock() conn.isConnected = true conn.mu.Unlock() // by default, we discard any errors // send something to defErrDone to destroy // this goroutine and start consume the errors go func() { for { select { case <-conn.errChan: case <-conn.defErrDone: conn.mu.Lock() if conn.hasAutoRedial { conn.mu.Unlock() return } conn.mu.Unlock() // Drain the errChan channel before // the exit. for { if _, ok := <-conn.errChan; !ok { return } } } } }() return nil } err := conn.dialFn() if err != nil { return nil, err } return conn, nil } // NotifyClose publishs notifications about server or client errors in the given channel func (conn *Conn) NotifyClose(c chan wabbit.Error) chan wabbit.Error { conn.errSpread.Add(c) return c } // AutoRedial mock the reconnection faking a delay of 1 second func (conn *Conn) AutoRedial(outChan chan wabbit.Error, done chan bool) { if !conn.hasAutoRedial { conn.mu.Lock() conn.hasAutoRedial = true conn.mu.Unlock() conn.defErrDone <- true } go func() { var err wabbit.Error var attempts uint select { case amqpErr := <-conn.errChan: err = amqpErr if amqpErr == nil { // Gracefull connection close return } lattempts: // send the error to client outChan <- err if attempts > 60 { attempts = 0 } // Wait n Seconds where n == attempts... time.Sleep(time.Duration(attempts) * time.Second) connErr := conn.dialFn() if connErr != nil { attempts++ goto lattempts } // enabled AutoRedial on the new connection conn.AutoRedial(outChan, done) done <- true return } }() } // Close the fake connection func (conn *Conn) Close() error { conn.mu.Lock() defer conn.mu.Unlock() if conn.isConnected { // Disconnect from the server. if err := server.Close(conn.amqpuri, conn.connID); err != nil { return err } conn.isConnected = false conn.amqpServer = nil } // enables AutoRedial to gracefully shutdown // This isn't wabbit stuff. It's the streadway/amqp way of notify the shutdown if conn.hasAutoRedial { conn.errSpread.Write(nil) } else { conn.errSpread.Delete(conn.errChan) close(conn.errChan) conn.defErrDone <- true } return nil } // Channel creates a new fake channel func (conn *Conn) Channel() (wabbit.Channel, error) { return conn.amqpServer.CreateChannel(conn.connID) } wabbit-master/amqptest/queue.go0000644000000000000000000000050213163221607015624 0ustar rootrootpackage amqptest type Queue struct { name string consumers int messages int } func NewQueue(name string) *Queue { return &Queue{ name: name, } } func (q *Queue) Name() string { return q.name } func (q *Queue) Messages() int { return q.messages } func (q *Queue) Consumers() int { return q.consumers } wabbit-master/amqptest/publisher.go0000644000000000000000000000117113163221607016500 0ustar rootrootpackage amqptest import "github.com/NeowayLabs/wabbit" type Publisher struct { channel wabbit.Publisher conn wabbit.Conn } func NewPublisher(conn wabbit.Conn, channel wabbit.Channel) (*Publisher, error) { var err error if channel == nil { channel, err = conn.Channel() if err != nil { return nil, err } } return &Publisher{ conn: conn, channel: channel, }, nil } func (pb *Publisher) Publish(exc string, route string, message []byte, opt wabbit.Option) error { err := pb.channel.Publish( exc, // publish to an exchange route, // routing to 0 or more queues message, opt, ) return err } wabbit-master/amqptest/dial_test.go0000644000000000000000000000612613163221607016460 0ustar rootrootpackage amqptest import ( "errors" "fmt" "testing" "time" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest/server" "github.com/pborman/uuid" ) var rabbitmqPort = "35672" // WaitOK blocks until rabbitmq can accept connections on // :5672 func waitRabbitOK(amqpuri string) error { var err error var counter = 0 dial: if counter > 120 { panic("Impossible to connect to rabbitmq") } _, err = Dial(amqpuri) if err != nil { time.Sleep(500 * time.Millisecond) counter++ goto dial } return nil } // TestDial test a simple connection to rabbitmq. // If the rabbitmq disconnects will not be tested here! func TestDial(t *testing.T) { amqpuri := "amqp://guest:guest@localhost:35672/%2f" // Should fail conn, err := Dial(amqpuri) if err == nil { t.Error("No backend started... Should fail") return } server := server.NewServer(amqpuri) server.Start() err = waitRabbitOK(amqpuri) if err != nil { t.Error(err) return } conn, err = Dial(amqpuri) if err != nil || conn == nil { t.Error(err) return } server.Stop() } func TestAutoRedial(t *testing.T) { var err error amqpuri := "amqp://guest:guest@localhost:35672/%2f" server := server.NewServer(amqpuri) server.Start() defer server.Stop() err = waitRabbitOK(amqpuri) if err != nil { t.Error(err) return } conn, err := Dial(amqpuri) if err != nil { t.Error(err) return } defer conn.Close() redialErrors := make(chan wabbit.Error) done := make(chan bool) conn.AutoRedial(redialErrors, done) // required goroutine to consume connection error messages go func() { for { // discards the connection errors <-redialErrors } }() server.Stop() // concurrently starts the rabbitmq after 1 second go func() { time.Sleep(10 * time.Second) server.Start() }() select { case <-time.After(20 * time.Second): err = errors.New("Timeout exceeded. AMQP reconnect failed") case <-done: err = nil } if err != nil { t.Errorf("Client doesn't reconnect in 3 seconds: %s", err.Error()) return } } func TestConnMock(t *testing.T) { var conn wabbit.Conn // rabbitmq.Conn satisfies wabbit.Conn interface conn = &Conn{} if conn == nil { t.Error("Maybe wabbit.Conn interface does not mock amqp.Conn correctly") } } func TestConnCloseBeforeServerStop(t *testing.T) { tests := []struct { name string sleep time.Duration }{ { name: "10 ms sleep before server.Stop()", sleep: 10 * time.Millisecond, }, { name: "100 ms sleep before server.Stop()", sleep: 100 * time.Millisecond, }, { name: "1000 ms sleep before server.Stop()", sleep: time.Second, }, } for _, tc := range tests { tc := tc // capture range variable. t.Run(tc.name, func(t *testing.T) { // Start a server. uri := fmt.Sprintf("amqp://guest:guest@localhost:35672/%s", uuid.New()) server := server.NewServer(uri) server.Start() defer server.Stop() // Dial to the server. conn, err := Dial(uri) if err != nil { t.Fatalf("Dial(): %v", err) } conn.Close() // Sleep before stopping the server. time.Sleep(tc.sleep) }) } } wabbit-master/_examples/0000755000000000000000000000000013163221607014273 5ustar rootrootwabbit-master/_examples/simple-consumer/0000755000000000000000000000000013163221607017415 5ustar rootrootwabbit-master/_examples/simple-consumer/exchange.go0000644000000000000000000001030513163221607021525 0ustar rootroot// This example declares a durable Exchange, an ephemeral (auto-delete) Queue, // binds the Queue to the Exchange with a binding key, and consumes every // message published to that Exchange with that routing key. // package main import ( "flag" "fmt" "log" "time" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqp" ) var ( uri = flag.String("uri", "amqp://guest:guest@localhost:5672/", "AMQP URI") exchange = flag.String("exchange", "test-exchange", "Durable, non-auto-deleted AMQP exchange name") exchangeType = flag.String("exchange-type", "direct", "Exchange type - direct|fanout|topic|x-custom") queue = flag.String("queue", "test-queue", "Ephemeral AMQP queue name") bindingKey = flag.String("key", "test-key", "AMQP binding key") consumerTag = flag.String("consumer-tag", "simple-consumer", "AMQP consumer tag (should not be blank)") lifetime = flag.Duration("lifetime", 5*time.Second, "lifetime of process before shutdown (0s=infinite)") ) func init() { flag.Parse() } func main() { c, err := NewConsumer(*uri, *exchange, *exchangeType, *queue, *bindingKey, *consumerTag) if err != nil { log.Fatalf("%s", err) } if *lifetime > 0 { log.Printf("running for %s", *lifetime) time.Sleep(*lifetime) } else { log.Printf("running forever") select {} } log.Printf("shutting down") if err := c.Shutdown(); err != nil { log.Fatalf("error during shutdown: %s", err) } } type Consumer struct { conn wabbit.Conn channel wabbit.Channel tag string done chan error } func NewConsumer(amqpURI, exchange, exchangeType, queueName, key, ctag string) (*Consumer, error) { c := &Consumer{ conn: nil, channel: nil, tag: ctag, done: make(chan error), } var err error log.Printf("dialing %q", amqpURI) c.conn, err = amqp.Dial(amqpURI) if err != nil { return nil, fmt.Errorf("Dial: %s", err) } go func() { fmt.Printf("closing: %s", <-c.conn.NotifyClose(make(chan wabbit.Error))) }() log.Printf("got Connection, getting Channel") c.channel, err = c.conn.Channel() if err != nil { return nil, fmt.Errorf("Channel: %s", err) } log.Printf("got Channel, declaring Exchange (%q)", exchange) if err = c.channel.ExchangeDeclare( exchange, // name of the exchange exchangeType, // type wabbit.Option{ "durable": true, "delete": false, "internal": false, "noWait": false, }, ); err != nil { return nil, fmt.Errorf("Exchange Declare: %s", err) } log.Printf("declared Exchange, declaring Queue %q", queueName) queue, err := c.channel.QueueDeclare( queueName, // name of the queue wabbit.Option{ "durable": true, "delete": false, "exclusive": false, "noWait": false, }, ) if err != nil { return nil, fmt.Errorf("Queue Declare: %s", err) } log.Printf("declared Queue (%q %d messages, %d consumers), binding to Exchange (key %q)", queue.Name(), queue.Messages(), queue.Consumers(), key) if err = c.channel.QueueBind( queue.Name(), // name of the queue key, // bindingKey exchange, // sourceExchange wabbit.Option{ "noWait": false, }, ); err != nil { return nil, fmt.Errorf("Queue Bind: %s", err) } log.Printf("Queue bound to Exchange, starting Consume (consumer tag %q)", c.tag) deliveries, err := c.channel.Consume( queue.Name(), // name c.tag, // consumerTag, wabbit.Option{ "noAck": false, "exclusive": false, "noLocal": false, "noWait": false, }, ) if err != nil { return nil, fmt.Errorf("Queue Consume: %s", err) } go handle(deliveries, c.done) return c, nil } func (c *Consumer) Shutdown() error { // will close() the deliveries channel if err := c.channel.Cancel(c.tag, true); err != nil { return fmt.Errorf("Consumer cancel failed: %s", err) } if err := c.conn.Close(); err != nil { return fmt.Errorf("AMQP connection close error: %s", err) } defer log.Printf("AMQP shutdown OK") // wait for handle() to exit return <-c.done } func handle(deliveries <-chan wabbit.Delivery, done chan error) { for d := range deliveries { log.Printf( "got %dB delivery: [%v] %q", len(d.Body()), d.DeliveryTag(), d.Body(), ) d.Ack(false) } log.Printf("handle: deliveries channel closed") done <- nil } wabbit-master/_examples/README.md0000644000000000000000000000027413163221607015555 0ustar rootroot### How to use ? **Publish** - [Worker Queue](publisher/worker_queue.go) - [Generic Exchange](publisher/exchange.go) **Consumer** - [Generic Exchange](simple-consumer/exchange.go) wabbit-master/_examples/publisher/0000755000000000000000000000000013163221607016270 5ustar rootrootwabbit-master/_examples/publisher/worker_queue.go0000644000000000000000000000514213163221607021336 0ustar rootrootpackage main import ( "flag" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqp" "log" ) var ( uri = flag.String("uri", "amqp://guest:guest@localhost:5672/", "AMQP URI") queueName = flag.String("queue", "test-queue", "Ephemeral AMQP queue name") body = flag.String("body", "body test", "Body of message") reliable = flag.Bool("reliable", true, "Wait for the publisher confirmation before exiting") ) func init() { flag.Parse() } func main() { publish(*uri, *queueName, *body, *reliable) } func publish(uri string, queueName string, body string, reliable bool) { log.Println("[-] Connecting to", uri) connection, err := connect(uri) if err != nil { log.Fatalf("[x] AMQP connection error: %s", err) } log.Println("[√] Connected successfully") channel, err := connection.Channel() if err != nil { log.Fatalf("[x] Failed to open a channel: %s", err) } defer channel.Close() log.Println("[-] Declaring queue", queueName, "into channel") queue, err := declareQueue(queueName, channel) if err != nil { log.Fatalf("[x] Queue could not be declared. Error: %s", err.Error()) } log.Println("[√] Queue", queueName, "has been declared successfully") if reliable { log.Printf("[-] Enabling publishing confirms.") if err := channel.Confirm(false); err != nil { log.Fatalf("[x] Channel could not be put into confirm mode: %s", err) } confirms := channel.NotifyPublish(make(chan wabbit.Confirmation, 1)) defer confirmOne(confirms) } log.Println("[-] Sending message to", queueName) log.Println("\t", body) err = publishMessage(body, queue, channel) if err != nil { log.Fatalf("[x] Failed to publish a message. Error: %s", err.Error()) } } func connect(uri string) (*amqp.Conn, error) { return amqp.Dial(uri) } func declareQueue(queueName string, channel wabbit.Channel) (wabbit.Queue, error) { return channel.QueueDeclare( queueName, wabbit.Option{ "durable": false, "autoDelete": false, "exclusive": false, "noWait": false, }, ) } func publishMessage(body string, queue wabbit.Queue, channel wabbit.Channel) error { return channel.Publish( "", // exchange queue.Name(), // routing key []byte(body), wabbit.Option{ "deliveryMode": 2, "contentType": "text/plain", }) } func confirmOne(confirms <-chan wabbit.Confirmation) { log.Printf("[-] Waiting for confirmation of one publishing") if confirmed := <-confirms; confirmed.Ack() { log.Printf("[√] Confirmed delivery with delivery tag: %d", confirmed.DeliveryTag) } else { log.Printf("[x] Failed delivery of delivery tag: %d", confirmed.DeliveryTag) } } wabbit-master/_examples/publisher/exchange.go0000644000000000000000000000631413163221607020405 0ustar rootrootpackage main import ( "flag" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqp" "log" ) var ( uri = flag.String("uri", "amqp://guest:guest@localhost:5672/", "AMQP URI") queueName = flag.String("queue", "test-queue", "Ephemeral AMQP queue name") exchange = flag.String("exchange", "test-exchange", "Durable, non-auto-deleted AMQP exchange name") exchangeType = flag.String("exchange-type", "direct", "Exchange type - direct|fanout|topic|x-custom") body = flag.String("body", "body test", "Body of message") reliable = flag.Bool("reliable", true, "Wait for the publisher confirmation before exiting") ) func init() { flag.Parse() } func main() { publish(*uri, *queueName, *exchange, *exchangeType, *body, *reliable) } func publish(uri string, queueName string, exchange string, exchangeType string, body string, reliable bool) { log.Println("[-] Connecting to", uri) connection, err := connect(uri) if err != nil { log.Fatalf("[x] AMQP connection error: %s", err) } log.Println("[√] Connected successfully") channel, err := connection.Channel() if err != nil { log.Fatalf("[x] Failed to open a channel: %s", err) } defer channel.Close() log.Println("[-] Declaring Exchange", exchangeType, exchange) err = channel.ExchangeDeclare(exchange, exchangeType, nil) if err != nil { log.Fatalf("[x] Failed to declare exchange: %s", err) } log.Println("[√] Exchange", exchange, "has been declared successfully") log.Println("[-] Declaring queue", queueName, "into channel") queue, err := declareQueue(queueName, channel) if err != nil { log.Fatalf("[x] Queue could not be declared. Error: %s", err.Error()) } log.Println("[√] Queue", queueName, "has been declared successfully") if reliable { log.Printf("[-] Enabling publishing confirms.") if err := channel.Confirm(false); err != nil { log.Fatalf("[x] Channel could not be put into confirm mode: %s", err) } confirms := channel.NotifyPublish(make(chan wabbit.Confirmation, 1)) defer confirmOne(confirms) } log.Println("[-] Sending message to queue:", queueName, "- exchange:", exchange) log.Println("\t", body) err = publishMessage(body, exchange, queue, channel) if err != nil { log.Fatalf("[x] Failed to publish a message. Error: %s", err.Error()) } } func connect(uri string) (*amqp.Conn, error) { return amqp.Dial(uri) } func declareQueue(queueName string, channel wabbit.Channel) (wabbit.Queue, error) { return channel.QueueDeclare( queueName, wabbit.Option{ "durable": true, "autoDelete": false, "exclusive": false, "noWait": false, }, ) } func publishMessage(body string, exchange string, queue wabbit.Queue, channel wabbit.Channel) error { return channel.Publish( exchange, // exchange queue.Name(), // routing key []byte(body), wabbit.Option{ "deliveryMode": 2, "contentType": "text/plain", }) } func confirmOne(confirms <-chan wabbit.Confirmation) { log.Printf("[-] Waiting for confirmation of one publishing") if confirmed := <-confirms; confirmed.Ack() { log.Printf("[√] Confirmed delivery with delivery tag: %d", confirmed.DeliveryTag) } else { log.Printf("[x] Failed delivery of delivery tag: %d", confirmed.DeliveryTag) } } wabbit-master/Makefile0000644000000000000000000000002713163221607013755 0ustar rootroottest: ./hack/check.sh wabbit-master/.gitignore0000644000000000000000000000001513163221607014302 0ustar rootrootcoverage.txt wabbit-master/amqp.go0000644000000000000000000000366013163221607013610 0ustar rootroot// Package wabbit provides an interface for AMQP client specification and a mock // implementation of that interface. package wabbit type ( // Option is a map of AMQP configurations Option map[string]interface{} // Conn is the amqp connection interface Conn interface { Channel() (Channel, error) AutoRedial(errChan chan Error, done chan bool) Close() error NotifyClose(chan Error) chan Error } // Channel is an AMQP channel interface Channel interface { Ack(tag uint64, multiple bool) error Nack(tag uint64, multiple bool, requeue bool) error Reject(tag uint64, requeue bool) error Confirm(noWait bool) error NotifyPublish(confirm chan Confirmation) chan Confirmation Cancel(consumer string, noWait bool) error ExchangeDeclare(name, kind string, opt Option) error QueueDeclare(name string, args Option) (Queue, error) QueueDelete(name string, args Option) (int, error) QueueBind(name, key, exchange string, opt Option) error QueueUnbind(name, route, exchange string, args Option) error Consume(queue, consumer string, opt Option) (<-chan Delivery, error) Qos(prefetchCount, prefetchSize int, global bool) error Close() error Publisher } // Queue is a AMQP queue interface Queue interface { Name() string Messages() int Consumers() int } // Publisher is an interface to something able to publish messages Publisher interface { Publish(exc, route string, msg []byte, opt Option) error } // Delivery is an interface to delivered messages Delivery interface { Ack(multiple bool) error Nack(multiple, request bool) error Reject(requeue bool) error Body() []byte Headers() Option DeliveryTag() uint64 ConsumerTag() string } // Confirmation is an interface to confrimation messages Confirmation interface { Ack() bool DeliveryTag() uint64 } // Error is an interface for AMQP errors Error interface { Code() int Reason() string Server() bool Recover() bool error } ) wabbit-master/utils/0000755000000000000000000000000013163221607013456 5ustar rootrootwabbit-master/utils/opt.go0000644000000000000000000000311113163221607014603 0ustar rootrootpackage utils import ( "fmt" "github.com/NeowayLabs/wabbit" "github.com/streadway/amqp" ) type xstring []string var ( amqpOptions xstring = []string{ "headers", "contentType", "contentEncoding", "deliveryMode", "priority", } ) func (s xstring) Contains(key string) bool { for _, v := range s { if key == v { return true } } return false } func ConvertOpt(opt wabbit.Option) (amqp.Publishing, error) { var ( headers = amqp.Table{} contentType = "text/plain" contentEncoding = "" deliveryMode = amqp.Transient priority = uint8(0) ) if wrongOpt, ok := checkOptions(opt); !ok { return amqp.Publishing{}, fmt.Errorf("Wring option '%s'. Check the docs.", wrongOpt) } if opt != nil { if h, ok := opt["headers"].(amqp.Table); ok { headers = h } if c, ok := opt["contentType"].(string); ok { contentType = c } if c, ok := opt["contentEncoding"].(string); ok { contentEncoding = c } if d, ok := opt["deliveryMode"].(uint8); ok { deliveryMode = d } if p, ok := opt["priority"].(uint8); ok { priority = p } } return amqp.Publishing{ Headers: headers, ContentType: contentType, ContentEncoding: contentEncoding, DeliveryMode: deliveryMode, // 1=non-persistent, 2=persistent Priority: priority, // 0-9 // a bunch of application/implementation-specific fields }, nil } func checkOptions(opt wabbit.Option) (string, bool) { optMap := map[string]interface{}(opt) for k, _ := range optMap { if !amqpOptions.Contains(k) { return k, false } } return "", true } wabbit-master/utils/broadcast_test.go0000644000000000000000000000365013163221607017012 0ustar rootrootpackage utils import ( "testing" "time" "github.com/NeowayLabs/wabbit" ) func TestBroadcast(t *testing.T) { spread := NewErrBroadcast() r := make(chan wabbit.Error) spread.Add(r) go func() { spread.Write(NewError(1337, "teste", true, true)) }() select { case <-time.After(5 * time.Second): t.Errorf("Broadcast not working....") return case v := <-r: if v == nil { t.Errorf("Invalid value for err...") return } if v.Code() != 1337 || v.Reason() != "teste" { t.Errorf("Broadcast not working...") return } } } func TestBroadcastDelete(t *testing.T) { tests := []struct { name string listener int placement int }{ { name: "delete from zero listener", listener: 0, }, { name: "delete from one listener", listener: 1, placement: 0, }, { name: "delete the first from 64 listeners", listener: 64, placement: 0, }, { name: "delete the middle from 64 listeners", listener: 64, placement: 31, }, { name: "delete the last from 64 listeners", listener: 64, placement: 63, }, { name: "delete out of bound from 64 listeners", listener: 64, placement: 64, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { spread := NewErrBroadcast() del := make(chan wabbit.Error) for i := 0; i < tc.listener; i++ { if i == tc.placement { spread.Add(del) } else { spread.Add(make(chan wabbit.Error)) } } if tc.placement < tc.listener { i, ok := spread.findIndex(del) if !ok || i != tc.placement { t.Errorf("add failed") } } spread.Delete(del) _, ok := spread.findIndex(del) if ok { t.Errorf("delete failed") } if tc.placement < tc.listener { got, want := len(spread.listeners), tc.listener-1 if got != want { t.Errorf("unexpected listner size:\n- want: %d\n- got: %d", want, got) } } }) } } wabbit-master/utils/spec.go0000644000000000000000000000161613163221607014743 0ustar rootrootpackage utils // Error codes that can be sent from the server during a connection or // channel exception or used by the client to indicate a class of error like // ErrCredentials. The text of the error is likely more interesting than // these constants. const ( frameMethod = 1 frameHeader = 2 frameBody = 3 frameHeartbeat = 8 frameMinSize = 4096 frameEnd = 206 replySuccess = 200 ContentTooLarge = 311 NoRoute = 312 NoConsumers = 313 ConnectionForced = 320 InvalidPath = 402 AccessRefused = 403 NotFound = 404 ResourceLocked = 405 PreconditionFailed = 406 FrameError = 501 SyntaxError = 502 CommandInvalid = 503 ChannelError = 504 UnexpectedFrame = 505 ResourceError = 506 NotAllowed = 530 NotImplemented = 540 InternalError = 541 ) wabbit-master/utils/error.go0000644000000000000000000000104513163221607015136 0ustar rootrootpackage utils import "fmt" type Error struct { code int reason string server bool recover bool } func NewError(c int, r string, s bool, rc bool) *Error { return &Error{ code: c, reason: r, server: s, recover: rc, } } func (e Error) Error() string { return fmt.Sprintf("Exception (%d) Reason: %q", e.code, e.reason) } func (e Error) Code() int { return e.code } func (e Error) Reason() string { return e.reason } func (e Error) Server() bool { return e.server } func (e Error) Recover() bool { return e.recover } wabbit-master/utils/broadcast.go0000644000000000000000000000272213163221607015752 0ustar rootrootpackage utils import ( "sync" "github.com/NeowayLabs/wabbit" ) const listenerSlots = 128 // ErrBroadcast enables broadcast an error channel to various listener channels type ErrBroadcast struct { sync.Mutex // Protects listeners listeners []chan<- wabbit.Error c chan wabbit.Error } // NewErrBroadcast creates a broadcast object for push errors to subscribed channels func NewErrBroadcast() *ErrBroadcast { b := &ErrBroadcast{ c: make(chan wabbit.Error), listeners: make([]chan<- wabbit.Error, 0, listenerSlots), } go func() { for { select { case e := <-b.c: b.spread(e) } } }() return b } // Add a new listener func (b *ErrBroadcast) Add(c chan<- wabbit.Error) { b.Lock() b.listeners = append(b.listeners, c) b.Unlock() } // Delete the listener func (b *ErrBroadcast) Delete(c chan<- wabbit.Error) { i, ok := b.findIndex(c) if !ok { return } b.Lock() b.listeners[i] = b.listeners[len(b.listeners)-1] b.listeners[len(b.listeners)-1] = nil b.listeners = b.listeners[:len(b.listeners)-1] b.Unlock() } // Write to subscribed channels func (b *ErrBroadcast) Write(err wabbit.Error) { b.c <- err } func (b *ErrBroadcast) spread(err wabbit.Error) { b.Lock() for _, l := range b.listeners { l <- err } b.Unlock() } func (b *ErrBroadcast) findIndex(c chan<- wabbit.Error) (int, bool) { b.Lock() defer b.Unlock() for i := range b.listeners { if b.listeners[i] == c { return i, true } } return -1, false } wabbit-master/utils/opt_test.go0000644000000000000000000000330613163221607015650 0ustar rootrootpackage utils import ( "testing" "github.com/NeowayLabs/wabbit" "github.com/streadway/amqp" ) func TestConvertOptDefaults(t *testing.T) { opt, err := ConvertOpt(nil) if err != nil { t.Error(err) return } if opt.ContentType != "text/plain" { t.Errorf("Invalid opt content type: %s", opt.ContentType) } if opt.ContentEncoding != "" { t.Errorf("Invalid opt encoding: %s", opt.ContentEncoding) } if opt.DeliveryMode != amqp.Transient { t.Errorf("Invalid default delivery mode: %d\n", opt.DeliveryMode) } if opt.Priority != uint8(0) { t.Errorf("Invalid default priority: %d\n", opt.Priority) } if len(opt.Headers) != 0 { t.Errorf("Invalid value for headers: %v", opt.Headers) } } func TestConvertOpt(t *testing.T) { opt, err := ConvertOpt(wabbit.Option{ "contentType": "binary/fuzz", }) if err != nil { t.Error(err) return } if opt.ContentType != "binary/fuzz" { t.Errorf("Wrong value for content type: %s", opt.ContentType) } opt, err = ConvertOpt(wabbit.Option{ "contentEncoding": "bleh", }) if err != nil { t.Error(err) return } if opt.ContentEncoding != "bleh" { t.Errorf("Invalid value for contentEncoding: %s", opt.ContentEncoding) } opt, err = ConvertOpt(wabbit.Option{ "contentEncoding": "bleh", "contentType": "binary/fuzz", }) if err != nil { t.Error(err) return } if opt.ContentType != "binary/fuzz" { t.Errorf("Wrong value for content type: %s", opt.ContentType) } if opt.ContentEncoding != "bleh" { t.Errorf("Invalid value for contentEncoding: %s", opt.ContentEncoding) } // setting invalid value opt, err = ConvertOpt(wabbit.Option{ "NotExists": "bleh", }) if err == nil { t.Errorf("Shall fail...") return } } wabbit-master/mkfile0000644000000000000000000000003313163221607013504 0ustar rootroot check: ./hack/check.rc wabbit-master/.travis.yml0000644000000000000000000000102413163221607014424 0ustar rootrootlanguage: go sudo: required services: - docker go: - tip - 1.8 install: - go get -d -v ./... - go build script: - go get github.com/axw/gocov/gocov - go get github.com/mattn/goveralls - go get github.com/fsouza/go-dockerclient - go get github.com/tiago4orion/conjure - go get golang.org/x/tools/cmd/cover - hack/check.sh # - goveralls -coverprofile=coverage.txt -service=travis-ci before_install: - sudo pip install codecov after_success: codecov notifications: email: - tiago.natel@neoway.com.br wabbit-master/README.md0000644000000000000000000000536713163221607013610 0ustar rootroot# Wabbit - Go AMQP Mocking Library [![GoDoc](https://godoc.org/github.com/NeowayLabs/wabbit?status.svg)](https://godoc.org/github.com/NeowayLabs/wabbit) [![Go Report Card](https://goreportcard.com/badge/github.com/NeowayLabs/wabbit)](https://goreportcard.com/report/github.com/NeowayLabs/wabbit) > Elmer Fudd: Shhh. Be vewy vewy quiet, I'm hunting wabbits AMQP is a verbose protocol that makes it difficult to implement proper unit-testing on your application. The first goal of this package is provide a sane interface for an AMQP client implementation based on the specification AMQP-0-9-1 (no extension) and then an implementation of this interface using the well established package [streadway/amqp](https://github.com/streadway/amqp) (a wrapper). What are the advantages of this? ### Usage [How to use ?](/_examples) ### Testing This package have an AMQP interface and two possible implementations: - wabbit/amqp - Bypass to [streadway/amqp](https://github.com/streadway/amqp) - wabbit/amqptest In the same way you can use the http package in your software and use the *httptest* for testing, when using wabbit is recommended to use the *wabbit/amqp* package on your software and *wabbit/amqptest* in your tests. Simple test example: ```go package main import ( "testing" "github.com/NeowayLabs/wabbit/amqptest" "github.com/NeowayLabs/wabbit/amqptest/server" "github.com/NeowayLabs/wabbit/amqp" ) func TestChannelCreation(t *testing.T) { mockConn, err := amqptest.Dial("amqp://localhost:5672/%2f") // will fail, if err == nil { t.Error("This shall fail, because no fake amqp server is running...") } fakeServer := server.NewServer("amqp://localhost:5672/%2f") fakeServer.Start() mockConn, err = amqptest.Dial("amqp://localhost:5672/%2f") // now it works =D if err != nil { t.Error(err) } //Now you can use mockConn as a real amqp connection. channel, err := mockConn.Channel() // ... } ``` The package *amqptest/server* implements a mock AMQP server and it can be used to simulate network partitions or broker crashs. To create a new server instance use *server.NewServer* passing any amqpuri. You can create more than one server, but they need to have different amqpuris. Example below: ```go broker1 := server.NewServer("amqp://localhost:5672/%2f") broker2 := server.NewServer("amqp://192.168.10.165:5672/%2f") broker3 := server.NewServer("amqp://192.168.10.169:5672/%2f") broker1.Start() broker2.Start() broker3.Start() ``` Calling NewServer with same amqpuri will return the same server instance. Use *broker.Stop()* to abruptly stop the amqp server. **There's no fake clustering support yet (maybe never)** It's a very straightforward implementation that need a lot of improvements yet. Take careful when using it. []'s wabbit-master/todo_wabbit.org0000644000000000000000000000052413163221607015325 0ustar rootroot* Wabbit Project Planning ** DONE Add support for ack, nack and reject messages :LOGBOOK: CLOCK: [2016-03-20 Sun 07:31]--[2016-03-20 Sun 19:01] => 11:30 :END: ** TODO Improve documentation :LOGBOOK: CLOCK: [2016-03-30 Wed 11:12]--[2016-03-30 Wed 11:26] => 0:14 :END: ** TODO Add more integration tests with real rabbitmq