From fa0a79a4d0c0e0e5106316597be0dcd92eeaf42f Mon Sep 17 00:00:00 2001 From: Enrique Bris Date: Fri, 11 Jan 2019 17:57:20 -0500 Subject: [PATCH 1/3] FIFO queue tests --- fifo_queue.go | 83 ++++++++++++++ fifo_queue_test.go | 266 +++++++++++++++++++++++++++++++++++++++++++++ queue.go | 14 +++ 3 files changed, 363 insertions(+) create mode 100644 fifo_queue.go create mode 100644 fifo_queue_test.go create mode 100644 queue.go diff --git a/fifo_queue.go b/fifo_queue.go new file mode 100644 index 0000000..4f013f8 --- /dev/null +++ b/fifo_queue.go @@ -0,0 +1,83 @@ +package goconcurrentqueue + +import ( + "fmt" + "sync" +) + +// First In First Out (FIFO) concurrent queue +type FIFO struct { + slice []interface{} + rwmutex sync.RWMutex +} + +// NewFIFO returns a new FIFO concurrent queue +func NewFIFO() *FIFO { + ret := &FIFO{} + ret.initialize() + + return ret +} + +func (st *FIFO) initialize() { + st.slice = make([]interface{}, 0) +} + +// Enqueue enqueues an element +func (st *FIFO) Enqueue(value interface{}) { + st.rwmutex.Lock() + defer st.rwmutex.Unlock() + + st.slice = append(st.slice, value) +} + +// Dequeue dequeues an element +func (st *FIFO) Dequeue() (interface{}, error) { + st.rwmutex.Lock() + defer st.rwmutex.Unlock() + + len := len(st.slice) + if len == 0 { + return nil, fmt.Errorf("queue is empty") + } + + elementToReturn := st.slice[0] + st.slice = st.slice[1:] + + return elementToReturn, nil +} + +// Get returns an element's value and keeps the element at the queue +func (st *FIFO) Get(index int) (interface{}, error) { + st.rwmutex.RLock() + defer st.rwmutex.RUnlock() + + if len(st.slice) <= index { + return nil, fmt.Errorf("index out of bounds: %v", index) + } + + return st.slice[index], nil +} + +// Remove removes an element from the queue +func (st *FIFO) Remove(index int) error { + st.rwmutex.Lock() + defer st.rwmutex.Unlock() + + if len(st.slice) <= index { + return fmt.Errorf("index out of bounds: %v", index) + } + + // remove the element + st.slice = append(st.slice[:index], st.slice[index+1:]...) + + return nil +} + +// GetLen returns the number of enqueued elements +func (st *FIFO) GetLen() int { + st.rwmutex.RLock() + defer st.rwmutex.RUnlock() + + return len(st.slice) +} diff --git a/fifo_queue_test.go b/fifo_queue_test.go new file mode 100644 index 0000000..a03b813 --- /dev/null +++ b/fifo_queue_test.go @@ -0,0 +1,266 @@ +package goconcurrentqueue + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/suite" +) + +const ( + testValue = "test value" +) + +type FIFOTestSuite struct { + suite.Suite + fifo *FIFO +} + +func (suite *FIFOTestSuite) SetupTest() { + suite.fifo = NewFIFO() +} + +// *************************************************************************************** +// ** Queue initialization +// *************************************************************************************** + +// no elements at initialization +func (suite *FIFOTestSuite) TestNoElementsAtInitialization() { + len := suite.fifo.GetLen() + suite.Equalf(0, len, "No elements expected at initialization, currently: %v", len) +} + +// *************************************************************************************** +// ** Enqueue +// *************************************************************************************** + +// single enqueue (1 element, 1 goroutine) +func (suite *FIFOTestSuite) TestEnqueueLenSingleGR() { + suite.fifo.Enqueue(testValue) + len := suite.fifo.GetLen() + suite.Equalf(1, len, "Expected number of elements in queue: 1, currently: %v", len) + + suite.fifo.Enqueue(5) + len = suite.fifo.GetLen() + suite.Equalf(2, len, "Expected number of elements in queue: 2, currently: %v", len) +} + +// TestEnqueueLenMultipleGR enqueues elements concurrently +// +// Detailed steps: +// 1 - Enqueue totalGRs concurrently (from totalGRs different GRs) +// 2 - Verifies the len, it should be equal to totalGRs +// 3 - Verifies that all elements from 0 to totalGRs were enqueued +func (suite *FIFOTestSuite) TestEnqueueLenMultipleGR() { + var ( + totalGRs = 500 + wg sync.WaitGroup + ) + + // concurrent enqueueing + // multiple GRs concurrently enqueueing consecutive integers from 0 to (totalGRs - 1) + for i := 0; i < totalGRs; i++ { + wg.Add(1) + go func(value int) { + defer wg.Done() + suite.fifo.Enqueue(value) + }(i) + } + + wg.Wait() + + // check that there are totalGRs elements enqueued + totalElements := suite.fifo.GetLen() + suite.Equalf(totalGRs, totalElements, "Total enqueued elements should be %v, currently: %v", totalGRs, totalElements) + + // checking that the expected elements (1, 2, 3, ... totalGRs-1 ) were enqueued + var ( + tmpVal interface{} + val int + err error + totalElementsVerified int + ) + // slice to check every element + sl2check := make([]bool, totalGRs) + + for i := 0; i < totalElements; i++ { + tmpVal, err = suite.fifo.Get(i) + suite.NoError(err, "No error should be returned trying to get an existent queue element") + + val = tmpVal.(int) + if !sl2check[val] { + totalElementsVerified++ + sl2check[val] = true + } else { + suite.Failf("Duplicated element", "Unexpected duplicated value: %v", val) + } + } + suite.True(totalElementsVerified == totalGRs, "Enqueued elements are missing") +} + +// *************************************************************************************** +// ** Get +// *************************************************************************************** + +// get a valid element +func (suite *FIFOTestSuite) TestGetSingleGR() { + suite.fifo.Enqueue(testValue) + val, err := suite.fifo.Get(0) + + // verify error (should be nil) + suite.NoError(err, "Unexpected error after ask for an existent element") + + // verify element's value + suite.Equalf(testValue, val, "Different element returned: %v", val) +} + +// get a invalid element +func (suite *FIFOTestSuite) TestGetInvalidElementSingleGR() { + suite.fifo.Enqueue(testValue) + val, err := suite.fifo.Get(1) + + // verify error + suite.Error(err, "An error should be returned after ask for a no existent element") + + // verify element's value + suite.Equalf(val, nil, "Nil should be returner, currently returned: %v", val) +} + +// *************************************************************************************** +// ** Remove +// *************************************************************************************** + +// remove elements +func (suite *FIFOTestSuite) TestRemoveSingleGR() { + suite.fifo.Enqueue(testValue) + suite.fifo.Enqueue(5) + + // removing first element + err := suite.fifo.Remove(0) + suite.NoError(err, "Unexpected error") + + // get element at index 0 + val, err2 := suite.fifo.Get(0) + suite.NoError(err2, "Unexpected error") + suite.Equal(5, val, "Queue returned the wrong element") + + // remove element having a wrong index + err3 := suite.fifo.Remove(1) + suite.Errorf(err3, "The index of the element to remove is out of bounds") +} + +// TestRemoveMultipleGRs removes elements concurrently. +// +// Detailed steps: +// 1 - Enqueues totalElementsToEnqueue consecutive elements (0, 1, 2, 3, ... totalElementsToEnqueue - 1) +// 2 - Hits fifo.Remove(1) concurrently from totalElementsToRemove different GRs +// 3 - Verifies the final len == totalElementsToEnqueue - totalElementsToRemove +// 4 - Verifies that final 2nd element == (1 + totalElementsToRemove) +func (suite *FIFOTestSuite) TestRemoveMultipleGRs() { + var ( + wg sync.WaitGroup + totalElementsToEnqueue = 100 + totalElementsToRemove = 90 + ) + + for i := 0; i < totalElementsToEnqueue; i++ { + suite.fifo.Enqueue(i) + } + + for i := 0; i < totalElementsToRemove; i++ { + wg.Add(1) + go func() { + defer wg.Done() + err := suite.fifo.Remove(1) + suite.NoError(err, "Unexpected error during concurrent Remove(n)") + }() + } + wg.Wait() + + // check len, should be == totalElementsToEnqueue - totalElementsToRemove + totalElementsAfterRemove := suite.fifo.GetLen() + suite.Equal(totalElementsToEnqueue-totalElementsToRemove, totalElementsAfterRemove, "Total elements on list does not match with expected number") + + // check current 2nd element (index 1) on the queue + val, err := suite.fifo.Get(1) + suite.NoError(err, "No error should be returned when getting an existent element") + suite.Equalf(1+totalElementsToRemove, val, "The expected value at position 1 (2nd element) should be: %v", 1+totalElementsToRemove) +} + +// *************************************************************************************** +// ** Dequeue +// *************************************************************************************** + +// dequeue an empty queue +func (suite *FIFOTestSuite) TestDequeueEmptyQueueSingleGR() { + val, err := suite.fifo.Dequeue() + suite.Errorf(err, "Can't dequeue an empty queue") + suite.Equal(nil, val, "Can't get a value different than nil from an empty queue") +} + +// dequeue all elements +func (suite *FIFOTestSuite) TestDequeueSingleGR() { + suite.fifo.Enqueue(testValue) + suite.fifo.Enqueue(5) + + // get the first element + val, err := suite.fifo.Dequeue() + suite.NoError(err, "Unexpected error") + suite.Equal(testValue, val, "Wrong element's value") + len := suite.fifo.GetLen() + suite.Equal(1, len, "Incorrect number of queue elements") + + // get the second element + val, err = suite.fifo.Dequeue() + suite.NoError(err, "Unexpected error") + suite.Equal(5, val, "Wrong element's value") + len = suite.fifo.GetLen() + suite.Equal(0, len, "Incorrect number of queue elements") + +} + +// TestDequeueMultipleGRs dequeues elements concurrently +// +// Detailed steps: +// 1 - Enqueues totalElementsToEnqueue consecutive integers +// 2 - Dequeues totalElementsToDequeue concurrently from totalElementsToDequeue GRs +// 3 - Verifies the final len, should be equal to totalElementsToEnqueue - totalElementsToDequeue +// 4 - Verifies that the next dequeued element's value is equal to totalElementsToDequeue +func (suite *FIFOTestSuite) TestDequeueMultipleGRs() { + var ( + wg sync.WaitGroup + totalElementsToEnqueue = 100 + totalElementsToDequeue = 90 + ) + + for i := 0; i < totalElementsToEnqueue; i++ { + suite.fifo.Enqueue(i) + } + + for i := 0; i < totalElementsToDequeue; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _, err := suite.fifo.Dequeue() + suite.NoError(err, "Unexpected error during concurrent Dequeue()") + }() + } + wg.Wait() + + // check len, should be == totalElementsToEnqueue - totalElementsToDequeue + totalElementsAfterDequeue := suite.fifo.GetLen() + suite.Equal(totalElementsToEnqueue-totalElementsToDequeue, totalElementsAfterDequeue, "Total elements on queue (after Dequeue) does not match with expected number") + + // check current first element + val, err := suite.fifo.Dequeue() + suite.NoError(err, "No error should be returned when getting an existent element") + suite.Equalf(totalElementsToDequeue, val, "The expected last element's value should be: %v", totalElementsToEnqueue-totalElementsToDequeue) +} + +// *************************************************************************************** +// ** Run suite +// *************************************************************************************** + +func TestRunSuite(t *testing.T) { + suite.Run(t, new(FIFOTestSuite)) +} diff --git a/queue.go b/queue.go new file mode 100644 index 0000000..4bc315a --- /dev/null +++ b/queue.go @@ -0,0 +1,14 @@ +package goconcurrentqueue + +type Queue interface { + // Enqueue element + Enqueue(interface{}) + // Dequeue element + Dequeue() (interface{}, error) + // Get number of enqueued elements + GetLen() int + // Get an element's value and keep it at the queue + Get(int) (interface{}, error) + // Remove any element from the queue + Remove(index int) error +} From 6e3ecbac87f8ec263ad1a1203e0b0370138a5f4b Mon Sep 17 00:00:00 2001 From: Enrique Bris Date: Fri, 11 Jan 2019 22:47:17 -0500 Subject: [PATCH 2/3] FIFO queue tests - 2 --- fifo_queue_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/fifo_queue_test.go b/fifo_queue_test.go index a03b813..a253891 100644 --- a/fifo_queue_test.go +++ b/fifo_queue_test.go @@ -31,7 +31,7 @@ func (suite *FIFOTestSuite) TestNoElementsAtInitialization() { } // *************************************************************************************** -// ** Enqueue +// ** Enqueue && GetLen // *************************************************************************************** // single enqueue (1 element, 1 goroutine) @@ -85,7 +85,7 @@ func (suite *FIFOTestSuite) TestEnqueueLenMultipleGR() { for i := 0; i < totalElements; i++ { tmpVal, err = suite.fifo.Get(i) - suite.NoError(err, "No error should be returned trying to get an existent queue element") + suite.NoError(err, "No error should be returned trying to get an existent element") val = tmpVal.(int) if !sl2check[val] { @@ -98,6 +98,30 @@ func (suite *FIFOTestSuite) TestEnqueueLenMultipleGR() { suite.True(totalElementsVerified == totalGRs, "Enqueued elements are missing") } +// call GetLen concurrently +func (suite *FIFOTestSuite) TestGetLenMultipleGRs() { + var ( + totalGRs = 100 + totalElementsToEnqueue = 10 + wg sync.WaitGroup + ) + + for i := 0; i < totalElementsToEnqueue; i++ { + suite.fifo.Enqueue(i) + } + + for i := 0; i < totalGRs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + total := suite.fifo.GetLen() + suite.Equalf(totalElementsToEnqueue, total, "Expected len: %v", totalElementsToEnqueue) + }() + } + wg.Wait() +} + // *************************************************************************************** // ** Get // *************************************************************************************** @@ -108,7 +132,7 @@ func (suite *FIFOTestSuite) TestGetSingleGR() { val, err := suite.fifo.Get(0) // verify error (should be nil) - suite.NoError(err, "Unexpected error after ask for an existent element") + suite.NoError(err, "No error should be enqueueing an element") // verify element's value suite.Equalf(testValue, val, "Different element returned: %v", val) @@ -126,6 +150,34 @@ func (suite *FIFOTestSuite) TestGetInvalidElementSingleGR() { suite.Equalf(val, nil, "Nil should be returner, currently returned: %v", val) } +// call Get concurrently +func (suite *FIFOTestSuite) TestGetMultipleGRs() { + var ( + totalGRs = 100 + totalElementsToEnqueue = 10 + wg sync.WaitGroup + ) + + for i := 0; i < totalElementsToEnqueue; i++ { + suite.fifo.Enqueue(i) + } + + for i := 0; i < totalGRs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + val, err := suite.fifo.Get(5) + + suite.NoError(err, "No error should be returned trying to get an existent element") + suite.Equal(5, val.(int), "Expected element's value: 5") + }() + } + wg.Wait() + + total := suite.fifo.GetLen() + suite.Equalf(totalElementsToEnqueue, total, "Expected len: %v", totalElementsToEnqueue) +} + // *************************************************************************************** // ** Remove // *************************************************************************************** From 038f753057e40ec2233f9e0ec7d24057430440bc Mon Sep 17 00:00:00 2001 From: Enrique Bris Date: Mon, 14 Jan 2019 11:18:04 -0500 Subject: [PATCH 3/3] readme.md && travis-ci && codecov.io --- readme.md | 18 ++++++++++++++++++ travis.yml | 15 +++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 readme.md create mode 100644 travis.yml diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..09d36b2 --- /dev/null +++ b/readme.md @@ -0,0 +1,18 @@ +[![godoc reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/enriquebris/goconcurrentqueue) ![version](https://img.shields.io/badge/version-v0.1.0-yellowgreen.svg?style=flat "goconcurrentqueue v0.1.0") [![Go Report Card](https://goreportcard.com/badge/github.com/enriquebris/goconcurrentqueue)](https://goreportcard.com/report/github.com/enriquebris/goconcurrentqueue) [![Build Status](https://travis-ci.org/enriquebris/goconcurrentqueue.svg?branch=master)](https://travis-ci.org/enriquebris/goconcurrentqueue) [![codecov](https://codecov.io/gh/enriquebris/goconcurrentqueue/branch/master/graph/badge.svg)](https://codecov.io/gh/enriquebris/goconcurrentqueue) + +# goconcurrentqueue - Concurrent queues +Concurrent safe queues + +## Installation + +Execute +```bash +go get github.com/enriquebris/goconcurrentqueue +``` + +## Documentation +Visit [goconcurrentqueue at godoc.org](https://godoc.org/github.com/enriquebris/goworkerpool) + +## Qeueues + +- First In First Out (FIFO) \ No newline at end of file diff --git a/travis.yml b/travis.yml new file mode 100644 index 0000000..54c903a --- /dev/null +++ b/travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - 1.9.x + - 1.10.x + - 1.11.x + +before_install: + - go get -t -v ./... + +script: + - go test -coverprofile=coverage.txt -covermode=atomic + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file