From 45dd9caea555714e5c8936dd1c302d539ec4bc83 Mon Sep 17 00:00:00 2001 From: teivah Date: Sat, 15 Feb 2020 14:46:10 +0000 Subject: [PATCH] Cleaning --- LICENSE => LICENSE.md | 0 assert.go | 339 +----- assert_test.go | 55 - connectableobservable.go | 339 ------ connectableobservable_test.go | 40 - disposable.go | 8 - duration.go | 39 - duration_test.go | 13 - errors.go | 41 - factory.go | 9 + factory_test.go | 22 + flatmap.go | 68 -- flatmap_test.go | 106 -- fx.go | 57 - go.mod | 7 +- go.sum | 18 +- handlers.go | 42 - handlers_test.go | 97 -- iterable.go | 67 +- iterator.go | 81 -- iterator_test.go | 46 - mock.go | 343 ------ observable.go | 1862 +------------------------------- observable_test.go | 1883 +-------------------------------- observablecreate.go | 461 -------- observablecreate_test.go | 439 -------- observer.go | 127 +-- observer_mock.go | 51 - observer_test.go | 94 +- optional.go | 52 - optional_test.go | 37 - options.go | 133 --- options_test.go | 12 - single.go | 151 --- single_test.go | 75 -- types.go | 12 + worker.go | 61 -- 37 files changed, 200 insertions(+), 7087 deletions(-) rename LICENSE => LICENSE.md (100%) delete mode 100644 assert_test.go delete mode 100644 connectableobservable.go delete mode 100644 connectableobservable_test.go delete mode 100644 disposable.go delete mode 100644 duration.go delete mode 100644 duration_test.go delete mode 100644 errors.go create mode 100644 factory.go create mode 100644 factory_test.go delete mode 100644 flatmap.go delete mode 100644 flatmap_test.go delete mode 100644 fx.go delete mode 100644 handlers.go delete mode 100644 handlers_test.go delete mode 100644 iterator.go delete mode 100644 iterator_test.go delete mode 100644 mock.go delete mode 100644 observablecreate.go delete mode 100644 observablecreate_test.go delete mode 100644 observer_mock.go delete mode 100644 optional.go delete mode 100644 optional_test.go delete mode 100644 options.go delete mode 100644 options_test.go delete mode 100644 single.go delete mode 100644 single_test.go create mode 100644 types.go delete mode 100644 worker.go diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/assert.go b/assert.go index 7368962c..991193fd 100644 --- a/assert.go +++ b/assert.go @@ -2,342 +2,85 @@ package rxgo import ( "context" - "errors" - "testing" - "time" - "github.com/stretchr/testify/assert" + "testing" ) -const testWaitTime = 30 * time.Millisecond - -// RxAssertion lists the assertions which may be configured on an Observable. -type RxAssertion interface { - apply(*assertion) - hasItemsFunc() (bool, []interface{}) - hasItemsNoOrderFunc() (bool, []interface{}) - hasSizeFunc() (bool, int) - hasValueFunc() (bool, interface{}) - hasRaisedErrorFunc() (bool, error) - hasRaisedAnErrorFunc() bool - hasNotRaisedAnErrorFunc() bool - isEmptyFunc() (bool, bool) -} - -type assertion struct { - f func(*assertion) - checkHasItems bool - hasItems []interface{} - checkHasItemsNoOrder bool - hasItemsNoOrder []interface{} - checkHasSize bool - hasSize int - checkHasValue bool - hasValue interface{} - checkHasRaisedError bool - hasRaisedError error - checkHasRaisedAnError bool - checkHasNotRaisedAnError bool - checkIsEmpty bool - isEmpty bool -} - -func (ass *assertion) hasItemsFunc() (bool, []interface{}) { - return ass.checkHasItems, ass.hasItems -} - -func (ass *assertion) hasItemsNoOrderFunc() (bool, []interface{}) { - return ass.checkHasItemsNoOrder, ass.hasItemsNoOrder -} - -func (ass *assertion) hasSizeFunc() (bool, int) { - return ass.checkHasSize, ass.hasSize -} - -func (ass *assertion) hasValueFunc() (bool, interface{}) { - return ass.checkHasValue, ass.hasValue +type rxAssert interface { + apply(*rxAssertImpl) + hasItemsF() (bool, []interface{}) + hasRaisedError() (bool, error) } -func (ass *assertion) hasRaisedErrorFunc() (bool, error) { - return ass.checkHasRaisedError, ass.hasRaisedError +type rxAssertImpl struct { + f func(*rxAssertImpl) + checkHasItems bool + hasItems []interface{} + checkHasRaisedError bool + hasError error } -func (ass *assertion) hasRaisedAnErrorFunc() bool { - return ass.checkHasRaisedAnError -} - -func (ass *assertion) hasNotRaisedAnErrorFunc() bool { - return ass.checkHasNotRaisedAnError +func (ass *rxAssertImpl) apply(do *rxAssertImpl) { + ass.f(do) } -func (ass *assertion) isEmptyFunc() (bool, bool) { - return ass.checkIsEmpty, ass.isEmpty +func (ass *rxAssertImpl) hasItemsF() (bool, []interface{}) { + return ass.checkHasItems, ass.hasItems } -func (ass *assertion) apply(do *assertion) { - ass.f(do) +func (ass *rxAssertImpl) hasRaisedError() (bool, error) { + return ass.checkHasRaisedError, ass.hasError } -func newAssertion(f func(*assertion)) *assertion { - return &assertion{ +func newAssertion(f func(*rxAssertImpl)) *rxAssertImpl { + return &rxAssertImpl{ f: f, } } -func parseAssertions(assertions ...RxAssertion) RxAssertion { - a := new(assertion) - for _, assertion := range assertions { - assertion.apply(a) - } - return a -} - -// HasItems checks that an observable produces the corresponding items. -func HasItems(items ...interface{}) RxAssertion { - return newAssertion(func(a *assertion) { +func hasItems(items ...interface{}) rxAssert { + return newAssertion(func(a *rxAssertImpl) { a.checkHasItems = true a.hasItems = items }) } -// HasItemsNoOrder checks that an observable produces the corresponding items regardless of the order. -func HasItemsNoOrder(items ...interface{}) RxAssertion { - return newAssertion(func(a *assertion) { - a.checkHasItemsNoOrder = true - a.hasItemsNoOrder = items - }) -} - -// HasSize checks that an observable produces the corresponding number of items. -func HasSize(size int) RxAssertion { - return newAssertion(func(a *assertion) { - a.checkHasSize = true - a.hasSize = size - }) -} - -// IsEmpty checks that an observable produces zero items. -func IsEmpty() RxAssertion { - return newAssertion(func(a *assertion) { - a.checkIsEmpty = true - a.isEmpty = true - }) -} - -// IsNotEmpty checks that an observable produces items. -func IsNotEmpty() RxAssertion { - return newAssertion(func(a *assertion) { - a.checkIsEmpty = true - a.isEmpty = false - }) -} - -// HasValue checks that a single produces the corresponding value. -func HasValue(value interface{}) RxAssertion { - return newAssertion(func(a *assertion) { - a.checkHasValue = true - a.hasValue = value - }) -} - -// HasRaisedError checks that a single raises the corresponding error. -func HasRaisedError(err error) RxAssertion { - return newAssertion(func(a *assertion) { +func hasRaisedError(err error) rxAssert { + return newAssertion(func(a *rxAssertImpl) { a.checkHasRaisedError = true - a.hasRaisedError = err - }) -} - -// HasRaisedAnError checks that a single raises an error. -func HasRaisedAnError() RxAssertion { - return newAssertion(func(a *assertion) { - a.checkHasRaisedAnError = true + a.hasError = err }) } -// HasNotRaisedAnyError checks that a single does not raise an error. -func HasNotRaisedAnyError() RxAssertion { - return newAssertion(func(a *assertion) { - a.checkHasNotRaisedAnError = true - }) -} - -func assertObservable(t *testing.T, ass RxAssertion, got []interface{}, err error) { - checkHasItems, items := ass.hasItemsFunc() - if checkHasItems { - assert.Equal(t, items, got) - } - - checkHasItemsNoOrder, itemsNoOrder := ass.hasItemsNoOrderFunc() - if checkHasItemsNoOrder { - m := make(map[interface{}]interface{}) - for _, v := range itemsNoOrder { - m[v] = nil - } - - for _, v := range got { - delete(m, v) - } - assert.Equal(t, 0, len(m)) - } - - checkHasSize, size := ass.hasSizeFunc() - if checkHasSize { - assert.Equal(t, size, len(got)) - } - - checkIsEmpty, empty := ass.isEmptyFunc() - if checkIsEmpty { - if empty { - assert.Equal(t, 0, len(got)) - } else { - assert.NotEqual(t, 0, len(got)) - } - } - - checkHasRaisedAnError := ass.hasRaisedAnErrorFunc() - if checkHasRaisedAnError { - assert.NotNil(t, err) - } - - checkHasRaisedError, value := ass.hasRaisedErrorFunc() - if checkHasRaisedError { - assert.Equal(t, value, err) - } - - checkHasNotRaisedError := ass.hasNotRaisedAnErrorFunc() - if checkHasNotRaisedError { - assert.Nil(t, err) +func parseAssertions(assertions ...rxAssert) rxAssert { + ass := new(rxAssertImpl) + for _, assertion := range assertions { + assertion.apply(ass) } + return ass } -// AssertObservable asserts the result of an Observable against a list of assertions. -func AssertObservable(t *testing.T, observable Observable, assertions ...RxAssertion) { +func assertObservable(t *testing.T, ctx context.Context, observable Observable, assertions ...rxAssert) { ass := parseAssertions(assertions...) - got := make([]interface{}, 0) - var err error - observable.ForEach(func(i interface{}) { - got = append(got, i) - }, func(e error) { - err = e - }, nil).Block() - assertObservable(t, ass, got, err) -} - -// AssertObservableEventually asserts eventually the result of an Observable against a list of assertions. -func AssertObservableEventually(t *testing.T, observable Observable, timeout time.Duration, assertions ...RxAssertion) { - ass := parseAssertions(assertions...) - - chItem := make(chan interface{}, 1) - defer close(chItem) - chErr := make(chan error) - defer close(chErr) got := make([]interface{}, 0) var err error + done := make(chan struct{}) - observable.ForEach(func(i interface{}) { - chItem <- i + observable.ForEach(ctx, func(i interface{}) { + got = append(got, i) }, func(e error) { - chErr <- e - }, nil) - ctxTimeout, ctxTimeoutF := context.WithTimeout(context.Background(), timeout) - defer ctxTimeoutF() - -mainLoop: - for { - select { - case item, open := <-chItem: - if open { - got = append(got, item) - } else { - break mainLoop - } - case e := <-chErr: - err = e - case <-ctxTimeout.Done(): - break mainLoop - } - } - - assertObservable(t, ass, got, err) -} - -// AssertSingle asserts the result of a Single against a list of assertions. -func AssertSingle(t *testing.T, single Single, assertions ...RxAssertion) { - ass := parseAssertions(assertions...) - - var v interface{} - var err error - single.Subscribe(NewObserver(NextFunc(func(i interface{}) { - v = i - }), ErrFunc(func(e error) { err = e - }))).Block() - - checkHasValue, value := ass.hasValueFunc() - if checkHasValue { - assert.NoError(t, err) - assert.Equal(t, value, v) - } - - checkHasRaisedAnError := ass.hasRaisedAnErrorFunc() - if checkHasRaisedAnError { - assert.NotNil(t, err) - } - - checkHasRaisedError, value := ass.hasRaisedErrorFunc() - if checkHasRaisedError { - assert.Equal(t, value, err) - } + }, func() { + close(done) + }) - checkHasNotRaisedError := ass.hasNotRaisedAnErrorFunc() - if checkHasNotRaisedError { - assert.Nil(t, err) + if checkHasItems, expectedItems := ass.hasItemsF(); checkHasItems { + <-done + assert.Equal(t, expectedItems, got) } -} - -// AssertOptionalSingle asserts the result of an OptionalSingle against a list of assertions. -func AssertOptionalSingle(t *testing.T, optionalSingle OptionalSingle, assertions ...RxAssertion) { - ass := parseAssertions(assertions...) - - var v interface{} - var err error - optionalSingle.Subscribe(NewObserver(NextFunc(func(i interface{}) { - v = i - }), ErrFunc(func(e error) { - err = e - }))).Block() - assert.NoError(t, err) - - if optional, ok := v.(Optional); ok { - checkIsEmpty, empty := ass.isEmptyFunc() - if checkIsEmpty { - if empty { - assert.True(t, optional.IsEmpty()) - } else { - assert.False(t, optional.IsEmpty()) - } - } - - got, emptyError := optional.Get() - - checkHasRaisedAnError := ass.hasRaisedAnErrorFunc() - if checkHasRaisedAnError { - assert.Nil(t, emptyError) - assert.IsType(t, errors.New(""), got) - } - - checkHasRaisedError, emptyError := ass.hasRaisedErrorFunc() - if checkHasRaisedError { - assert.Equal(t, emptyError, got) - } - checkHasValue, value := ass.hasValueFunc() - if checkHasValue { - assert.Equal(t, value, got) - } - } else { - assert.Fail(t, "OptionalSingle did not produce an Optional") + if checkHasRaisedError, expectedError := ass.hasRaisedError(); checkHasRaisedError { + assert.Equal(t, expectedError, err) } } diff --git a/assert_test.go b/assert_test.go deleted file mode 100644 index 1354ea96..00000000 --- a/assert_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package rxgo - -import ( - "errors" - "testing" -) - -func TestAssertThatObservableHasItems(t *testing.T) { - AssertObservable(t, Just(1, 2, 3), HasItems(1, 2, 3)) -} - -func TestAssertThatObservableHasSize(t *testing.T) { - AssertObservable(t, Just(1, 2, 3), HasSize(3)) -} - -func TestAssertThatObservableIsEmpty(t *testing.T) { - AssertObservable(t, Empty(), IsEmpty()) -} - -func TestAssertThatObservableIsNotEmpty(t *testing.T) { - AssertObservable(t, Just(1), IsNotEmpty()) -} - -func TestAssertThatSingleHasValue(t *testing.T) { - AssertSingle(t, newSingleFrom(1), HasValue(1)) -} - -func TestAssertThatSingleError(t *testing.T) { - AssertSingle(t, newSingleFrom(errors.New("foo")), - HasRaisedAnError(), HasRaisedError(errors.New("foo"))) -} - -func TestAssertThatSingleNotError(t *testing.T) { - AssertSingle(t, newSingleFrom(1), HasNotRaisedAnyError()) -} - -func TestAssertThatOptionalSingleIsEmpty(t *testing.T) { - AssertOptionalSingle(t, newOptionalSingleFrom(EmptyOptional()), IsEmpty()) -} - -func TestAssertThatOptionalSingleIsNotEmpty(t *testing.T) { - AssertOptionalSingle(t, newOptionalSingleFrom(Of(1)), IsNotEmpty()) -} - -func TestAssertThatOptionalSingleHasValue(t *testing.T) { - AssertOptionalSingle(t, newOptionalSingleFrom(Of(1)), HasValue(1)) -} - -func TestAssertThatOptionalSingleHasRaisedAnError(t *testing.T) { - AssertOptionalSingle(t, newOptionalSingleFrom(Of(errors.New("foo"))), HasRaisedAnError()) -} - -func TestAssertThatOptionalSingleHasRaisedError(t *testing.T) { - AssertOptionalSingle(t, newOptionalSingleFrom(Of(errors.New("foo"))), HasRaisedError(errors.New("foo"))) -} diff --git a/connectableobservable.go b/connectableobservable.go deleted file mode 100644 index c7eb4047..00000000 --- a/connectableobservable.go +++ /dev/null @@ -1,339 +0,0 @@ -package rxgo - -import ( - "context" - "sync" - - "github.com/pkg/errors" -) - -// ConnectableObservable is an observable that send items once an observer is connected -type ConnectableObservable interface { - Observable - Connect() Observer -} - -type connectableObservable struct { - observable Observable - observersMutex sync.Mutex - observers []Observer -} - -func newConnectableObservableFromObservable(observable Observable) ConnectableObservable { - return &connectableObservable{ - observable: observable, - } -} - -func (c *connectableObservable) Iterator(ctx context.Context) Iterator { - return c.observable.Iterator(context.Background()) -} - -func (c *connectableObservable) All(predicate Predicate) Single { - return c.observable.All(predicate) -} - -func (c *connectableObservable) AverageFloat32() Single { - return c.observable.AverageFloat32() -} - -func (c *connectableObservable) AverageFloat64() Single { - return c.observable.AverageFloat64() -} - -func (c *connectableObservable) AverageInt() Single { - return c.observable.AverageInt() -} - -func (c *connectableObservable) AverageInt8() Single { - return c.observable.AverageInt8() -} - -func (c *connectableObservable) AverageInt16() Single { - return c.observable.AverageInt16() -} - -func (c *connectableObservable) AverageInt32() Single { - return c.observable.AverageInt32() -} - -func (c *connectableObservable) AverageInt64() Single { - return c.observable.AverageInt64() -} - -func (c *connectableObservable) BufferWithCount(count, skip int) Observable { - return c.observable.BufferWithCount(count, skip) -} - -func (c *connectableObservable) BufferWithTime(timespan, timeshift Duration) Observable { - return c.observable.BufferWithTime(timespan, timeshift) -} - -func (c *connectableObservable) BufferWithTimeOrCount(timespan Duration, count int) Observable { - return c.observable.BufferWithTimeOrCount(timespan, count) -} - -func (c *connectableObservable) Connect() Observer { - out := NewObserver() - go func() { - it := c.observable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - c.observersMutex.Lock() - for _, observer := range c.observers { - c.observersMutex.Unlock() - select { - case observer.getItemChannel() <- item: - default: - } - c.observersMutex.Lock() - } - c.observersMutex.Unlock() - } else { - break - } - } - }() - return out -} - -func (c *connectableObservable) Contains(equal Predicate) Single { - return c.observable.Contains(equal) -} - -func (c *connectableObservable) Count() Single { - return c.observable.Count() -} - -func (c *connectableObservable) DefaultIfEmpty(defaultValue interface{}) Observable { - return c.observable.DefaultIfEmpty(defaultValue) -} - -func (c *connectableObservable) Distinct(apply Function) Observable { - return c.observable.Distinct(apply) -} - -func (c *connectableObservable) DistinctUntilChanged(apply Function) Observable { - return c.observable.DistinctUntilChanged(apply) -} - -func (c *connectableObservable) DoOnEach(onNotification Consumer) Observable { - return c.observable.DoOnEach(onNotification) -} - -func (c *connectableObservable) ElementAt(index uint) Single { - return c.observable.ElementAt(index) -} - -func (c *connectableObservable) Filter(apply Predicate) Observable { - return c.observable.Filter(apply) -} - -func (c *connectableObservable) First() Observable { - return c.observable.First() -} - -func (c *connectableObservable) FirstOrDefault(defaultValue interface{}) Single { - return c.observable.FirstOrDefault(defaultValue) -} - -func (c *connectableObservable) FlatMap(apply func(interface{}) Observable, maxInParallel uint) Observable { - return c.observable.FlatMap(apply, maxInParallel) -} - -func (c *connectableObservable) ForEach(nextFunc NextFunc, errFunc ErrFunc, - doneFunc DoneFunc, opts ...Option) Observer { - return c.observable.ForEach(nextFunc, errFunc, doneFunc, opts...) -} - -func (c *connectableObservable) IgnoreElements() Observable { - return c.observable.IgnoreElements() -} - -func (c *connectableObservable) Last() Observable { - return c.observable.Last() -} - -func (c *connectableObservable) LastOrDefault(defaultValue interface{}) Single { - return c.observable.LastOrDefault(defaultValue) -} - -func (c *connectableObservable) Map(apply Function, opts ...Option) Observable { - return c.observable.Map(apply) -} - -func (c *connectableObservable) Marshal(m Marshaler, opts ...Option) Observable { - return c.observable.Marshal(m, opts...) -} - -func (c *connectableObservable) Max(comparator Comparator) OptionalSingle { - return c.observable.Max(comparator) -} - -func (c *connectableObservable) Min(comparator Comparator) OptionalSingle { - return c.observable.Min(comparator) -} - -func (c *connectableObservable) Send(ch chan<- interface{}) { - c.observable.Send(ch) -} - -func (c *connectableObservable) OnErrorResumeNext(resumeSequence ErrorToObservableFunction) Observable { - return c.observable.OnErrorResumeNext(resumeSequence) -} - -func (c *connectableObservable) OnErrorReturn(resumeFunc ErrorFunction) Observable { - return c.observable.OnErrorReturn(resumeFunc) -} - -func (c *connectableObservable) OnErrorReturnItem(item interface{}) Observable { - return c.observable.OnErrorReturnItem(item) -} - -func (c *connectableObservable) Publish() ConnectableObservable { - return c.observable.Publish() -} - -func (c *connectableObservable) Reduce(apply Function2) OptionalSingle { - return c.observable.Reduce(apply) -} - -func (c *connectableObservable) Repeat(count int64, frequency Duration) Observable { - return c.observable.Repeat(count, frequency) -} - -func (c *connectableObservable) Sample(obs Observable) Observable { - return c.observable.Sample(obs) -} - -func (c *connectableObservable) Scan(apply Function2) Observable { - return c.observable.Scan(apply) -} - -func (c *connectableObservable) SequenceEqual(obs Observable) Single { - return c.observable.SequenceEqual(obs) -} - -func (c *connectableObservable) Skip(nth uint) Observable { - return c.observable.Skip(nth) -} - -func (c *connectableObservable) SkipLast(nth uint) Observable { - return c.observable.SkipLast(nth) -} - -func (c *connectableObservable) SkipWhile(apply Predicate) Observable { - return c.observable.SkipWhile(apply) -} - -func (c *connectableObservable) StartWithItems(item interface{}, items ...interface{}) Observable { - return c.observable.StartWithItems(item, items...) -} - -func (c *connectableObservable) StartWithIterable(iterable Iterable) Observable { - return c.observable.StartWithIterable(iterable) -} - -func (c *connectableObservable) StartWithObservable(observable Observable) Observable { - return c.observable.StartWithObservable(observable) -} - -func (c *connectableObservable) Subscribe(handler EventHandler, opts ...Option) Observer { - observableOptions := ParseOptions(opts...) - - ob := NewObserver(handler) - var ch chan interface{} - if observableOptions.BackpressureStrategy() == Buffer { - ch = make(chan interface{}, observableOptions.Buffer()) - } else { - ch = make(chan interface{}) - } - ob.setItemChannel(ch) - c.observersMutex.Lock() - c.observers = append(c.observers, ob) - c.observersMutex.Unlock() - - go func() { - for item := range ch { - switch item := item.(type) { - case error: - err := ob.OnError(item) - if err != nil { - panic(errors.Wrap(err, "error while sending error item from connectable observable")) - } - return - default: - err := ob.OnNext(item) - if err != nil { - panic(errors.Wrap(err, "error while sending next item from connectable observable")) - } - } - } - }() - - return ob -} - -func (c *connectableObservable) SumFloat32() Single { - return c.observable.SumFloat32() -} - -func (c *connectableObservable) SumFloat64() Single { - return c.observable.SumFloat64() -} - -func (c *connectableObservable) SumInt64() Single { - return c.observable.SumInt64() -} - -func (c *connectableObservable) Take(nth uint) Observable { - return c.observable.Take(nth) -} - -func (c *connectableObservable) TakeLast(nth uint) Observable { - return c.observable.TakeLast(nth) -} - -func (c *connectableObservable) TakeUntil(apply Predicate) Observable { - return c.observable.TakeUntil(apply) -} - -func (c *connectableObservable) TakeWhile(apply Predicate) Observable { - return c.observable.TakeWhile(apply) -} - -func (c *connectableObservable) Timeout(ctx context.Context) Observable { - return c.observable.Timeout(ctx) -} - -func (c *connectableObservable) ToChannel(opts ...Option) Channel { - return c.observable.ToChannel(opts...) -} - -func (c *connectableObservable) ToMap(keySelector Function) Single { - return c.observable.ToMap(keySelector) -} - -func (c *connectableObservable) ToSlice() Single { - return c.observable.ToSlice() -} - -func (c *connectableObservable) ToMapWithValueSelector(keySelector, valueSelector Function) Single { - return c.observable.ToMapWithValueSelector(keySelector, valueSelector) -} - -func (c *connectableObservable) Unmarshal(unmarshaler Unmarshaler, factory func() interface{}, opts ...Option) Observable { - return c.observable.Unmarshal(unmarshaler, factory, opts...) -} - -func (c *connectableObservable) ZipFromObservable(publisher Observable, zipper Function2) Observable { - return c.observable.ZipFromObservable(publisher, zipper) -} - -func (c *connectableObservable) getCustomErrorStrategy() func(Observable, Observer, error) error { - return c.observable.getCustomErrorStrategy() -} - -func (c *connectableObservable) getNextStrategy() func(Observer, interface{}) error { - return c.observable.getNextStrategy() -} diff --git a/connectableobservable_test.go b/connectableobservable_test.go deleted file mode 100644 index b251270a..00000000 --- a/connectableobservable_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package rxgo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestConnectableObservable(t *testing.T) { - in := make(chan interface{}, 2) - out1 := make(chan interface{}, 2) - out2 := make(chan interface{}, 2) - obs := FromChannel(in).Publish() - obs.Subscribe(NextFunc(func(i interface{}) { - out1 <- i - }), WithBufferBackpressureStrategy(2)) - obs.Subscribe(NextFunc(func(i interface{}) { - out2 <- i - }), WithBufferBackpressureStrategy(2)) - in <- 1 - in <- 2 - _, _, cancelled := channel(out1, testWaitTime) - assert.True(t, cancelled) - obs.Connect() - item, _, _ := channel(out1, testWaitTime) - assert.Equal(t, 1, item) - item, _, _ = channel(out1, testWaitTime) - assert.Equal(t, 2, item) - item, _, _ = channel(out2, testWaitTime) - assert.Equal(t, 1, item) - item, _, _ = channel(out2, testWaitTime) - assert.Equal(t, 2, item) -} - -func TestConnectableObservable_Map(t *testing.T) { - obs := FromSlice([]interface{}{1, 2, 3, 5}).Publish().Map(func(i interface{}) interface{} { - return i.(int) + 1 - }) - AssertObservable(t, obs, HasItems(2, 3, 4, 6)) -} diff --git a/disposable.go b/disposable.go deleted file mode 100644 index 732b7eb6..00000000 --- a/disposable.go +++ /dev/null @@ -1,8 +0,0 @@ -package rxgo - -// Disposable allows to dispose a subscription -type Disposable interface { - Dispose() - IsDisposed() bool - Notify(chan<- struct{}) -} diff --git a/duration.go b/duration.go deleted file mode 100644 index e3bc55f7..00000000 --- a/duration.go +++ /dev/null @@ -1,39 +0,0 @@ -package rxgo - -import ( - "time" - - "github.com/stretchr/testify/mock" -) - -// Infinite represents an infinite wait time -var Infinite int64 = -1 - -// Duration represents a duration -type Duration interface { - duration() time.Duration -} - -type duration struct { - d time.Duration -} - -type mockDuration struct { - mock.Mock -} - -func (m *mockDuration) duration() time.Duration { - args := m.Called() - return args.Get(0).(time.Duration) -} - -func (d *duration) duration() time.Duration { - return d.d -} - -// WithDuration is a duration option -func WithDuration(d time.Duration) Duration { - return &duration{ - d: d, - } -} diff --git a/duration_test.go b/duration_test.go deleted file mode 100644 index 71104548..00000000 --- a/duration_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package rxgo - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestWithFrequency(t *testing.T) { - frequency := WithDuration(100 * time.Millisecond) - assert.Equal(t, 100*time.Millisecond, frequency.duration()) -} diff --git a/errors.go b/errors.go deleted file mode 100644 index c720ebe8..00000000 --- a/errors.go +++ /dev/null @@ -1,41 +0,0 @@ -package rxgo - -// CancelledIteratorError is triggered when an iterator is canceled -type CancelledIteratorError struct { -} - -// IllegalInputError is triggered when the observable receives an illegal input -type IllegalInputError struct { -} - -// IndexOutOfBoundError is triggered when the observable cannot access to the specified index -type IndexOutOfBoundError struct { -} - -// NoSuchElementError is triggered when an element does not exist -type NoSuchElementError struct { -} - -// CancelledSubscriptionError is triggered when a subscription was cancelled manually by an end user -type CancelledSubscriptionError struct { -} - -func (e *CancelledIteratorError) Error() string { - return "cancelled iterator" -} - -func (e *IllegalInputError) Error() string { - return "illegal input" -} - -func (e *IndexOutOfBoundError) Error() string { - return "index out of bound" -} - -func (e *NoSuchElementError) Error() string { - return "no such element" -} - -func (e *CancelledSubscriptionError) Error() string { - return "timeout" -} diff --git a/factory.go b/factory.go new file mode 100644 index 00000000..58fa6567 --- /dev/null +++ b/factory.go @@ -0,0 +1,9 @@ +package rxgo + +import "context" + +func FromChannel(ctx context.Context, next <-chan interface{}, errs <-chan error) Observable { + return &observable{ + iterable: newIterable(ctx, next, errs), + } +} diff --git a/factory_test.go b/factory_test.go new file mode 100644 index 00000000..00eed52b --- /dev/null +++ b/factory_test.go @@ -0,0 +1,22 @@ +package rxgo + +import ( + "context" + "testing" +) + +func Test_FromChannel(t *testing.T) { + next := make(chan interface{}) + errs := make(chan error) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + next <- 1 + next <- 2 + next <- 3 + cancel() + }() + + obs := FromChannel(ctx, next, errs) + assertObservable(t, ctx, obs, hasItems(1, 2, 3)) +} diff --git a/flatmap.go b/flatmap.go deleted file mode 100644 index 31343b0d..00000000 --- a/flatmap.go +++ /dev/null @@ -1,68 +0,0 @@ -package rxgo - -import ( - "context" - - "github.com/pkg/errors" - - "golang.org/x/sync/semaphore" -) - -// Transforms emitted items into Observables and flattens them into a single Observable. -// The maxInParallel argument controls how many transformed Observables are processed in parallel. -// For an example, please take a look at flatmap_slice_test.go in the examples directory. -func (o *observable) FlatMap(apply func(interface{}) Observable, maxInParallel uint) Observable { - return o.flatMap(apply, maxInParallel, flatObservedSequence) -} - -func (o *observable) flatMap( - apply func(interface{}) Observable, - maxInParallel uint, - flatteningFunc func(out chan interface{}, o Observable, apply func(interface{}) Observable, maxInParallel uint)) Observable { - - out := make(chan interface{}) - - if maxInParallel < 1 { - maxInParallel = 1 - } - - go flatteningFunc(out, o, apply, maxInParallel) - - return newColdObservableFromChannel(out) -} - -func flatObservedSequence(out chan interface{}, o Observable, apply func(interface{}) Observable, maxInParallel uint) { - ctx := context.TODO() - sem := semaphore.NewWeighted(int64(maxInParallel)) - - defer close(out) - emissionObserver := newFlattenEmissionObserver(out) - - it := o.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - sequence := apply(item) - err := sem.Acquire(ctx, 1) - if err != nil { - panic(errors.Wrap(err, "error while acquiring semaphore from flatmap")) - } - go func(copy Observable) { - defer sem.Release(1) - copy.Subscribe(emissionObserver).Block() - }(sequence) - } else { - break - } - } - - err := sem.Acquire(ctx, int64(maxInParallel)) - if err != nil { - panic(errors.Wrap(err, "error while acquiring semaphore from flatmap")) - } -} - -func newFlattenEmissionObserver(out chan interface{}) Observer { - return NewObserver(NextFunc(func(element interface{}) { - out <- element - })) -} diff --git a/flatmap_test.go b/flatmap_test.go deleted file mode 100644 index 6f031014..00000000 --- a/flatmap_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package rxgo - -import ( - "testing" - - "github.com/stretchr/testify/mock" -) - -func TestFlatMapCompletesWhenSequenceIsEmpty(t *testing.T) { - // given - emissionObserver := NewObserverMock() - - // and empty sequence - sequence := Empty() - - // and flattens the sequence with identity - sequence = sequence.FlatMap(identity, 1) - - // when subscribes to the sequence - sequence.Subscribe(emissionObserver.Capture()).Block() - - // then completes without any emission - emissionObserver.AssertNotCalled(t, "OnNext", mock.Anything) - emissionObserver.AssertNotCalled(t, "OnError", mock.Anything) - emissionObserver.AssertCalled(t, "OnDone") -} - -func TestFlatMapReturnsSameElementBecauseIdentifyApplied(t *testing.T) { - // given - emissionObserver := NewObserverMock() - - // and sequence containing one element - element := 1 - sequence := Just(element) - - // and flattens the sequence with identity - sequence = sequence.FlatMap(identity, 1) - - // when subscribes to the sequence - sequence.Subscribe(emissionObserver.Capture()).Block() - - // then completes with emission of the same element - emissionObserver.AssertNotCalled(t, "OnError", mock.Anything) - emissionObserver.AssertCalled(t, "OnNext", element) - emissionObserver.AssertCalled(t, "OnDone") -} - -func TestFlatMapReturnsSliceElements(t *testing.T) { - // given - emissionObserver := NewObserverMock() - - // and sequence containing slice with few elements - element1 := "element1" - element2 := "element2" - element3 := "element3" - slice := &([]string{element1, element2, element3}) - sequence := Just(slice) - - // and flattens the sequence with identity - sequence = sequence.FlatMap(flattenThreeElementSlice, 1) - - // when subscribes to the sequence - sequence.Subscribe(emissionObserver.Capture()).Block() - - // then completes with emission of flatten elements - emissionObserver.AssertNotCalled(t, "OnError", mock.Anything) - emissionObserver.AssertNotCalled(t, "OnNext", slice) - emissionObserver.AssertCalled(t, "OnNext", element1) - emissionObserver.AssertCalled(t, "OnNext", element2) - emissionObserver.AssertCalled(t, "OnNext", element3) - emissionObserver.AssertCalled(t, "OnDone") -} - -// TODO To be reimplemented -//func TestFlatMapUsesForParallelProcessingAtLeast1Process(t *testing.T) { -// // given -// emissionObserver := observer.NewObserverMock() -// -// // and -// var maxInParallel uint = 0 -// -// // and -// var requestedMaxInParallel uint = 0 -// flatteningFuncMock := func(out chan interface{}, o Observable, apply func(interface{}) Observable, maxInParallel uint) { -// requestedMaxInParallel = maxInParallel -// flatObservedSequence(out, o, apply, maxInParallel) -// } -// -// // and flattens the sequence with identity -// sequence := someSequence.FlatMap(identity, maxInParallel, flatteningFuncMock) -// -// // when subscribes to the sequence -// <-sequence.Subscribe(emissionObserver.Capture()) -// -// // then completes with emission of the same element -// assert.Equal(t, uint(1), requestedMaxInParallel) -//} - -func identity(el interface{}) Observable { - return Just(el) -} - -func flattenThreeElementSlice(el interface{}) Observable { - slice := *(el.(*[]string)) - return Just(slice[0], slice[1], slice[2]) -} diff --git a/fx.go b/fx.go deleted file mode 100644 index 36809c94..00000000 --- a/fx.go +++ /dev/null @@ -1,57 +0,0 @@ -package rxgo - -// Comparison is the results of the Comparator function. -type Comparison uint32 - -const ( - // Equals represents an equality - Equals Comparison = iota - // Greater represents an item greater than another - Greater - // Smaller represents an item smaller than another - Smaller -) - -type ( - // Predicate defines a func that returns a bool from an input value. - Predicate func(interface{}) bool - - // BiPredicate defines a func that returns a bool from two input values. - BiPredicate func(interface{}, interface{}) bool - - // Comparator defines a func that returns: - // Equals if two elements are equals - // Greater if the first argument is greater than the second - // Smaller if the first argument is smaller than the second - Comparator func(interface{}, interface{}) Comparison - - // Consumer defines a func that accepts a single value - Consumer func(interface{}) - - // ErrorFunction defines a function that computes a value from an error. - ErrorFunction func(error) interface{} - - // ErrorToObservableFunction defines a function that computes an observable from an error. - ErrorToObservableFunction func(error) Observable - - // Function defines a function that computes a value from an input value. - Function func(interface{}) interface{} - - // Function2 defines a function that computes a value from two input values. - Function2 func(interface{}, interface{}) interface{} - - // FunctionN defines a function that computes a value from N input values. - FunctionN func(...interface{}) interface{} - - // Supplier defines a function that supplies a result from nothing. - Supplier func() interface{} - - // Channel defines a type representing chan interface{} - Channel chan interface{} - - // Marshaler defines a marshaler type (interface{} to []byte) - Marshaler func(interface{}) ([]byte, error) - - // Unmarshaler defines an unmarshaler type ([]byte to interface) - Unmarshaler func([]byte, interface{}) error -) diff --git a/go.mod b/go.mod index 1d579f79..55343308 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,4 @@ module github.com/reactivex/rxgo go 1.13 -require ( - github.com/pkg/errors v0.8.1 - github.com/stretchr/testify v1.3.0 - golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f -) +require github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index aabf8196..68164f93 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,12 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00= -golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/handlers.go b/handlers.go deleted file mode 100644 index ed7f7e4d..00000000 --- a/handlers.go +++ /dev/null @@ -1,42 +0,0 @@ -package rxgo - -type ( - // NextFunc handles a next item in a stream. - NextFunc func(interface{}) - - // ErrFunc handles an error in a stream. - ErrFunc func(error) - - // DoneFunc handles the end of a stream. - DoneFunc func() -) - -// EventHandler type is implemented by all handlers and Observer. -type EventHandler interface { - Handle(interface{}) -} - -// Handle registers NextFunc to EventHandler. -func (handle NextFunc) Handle(item interface{}) { - switch item := item.(type) { - case error: - return - default: - handle(item) - } -} - -// Handle registers ErrFunc to EventHandler. -func (handle ErrFunc) Handle(item interface{}) { - switch item := item.(type) { - case error: - handle(item) - default: - return - } -} - -// Handle registers DoneFunc to EventHandler. -func (handle DoneFunc) Handle(item interface{}) { - handle() -} diff --git a/handlers_test.go b/handlers_test.go deleted file mode 100644 index 1845c119..00000000 --- a/handlers_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package rxgo - -// -//func TestHandlersImplementEventHandler(t *testing.T) { -// assert := assert.New(t) -// assert.Implements((*rxgo.EventHandler)(nil), (*NextFunc)(nil)) -// assert.Implements((*rxgo.EventHandler)(nil), (*ErrFunc)(nil)) -// assert.Implements((*rxgo.EventHandler)(nil), (*DoneFunc)(nil)) -//} -// -//func TestNextFuncHandleMethod(t *testing.T) { -// assert := assert.New(t) -// -// var ( -// num = 1 -// word = "Hello" -// pi = 3.1416 -// ru = 'ด' -// err = errors.New("Anonymous error") -// ) -// -// samples := []interface{}{} -// -// // Append each item to samples slice -// nextf := NextFunc(func(item interface{}) { -// samples = append(samples, item) -// }) -// -// nextHandleTests := []interface{}{num, word, pi, ru, err} -// -// for _, tt := range nextHandleTests { -// nextf.Handle(tt) -// } -// -// expected := []interface{}{num, word, pi, ru} -// assert.Exactly(samples, expected) -//} -// -//func TestErrFuncHandleMethod(t *testing.T) { -// assert := assert.New(t) -// -// var ( -// num = 1 -// word = "Hello" -// pi = 3.1416 -// ru = 'ด' -// ) -// -// samples := []error{} -// -// errf := ErrFunc(func(err error) { -// samples = append(samples, err) -// }) -// -// errHandleTests := []interface{}{ -// fmt.Errorf("Integer %d error", num), -// fmt.Errorf("String %s error", word), -// pi, -// fmt.Errorf("Rune %U error", ru), -// } -// -// for _, tt := range errHandleTests { -// errf.Handle(tt) -// } -// -// expected := []error{ -// fmt.Errorf("Integer %d error", num), -// fmt.Errorf("String %s error", word), -// fmt.Errorf("Rune %U error", ru), -// } -// -// assert.Exactly(samples, expected) -//} -// -//func TestDoneFuncHandleMethod(t *testing.T) { -// assert := assert.New(t) -// -// var ( -// num = 1 -// word = "Hello" -// pi = 3.1416 -// ru = 'ด' -// ) -// -// samples := []string{} -// -// errf := DoneFunc(func() { -// samples = append(samples, "DONE") -// }) -// -// doneHandleTests := []interface{}{num, word, pi, ru} -// -// for n, tt := range doneHandleTests { -// errf.Handle(tt) -// assert.Equal(samples[n], "DONE") -// } -//} diff --git a/iterable.go b/iterable.go index 9c7351b5..592080cb 100644 --- a/iterable.go +++ b/iterable.go @@ -2,67 +2,30 @@ package rxgo import "context" -// Iterable creates an iterator type Iterable interface { - Iterator(ctx context.Context) Iterator + Done() <-chan struct{} + Error() <-chan error + Next() <-chan interface{} } -type iterableFromChannel struct { - ch chan interface{} +type Source struct { + ctx context.Context + next <-chan interface{} + errs <-chan error } -type iterableFromSlice struct { - s []interface{} +func newIterable(ctx context.Context, next <-chan interface{}, errs <-chan error) Iterable { + return &Source{ctx: ctx, next: next, errs: errs} } -type iterableFromRange struct { - start int - count int +func (s *Source) Done() <-chan struct{} { + return s.ctx.Done() } -type iterableFromFunc struct { - f func(chan interface{}) +func (s *Source) Error() <-chan error { + return s.errs } -func (it *iterableFromFunc) Iterator(ctx context.Context) Iterator { - out := make(chan interface{}) - go it.f(out) - return newIteratorFromChannel(out) -} - -func (it *iterableFromChannel) Iterator(ctx context.Context) Iterator { - return newIteratorFromChannel(it.ch) -} - -func (it *iterableFromSlice) Iterator(ctx context.Context) Iterator { - return newIteratorFromSlice(it.s) -} - -func (it *iterableFromRange) Iterator(ctx context.Context) Iterator { - return newIteratorFromRange(it.start-1, it.start+it.count) -} - -func newIterableFromChannel(ch chan interface{}) Iterable { - return &iterableFromChannel{ - ch: ch, - } -} - -func newIterableFromSlice(s []interface{}) Iterable { - return &iterableFromSlice{ - s: s, - } -} - -func newIterableFromRange(start, count int) Iterable { - return &iterableFromRange{ - start: start, - count: count, - } -} - -func newIterableFromFunc(f func(chan interface{})) Iterable { - return &iterableFromFunc{ - f: f, - } +func (s *Source) Next() <-chan interface{} { + return s.next } diff --git a/iterator.go b/iterator.go deleted file mode 100644 index 6790df30..00000000 --- a/iterator.go +++ /dev/null @@ -1,81 +0,0 @@ -package rxgo - -import ( - "context" - - "github.com/pkg/errors" -) - -// Iterator allows to iterates on an given structure -type Iterator interface { - Next(ctx context.Context) (interface{}, error) -} - -type iteratorFromChannel struct { - ch chan interface{} - ctx context.Context - cancelFunc context.CancelFunc -} - -type iteratorFromRange struct { - current int - end int // Included -} - -type iteratorFromSlice struct { - index int - s []interface{} -} - -func (it *iteratorFromChannel) Next(ctx context.Context) (interface{}, error) { - select { - case <-ctx.Done(): - return nil, &CancelledSubscriptionError{} - case <-it.ctx.Done(): - return nil, &CancelledIteratorError{} - case next, ok := <-it.ch: - if ok { - return next, nil - } - return nil, &NoSuchElementError{} - } -} - -func (it *iteratorFromRange) Next(ctx context.Context) (interface{}, error) { - it.current++ - if it.current <= it.end { - return it.current, nil - } - return nil, errors.Wrap(&NoSuchElementError{}, "range does not contain anymore elements") -} - -func (it *iteratorFromSlice) Next(ctx context.Context) (interface{}, error) { - it.index++ - if it.index < len(it.s) { - return it.s[it.index], nil - } - return nil, errors.Wrap(&NoSuchElementError{}, "slice does not contain anymore elements") -} - -func newIteratorFromChannel(ch chan interface{}) Iterator { - ctx, cancel := context.WithCancel(context.Background()) - return &iteratorFromChannel{ - ch: ch, - ctx: ctx, - cancelFunc: cancel, - } -} - -func newIteratorFromRange(start, end int) Iterator { - return &iteratorFromRange{ - current: start, - end: end, - } -} - -func newIteratorFromSlice(s []interface{}) Iterator { - return &iteratorFromSlice{ - index: -1, - s: s, - } -} diff --git a/iterator_test.go b/iterator_test.go deleted file mode 100644 index 31d1f445..00000000 --- a/iterator_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package rxgo - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestIteratorFromChannel(t *testing.T) { - ch := make(chan interface{}, 1) - it := newIteratorFromChannel(ch) - - ch <- 1 - next, err := it.Next(context.Background()) - assert.Nil(t, err) - assert.Equal(t, 1, next) - - ch <- 2 - next, err = it.Next(context.Background()) - assert.Nil(t, err) - assert.Equal(t, 2, next) - - close(ch) - _, err = it.Next(context.Background()) - assert.NotNil(t, err) -} - -func TestIteratorFromSlice(t *testing.T) { - it := newIteratorFromSlice([]interface{}{1, 2, 3}) - - next, err := it.Next(context.Background()) - assert.Nil(t, err) - assert.Equal(t, 1, next) - - next, err = it.Next(context.Background()) - assert.Nil(t, err) - assert.Equal(t, 2, next) - - next, err = it.Next(context.Background()) - assert.Nil(t, err) - assert.Equal(t, 3, next) - - _, err = it.Next(context.Background()) - assert.NotNil(t, err) -} diff --git a/mock.go b/mock.go deleted file mode 100644 index 1bb7dc81..00000000 --- a/mock.go +++ /dev/null @@ -1,343 +0,0 @@ -package rxgo - -import ( - "bufio" - "context" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - "github.com/stretchr/testify/mock" -) - -type mockData uint32 - -const ( - signalCh mockData = iota -) - -var errMock = errors.New("") - -type mockContext struct { - mock.Mock -} - -type mockIterable struct { - iterator Iterator -} - -type mockIterator struct { - mock.Mock -} - -type mockTask struct { - index int - item int - error error - close bool - context bool -} - -type mockType struct { - observable bool - context bool - index int -} - -func (m *mockContext) Deadline() (deadline time.Time, ok bool) { - panic("implement me") -} - -func (m *mockContext) Done() <-chan struct{} { - outputs := m.Called() - return outputs.Get(0).(chan struct{}) -} - -func (m *mockContext) Err() error { - panic("implement me") -} - -func (m *mockContext) Value(key interface{}) interface{} { - panic("implement me") -} - -func (s *mockIterable) Iterator(ctx context.Context) Iterator { - return s.iterator -} - -func newMockObservable(iterator Iterator) Observable { - return &observable{ - subscribeStrategy: coldSubscribe(), - coldIterable: &mockIterable{ - iterator: iterator, - }, - } -} - -func countTab(line string) int { - i := 0 - for _, runeValue := range line { - if runeValue == '\t' { - i++ - } else { - break - } - } - return i -} - -// TODO Causality with more than two observables -func causality(in string) ([]Observable, []context.Context) { - scanner := bufio.NewScanner(strings.NewReader(in)) - types := make([]mockType, 0) - tasks := make([]mockTask, 0) - // Search header - countObservables := 0 - countContexts := 0 - for scanner.Scan() { - s := scanner.Text() - if s == "" { - continue - } - splits := strings.Split(s, "\t") - for _, split := range splits { - switch split { - case "o": - types = append(types, mockType{ - observable: true, - index: countObservables, - }) - countObservables++ - case "c": - types = append(types, mockType{ - context: true, - index: countContexts, - }) - countContexts++ - default: - panic("unknown type: " + split) - } - } - break - } - for scanner.Scan() { - s := scanner.Text() - if s == "" { - continue - } - index := countTab(s) - v := strings.TrimSpace(s) - if types[index].observable { - switch v { - case "x": - tasks = append(tasks, mockTask{ - index: types[index].index, - close: true, - }) - case "e": - tasks = append(tasks, mockTask{ - index: types[index].index, - error: errMock, - }) - default: - n, err := strconv.Atoi(v) - if err != nil { - panic(errors.Wrapf(err, "conversion error")) - } - tasks = append(tasks, mockTask{ - index: types[index].index, - item: n, - }) - } - } else if types[index].context { - tasks = append(tasks, mockTask{ - index: types[index].index, - context: true, - }) - } else { - panic("unknwon type") - } - } - - iterators := make([]*mockIterator, 0, countObservables) - for i := 0; i < countObservables; i++ { - iterators = append(iterators, new(mockIterator)) - } - iteratorCalls := make([]*mock.Call, countObservables) - - contexts := make([]*mockContext, 0, countContexts) - for i := 0; i < countContexts; i++ { - contexts = append(contexts, new(mockContext)) - } - iteratorContexts := make([]*mock.Call, countContexts) - - lastObservableType := -1 - if tasks[0].context { - notif := make(chan struct{}, 1) - call := contexts[0].On("Done").Once().Return(notif) - iteratorContexts[0] = call - } else { - item, err := args(tasks[0]) - call := iterators[0].On("Next", mock.Anything).Once().Return(item, err) - iteratorCalls[0] = call - lastObservableType = tasks[0].index - } - - var lastCh chan struct{} - for i := 1; i < len(tasks); i++ { - t := tasks[i] - index := t.index - - if t.context { - ctx := contexts[index] - notif := make(chan struct{}, 1) - lastObservableType = -1 - if lastCh == nil { - ch := make(chan struct{}, 1) - lastCh = ch - if iteratorContexts[index] == nil { - iteratorContexts[index] = ctx.On("Done").Once().Return(notif). - Run(func(args mock.Arguments) { - preDone(notif, ch, nil) - }) - } else { - iteratorContexts[index].On("Done").Once().Return(notif). - Run(func(args mock.Arguments) { - preDone(notif, ch, nil) - }) - } - } else { - var ch chan struct{} - // If this is the latest task we do not set any wait channel - if i != len(tasks)-1 { - ch = make(chan struct{}, 1) - } - previous := lastCh - if iteratorContexts[index] == nil { - iteratorContexts[index] = ctx.On("Done").Once().Return(notif). - Run(func(args mock.Arguments) { - preDone(notif, ch, previous) - }) - } else { - iteratorContexts[index].On("Done").Once().Return(notif). - Run(func(args mock.Arguments) { - preDone(notif, ch, previous) - }) - } - lastCh = ch - } - } else { - obs := iterators[index] - item, err := args(t) - if lastObservableType == t.index { - if iteratorCalls[index] == nil { - iteratorCalls[index] = obs.On("Next", mock.Anything).Once().Return(item, err) - } else { - iteratorCalls[index].On("Next", mock.Anything).Once().Return(item, err) - } - } else { - lastObservableType = t.index - if lastCh == nil { - ch := make(chan struct{}) - lastCh = ch - if iteratorCalls[index] == nil { - iteratorCalls[index] = obs.On("Next", mock.Anything).Once().Return(item, err). - Run(func(args mock.Arguments) { - preNext(args, ch, nil) - }) - } else { - iteratorCalls[index].On("Next", mock.Anything).Once().Return(item, err). - Run(func(args mock.Arguments) { - preNext(args, ch, nil) - }) - } - } else { - var ch chan struct{} - // If this is the latest task we do not set any wait channel - if i != len(tasks)-1 { - ch = make(chan struct{}) - } - previous := lastCh - if iteratorCalls[index] == nil { - iteratorCalls[index] = obs.On("Next", mock.Anything).Once().Return(item, err). - Run(func(args mock.Arguments) { - preNext(args, ch, previous) - }) - } else { - iteratorCalls[index].On("Next", mock.Anything).Once().Return(item, err). - Run(func(args mock.Arguments) { - preNext(args, ch, previous) - }) - } - lastCh = ch - } - } - } - - } - - observables := make([]Observable, 0, len(iterators)) - for _, iterator := range iterators { - observables = append(observables, newMockObservable(iterator)) - } - ctxs := make([]context.Context, 0, len(iterators)) - for _, c := range contexts { - ctxs = append(ctxs, c) - } - return observables, ctxs -} - -func args(t mockTask) (interface{}, error) { - if t.close { - return nil, &NoSuchElementError{} - } - if t.error != nil { - return t.error, nil - } - return t.item, nil -} - -func preDone(notif chan struct{}, wait chan struct{}, send chan struct{}) { - if send != nil { - send <- struct{}{} - } - if wait != nil { - <-wait - } - notif <- struct{}{} -} - -func preNext(args mock.Arguments, wait chan struct{}, send chan struct{}) { - if send != nil { - send <- struct{}{} - } - if wait == nil { - return - } - if len(args) == 1 { - if ctx, ok := args[0].(context.Context); ok { - if sig, ok := ctx.Value(signalCh).(chan struct{}); ok { - select { - case <-wait: - case <-ctx.Done(): - sig <- struct{}{} - } - return - } - } - } - <-wait -} - -func (m *mockIterator) Next(ctx context.Context) (interface{}, error) { - sig := make(chan struct{}, 1) - defer close(sig) - outputs := m.Called(context.WithValue(ctx, signalCh, sig)) - select { - case <-sig: - return nil, &CancelledIteratorError{} - default: - return outputs.Get(0), outputs.Error(1) - } -} diff --git a/observable.go b/observable.go index 0cde24a1..a0eb241b 100644 --- a/observable.go +++ b/observable.go @@ -1,1862 +1,80 @@ package rxgo import ( - "container/ring" "context" - "fmt" - "sync" - "time" - - "github.com/pkg/errors" ) -// Observable is a basic observable interface type Observable interface { Iterable - All(predicate Predicate) Single - AverageFloat32() Single - AverageFloat64() Single - AverageInt() Single - AverageInt8() Single - AverageInt16() Single - AverageInt32() Single - AverageInt64() Single - BufferWithCount(count, skip int) Observable - BufferWithTime(timespan, timeshift Duration) Observable - BufferWithTimeOrCount(timespan Duration, count int) Observable - Contains(equal Predicate) Single - Count() Single - DefaultIfEmpty(defaultValue interface{}) Observable - Distinct(apply Function) Observable - DistinctUntilChanged(apply Function) Observable - DoOnEach(onNotification Consumer) Observable - ElementAt(index uint) Single - Filter(apply Predicate) Observable - First() Observable - FirstOrDefault(defaultValue interface{}) Single - FlatMap(apply func(interface{}) Observable, maxInParallel uint) Observable - ForEach(nextFunc NextFunc, errFunc ErrFunc, - doneFunc DoneFunc, opts ...Option) Observer - IgnoreElements() Observable - Last() Observable - LastOrDefault(defaultValue interface{}) Single - Map(apply Function, opts ...Option) Observable - Marshal(Marshaler, ...Option) Observable - Max(comparator Comparator) OptionalSingle - Min(comparator Comparator) OptionalSingle - OnErrorResumeNext(resumeSequence ErrorToObservableFunction) Observable - OnErrorReturn(resumeFunc ErrorFunction) Observable - OnErrorReturnItem(item interface{}) Observable - Publish() ConnectableObservable - Reduce(apply Function2) OptionalSingle - Repeat(count int64, frequency Duration) Observable - Sample(obs Observable) Observable - Scan(apply Function2) Observable - SequenceEqual(obs Observable) Single - Send(chan<- interface{}) - Skip(nth uint) Observable - SkipLast(nth uint) Observable - SkipWhile(apply Predicate) Observable - StartWithItems(item interface{}, items ...interface{}) Observable - StartWithIterable(iterable Iterable) Observable - StartWithObservable(observable Observable) Observable - // TODO Pass a context to cancel the subscription - Subscribe(handler EventHandler, opts ...Option) Observer - SumFloat32() Single - SumFloat64() Single - SumInt64() Single - Take(nth uint) Observable - TakeLast(nth uint) Observable - TakeUntil(apply Predicate) Observable - TakeWhile(apply Predicate) Observable - Timeout(ctx context.Context) Observable - ToChannel(opts ...Option) Channel - ToMap(keySelector Function) Single - ToMapWithValueSelector(keySelector, valueSelector Function) Single - ToSlice() Single - Unmarshal(Unmarshaler, func() interface{}, ...Option) Observable - ZipFromObservable(publisher Observable, zipper Function2) Observable - getCustomErrorStrategy() func(Observable, Observer, error) error - getNextStrategy() func(Observer, interface{}) error + ForEach(ctx context.Context, nextFunc NextFunc, errFunc ErrFunc, doneFunc DoneFunc) + Map(ctx context.Context, apply Func) Observable } -// observable is a structure handling a channel of interface{} and implementing Observable type observable struct { - // subscribeStrategy represents the observable strategy once an observer subscribes to the observable - subscribeStrategy func(o *observable, ob Observer) - // customErrorStrategy represents a custom strategy if the observable encounters an error - customErrorStrategy func(Observable, Observer, error) error - // nextStrategy represents the stategy for the next item - nextStrategy func(Observer, interface{}) error - // errorOnSubscription defines an error to be sent to the observer once it subscribes to the observable - errorOnSubscription error - // coldIterable is used while pulling data - coldIterable Iterable - // hotItemChannel is used while data are pushed from the observable - hotItemChannel <-chan interface{} - // hotObservers list the observer registered to the hot observable with none bp mode. - hotObservers []Observer - // hotObservers protects hotObservers from concurrent accesses - hotObserversMutex sync.Mutex - // hotSubscribers list the subscribers registered to the hot observable with none buffer bp mode. - hotSubscribers []chan<- interface{} -} - -func onErrorReturn(f ErrorFunction) func(Observable, Observer, error) error { - return func(_ Observable, observer Observer, err error) error { - return observer.OnNext(f(err)) - } -} - -func onErrorResumeNext(f ErrorToObservableFunction) func(Observable, Observer, error) error { - return func(observable Observable, observer Observer, err error) error { - return iterate(f(err), observer) - } -} - -func onErrorReturnItem(item interface{}) func(Observable, Observer, error) error { - return func(observable Observable, observer Observer, _ error) error { - return observer.OnNext(item) - } -} - -func onNextIgnore() func(Observer, interface{}) error { - return func(observer Observer, item interface{}) error { - return nil - } -} - -func onNext() func(Observer, interface{}) error { - return func(observer Observer, item interface{}) error { - return observer.OnNext(item) - } -} - -func iterate(observable Observable, observer Observer) error { - it := observable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case error: - errorStrategy := observable.getCustomErrorStrategy() - if errorStrategy == nil { - err := observer.OnError(item) - if err != nil { - return err - } - return item - } - err := errorStrategy(observable, observer, item) - if err != nil { - return err - } - default: - err := observable.getNextStrategy()(observer, item) - if err != nil { - return err - } - } - } else { - break - } - } - return nil -} - -func coldSubscribe() func(o *observable, ob Observer) { - return func(o *observable, ob Observer) { - go func() { - e := iterate(o, ob) - if e == nil { - err := ob.OnDone() - if err != nil { - panic(errors.Wrap(err, "error while sending done signal from observable")) - } - } - }() - } -} - -func hotSubscribeStrategyNoneBackPressure() func(o *observable, ob Observer) { - return func(o *observable, ob Observer) { - o.hotObserversMutex.Lock() - o.hotObservers = append(o.hotObservers, ob) - o.hotObserversMutex.Unlock() - } -} - -func hotSubscribeStrategyBufferBackPressure(bpBuffer int) func(o *observable, ob Observer) { - return func(o *observable, ob Observer) { - o.hotObserversMutex.Lock() - ch := make(chan interface{}, bpBuffer) - go func() { - for item := range ch { - ob.Handle(item) - } - }() - o.hotSubscribers = append(o.hotSubscribers, ch) - o.hotObserversMutex.Unlock() - } -} - -// Compares first items of two sequences and returns true if they are equal and false if -// they are not. Besides, it returns two new sequences - input sequences without compared items. -func popAndCompareFirstItems( - inputSequence1 []interface{}, - inputSequence2 []interface{}) (bool, []interface{}, []interface{}) { - if len(inputSequence1) > 0 && len(inputSequence2) > 0 { - s1, sequence1 := inputSequence1[0], inputSequence1[1:] - s2, sequence2 := inputSequence2[0], inputSequence2[1:] - return s1 == s2, sequence1, sequence2 - } - return true, inputSequence1, inputSequence2 -} - -func (o *observable) Iterator(ctx context.Context) Iterator { - return o.coldIterable.Iterator(ctx) -} - -func (o *observable) All(predicate Predicate) Single { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if !predicate(item) { - out <- false - close(out) - return - } - } else { - break - } - } - out <- true - close(out) - } - return newColdSingle(f) -} - -// AverageFloat32 calculates the average of numbers emitted by an Observable and emits this average float32. -func (o *observable) AverageFloat32() Single { - f := func(out chan interface{}) { - var sum float32 - var count float32 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if v, ok := item.(float32); ok { - sum += v - count++ - } else { - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: float32, got: %t", item)) - close(out) - return - } - } else { - break - } - } - if count == 0 { - out <- 0 - } else { - out <- sum / count - } - close(out) - } - return newColdSingle(f) -} - -// AverageInt calculates the average of numbers emitted by an Observable and emits this average int. -func (o *observable) AverageInt() Single { - f := func(out chan interface{}) { - sum := 0 - count := 0 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if v, ok := item.(int); ok { - sum += v - count++ - } else { - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: int, got: %t", item)) - close(out) - return - } - } else { - break - } - } - if count == 0 { - out <- 0 - } else { - out <- sum / count - } - close(out) - } - return newColdSingle(f) -} - -// AverageInt8 calculates the average of numbers emitted by an Observable and emits this average int8. -func (o *observable) AverageInt8() Single { - f := func(out chan interface{}) { - var sum int8 - var count int8 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if v, ok := item.(int8); ok { - sum += v - count++ - } else { - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: int8, got: %t", item)) - close(out) - return - } - } else { - break - } - } - if count == 0 { - out <- 0 - } else { - out <- sum / count - } - close(out) - } - return newColdSingle(f) -} - -// AverageFloat64 calculates the average of numbers emitted by an Observable and emits this average float64. -func (o *observable) AverageFloat64() Single { - f := func(out chan interface{}) { - var sum float64 - var count float64 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if v, ok := item.(float64); ok { - sum += v - count++ - } else { - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: float64, got: %t", item)) - close(out) - return - } - } else { - break - } - } - if count == 0 { - out <- 0 - } else { - out <- sum / count - } - close(out) - } - return newColdSingle(f) -} - -// AverageInt16 calculates the average of numbers emitted by an Observable and emits this average int16. -func (o *observable) AverageInt16() Single { - f := func(out chan interface{}) { - var sum int16 - var count int16 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if v, ok := item.(int16); ok { - sum += v - count++ - } else { - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: int16, got: %t", item)) - close(out) - return - } - } else { - break - } - } - if count == 0 { - out <- 0 - } else { - out <- sum / count - } - close(out) - } - return newColdSingle(f) -} - -// AverageInt32 calculates the average of numbers emitted by an Observable and emits this average int32. -func (o *observable) AverageInt32() Single { - f := func(out chan interface{}) { - var sum int32 - var count int32 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if v, ok := item.(int32); ok { - sum += v - count++ - } else { - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: int32, got: %t", item)) - close(out) - return - } - } else { - break - } - } - if count == 0 { - out <- 0 - } else { - out <- sum / count - } - close(out) - } - return newColdSingle(f) -} - -// AverageInt64 calculates the average of numbers emitted by an Observable and emits this average int64. -func (o *observable) AverageInt64() Single { - f := func(out chan interface{}) { - var sum int64 - var count int64 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if v, ok := item.(int64); ok { - sum += v - count++ - } else { - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: int64, got: %t", item)) - close(out) - return - } - } else { - break - } - } - if count == 0 { - out <- 0 - } else { - out <- sum / count - } - close(out) - } - return newColdSingle(f) + iterable Iterable + handler Handler } -// BufferWithCount returns an Observable that emits buffers of items it collects -// from the source Observable. -// The resulting Observable emits buffers every skip items, each containing a slice of count items. -// When the source Observable completes or encounters an error, -// the resulting Observable emits the current buffer and propagates -// the notification from the source Observable. -func (o *observable) BufferWithCount(count, skip int) Observable { - f := func(out chan interface{}) { - if count <= 0 { - out <- errors.Wrap(&IllegalInputError{}, "count must be positive") - close(out) - return - } - - if skip <= 0 { - out <- errors.Wrap(&IllegalInputError{}, "skip must be positive") - close(out) - return - } - - buffer := make([]interface{}, count) - iCount := 0 - iSkip := 0 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case error: - if iCount != 0 { - out <- buffer[:iCount] - } - out <- item - close(out) - return - default: - if iCount >= count { // Skip - iSkip++ - } else { // Add to buffer - buffer[iCount] = item - iCount++ - iSkip++ - } - - if iSkip == skip { // Send current buffer - out <- buffer - buffer = make([]interface{}, count) - iCount = 0 - iSkip = 0 - } - } - } else { - break - } - } - if iCount != 0 { - out <- buffer[:iCount] - } +func newObservable(ctx context.Context, source Observable, handler Handler) Observable { + next := make(chan interface{}) + errs := make(chan error) - close(out) - } - return newColdObservableFromFunction(f) -} + go handler(ctx, source.Next(), source.Error(), next, errs) -// BufferWithTime returns an Observable that emits buffers of items it collects from the source -// Observable. The resulting Observable starts a new buffer periodically, as determined by the -// timeshift argument. It emits each buffer after a fixed timespan, specified by the timespan argument. -// When the source Observable completes or encounters an error, the resulting Observable emits -// the current buffer and propagates the notification from the source Observable. -func (o *observable) BufferWithTime(timespan, timeshift Duration) Observable { - f := func(out chan interface{}) { - if timespan == nil || timespan.duration() == 0 { - out <- errors.Wrap(&IllegalInputError{}, "timespan must no be nil") - close(out) - return - } - - if timeshift == nil { - timeshift = WithDuration(0) - } - - var mux sync.Mutex - var listenMutex sync.Mutex - buffer := make([]interface{}, 0) - stop := false - listen := true - - // First goroutine in charge to check the timespan - go func() { - for { - time.Sleep(timespan.duration()) - mux.Lock() - if !stop { - out <- buffer - buffer = make([]interface{}, 0) - mux.Unlock() - - if timeshift.duration() != 0 { - listenMutex.Lock() - listen = false - listenMutex.Unlock() - time.Sleep(timeshift.duration()) - listenMutex.Lock() - listen = true - listenMutex.Unlock() - } - } else { - mux.Unlock() - return - } - } - }() - - // Second goroutine in charge to retrieve the items from the source observable - go func() { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case error: - mux.Lock() - if len(buffer) > 0 { - out <- buffer - } - out <- item - close(out) - stop = true - mux.Unlock() - return - default: - listenMutex.Lock() - l := listen - listenMutex.Unlock() - - mux.Lock() - if l { - buffer = append(buffer, item) - } - mux.Unlock() - } - } else { - break - } - } - mux.Lock() - if len(buffer) > 0 { - out <- buffer - } - close(out) - stop = true - mux.Unlock() - }() - - } - return newColdObservableFromFunction(f) -} - -// BufferWithTimeOrCount returns an Observable that emits buffers of items it collects -// from the source Observable. The resulting Observable emits connected, -// non-overlapping buffers, each of a fixed duration specified by the timespan argument -// or a maximum size specified by the count argument (whichever is reached first). -// When the source Observable completes or encounters an error, the resulting Observable -// emits the current buffer and propagates the notification from the source Observable. -func (o *observable) BufferWithTimeOrCount(timespan Duration, count int) Observable { - f := func(out chan interface{}) { - if timespan == nil || timespan.duration() == 0 { - out <- errors.Wrap(&IllegalInputError{}, "timespan must not be nil") - close(out) - return - } - - if count <= 0 { - out <- errors.Wrap(&IllegalInputError{}, "count must be positive") - close(out) - return - } - - sendCh := make(chan []interface{}) - errCh := make(chan error) - buffer := make([]interface{}, 0) - var bufferMutex sync.Mutex - - // First sender goroutine - go func() { - for { - select { - case currentBuffer := <-sendCh: - out <- currentBuffer - case error := <-errCh: - if len(buffer) > 0 { - out <- buffer - } - if error != nil { - out <- error - } - close(out) - return - case <-time.After(timespan.duration()): // Send on timer - bufferMutex.Lock() - b := make([]interface{}, len(buffer)) - copy(b, buffer) - buffer = make([]interface{}, 0) - bufferMutex.Unlock() - - out <- b - } - } - }() - - // Second goroutine in charge to retrieve the items from the source observable - go func() { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case error: - errCh <- item - return - default: - bufferMutex.Lock() - buffer = append(buffer, item) - if len(buffer) >= count { - b := make([]interface{}, len(buffer)) - copy(b, buffer) - buffer = make([]interface{}, 0) - bufferMutex.Unlock() - - sendCh <- b - } else { - bufferMutex.Unlock() - } - } - } else { - break - } - } - errCh <- nil - }() - - } - return newColdObservableFromFunction(f) -} - -// Contains returns an Observable that emits a Boolean that indicates whether -// the source Observable emitted an item (the comparison is made against a predicate). -func (o *observable) Contains(equal Predicate) Single { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if equal(item) { - out <- true - close(out) - return - } - } else { - break - } - } - out <- false - close(out) - } - return newColdSingle(f) -} - -func (o *observable) Count() Single { - f := func(out chan interface{}) { - var count int64 - it := o.coldIterable.Iterator(context.Background()) - for { - if _, err := it.Next(context.Background()); err == nil { - count++ - } else { - break - } - } - out <- count - close(out) - } - return newColdSingle(f) -} - -// DefaultIfEmpty returns an Observable that emits the items emitted by the source -// Observable or a specified default item if the source Observable is empty. -func (o *observable) DefaultIfEmpty(defaultValue interface{}) Observable { - f := func(out chan interface{}) { - empty := true - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - empty = false - out <- item - } else { - break - } - } - if empty { - out <- defaultValue - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// Distinct suppresses duplicate items in the original Observable and returns -// a new Observable. -func (o *observable) Distinct(apply Function) Observable { - f := func(out chan interface{}) { - keysets := make(map[interface{}]struct{}) - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - key := apply(item) - _, ok := keysets[key] - if !ok { - out <- item - } - keysets[key] = struct{}{} - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// DistinctUntilChanged suppresses consecutive duplicate items in the original -// Observable and returns a new Observable. -func (o *observable) DistinctUntilChanged(apply Function) Observable { - f := func(out chan interface{}) { - var current interface{} - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - key := apply(item) - if current != key { - out <- item - current = key - } - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// DoOnEach operator allows you to establish a callback that the resulting Observable -// will call each time it emits an item -func (o *observable) DoOnEach(onNotification Consumer) Observable { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - onNotification(item) - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -func (o *observable) ElementAt(index uint) Single { - f := func(out chan interface{}) { - takeCount := 0 - - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if takeCount == int(index) { - out <- item - close(out) - return - } - takeCount++ - } else { - break - } - } - out <- &IndexOutOfBoundError{} - out <- &IndexOutOfBoundError{} - close(out) - } - return newColdSingle(f) -} - -// Filter filters items in the original Observable and returns -// a new Observable with the filtered items. -func (o *observable) Filter(apply Predicate) Observable { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if apply(item) { - out <- item - } - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// FirstOrDefault returns new Observable which emit only first item. -// If the observable fails to emit any items, it emits a default value. -func (o *observable) FirstOrDefault(defaultValue interface{}) Single { - f := func(out chan interface{}) { - first := defaultValue - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - first = item - break - } else { - break - } - } - out <- first - close(out) - } - return newColdSingle(f) -} - -// First returns new Observable which emit only first item. -func (o *observable) First() Observable { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - break - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// ForEach subscribes to the Observable and receives notifications for each element. -func (o *observable) ForEach(nextFunc NextFunc, errFunc ErrFunc, - doneFunc DoneFunc, opts ...Option) Observer { - return o.Subscribe(NewObserver(nextFunc, errFunc, doneFunc), opts...) -} - -// IgnoreElements ignores all items emitted by the source ObservableSource and only calls onComplete -// or onError. -func (o *observable) IgnoreElements() Observable { return &observable{ - subscribeStrategy: coldSubscribe(), - coldIterable: o.coldIterable, - errorOnSubscription: o.errorOnSubscription, - nextStrategy: onNextIgnore(), - } -} - -// Last returns a new Observable which emit only last item. -func (o *observable) Last() Observable { - f := func(out chan interface{}) { - var last interface{} - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - last = item - } else { - break - } - } - out <- last - close(out) - } - return newColdObservableFromFunction(f) -} - -// Last returns a new Observable which emit only last item. -// If the observable fails to emit any items, it emits a default value. -func (o *observable) LastOrDefault(defaultValue interface{}) Single { - f := func(out chan interface{}) { - last := defaultValue - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - last = item - } else { - break - } - } - out <- last - close(out) - } - return newColdSingle(f) -} - -// Map maps a Function predicate to each item in Observable and -// returns a new Observable with applied items. -func (o *observable) Map(apply Function, opts ...Option) Observable { - f := func(out chan interface{}) { - options := ParseOptions(opts...) - - workerPoolCapacity := options.NewWorkerPool() - wp := options.WorkerPool() - - var ctx context.Context - if c := options.Context(); c != nil { - ctx = c - } else { - c, cancel := context.WithCancel(context.Background()) - ctx = c - defer cancel() - } - - if workerPoolCapacity != 0 || wp != nil { - var workerPool *workerPool - wg := sync.WaitGroup{} - - if wp != nil { - workerPool = wp - } else { - pool := newWorkerPool(ctx, workerPoolCapacity) - workerPool = &pool - } - - output := make(chan interface{}, workerPoolCapacity) - it := o.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - workerPool.sendTask(item, apply, output, &wg) - } else { - break - } - } - - workerPool.wait(func(i interface{}) { - out <- i - }, output, &wg) - - close(out) - } else { - it := o.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- apply(item) - } else { - break - } - } - close(out) - } + iterable: newIterable(ctx, next, errs), + handler: handler, } - - return newColdObservableFromFunction(f) -} - -func (o *observable) Marshal(marshaler Marshaler, opts ...Option) Observable { - return o.Map(func(i interface{}) interface{} { - b, err := marshaler(i) - if err != nil { - return err - } - return b - }, opts...) -} - -// Max determines and emits the maximum-valued item emitted by an Observable according to a comparator. -func (o *observable) Max(comparator Comparator) OptionalSingle { - out := make(chan Optional) - go func() { - empty := true - var max interface{} - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - empty = false - - if max == nil { - max = item - } else { - if comparator(max, item) == Smaller { - max = item - } - } - } else { - break - } - } - if empty { - out <- EmptyOptional() - } else { - out <- Of(max) - } - close(out) - }() - return &optionalSingle{itemChannel: out} -} - -// Min determines and emits the minimum-valued item emitted by an Observable according to a comparator. -func (o *observable) Min(comparator Comparator) OptionalSingle { - out := make(chan Optional) - go func() { - empty := true - var min interface{} - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - empty = false - - if min == nil { - min = item - } else { - if comparator(min, item) == Greater { - min = item - } - } - } else { - break - } - } - if empty { - out <- EmptyOptional() - } else { - out <- Of(min) - } - close(out) - }() - return &optionalSingle{itemChannel: out} -} - -func (o *observable) Send(ch chan<- interface{}) { - go func() { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - ch <- item - } else { - close(ch) - return - } - } - }() -} - -// OnErrorResumeNext instructs an Observable to pass control to another Observable rather than invoking -// onError if it encounters an error. -func (o *observable) OnErrorResumeNext(resumeSequence ErrorToObservableFunction) Observable { - o.customErrorStrategy = onErrorResumeNext(resumeSequence) - return o -} - -// OnErrorReturn instructs an Observable to emit an item (returned by a specified function) -// rather than invoking onError if it encounters an error. -func (o *observable) OnErrorReturn(resumeFunc ErrorFunction) Observable { - o.customErrorStrategy = onErrorReturn(resumeFunc) - return o -} - -// OnErrorReturnItem instructs on observale to emit an item if it encounters an error. -func (o *observable) OnErrorReturnItem(item interface{}) Observable { - o.customErrorStrategy = onErrorReturnItem(item) - return o } -// Publish returns a ConnectableObservable which waits until its connect method -// is called before it begins emitting items to those Observers that have subscribed to it. -func (o *observable) Publish() ConnectableObservable { - return newConnectableObservableFromObservable(o) +func (o *observable) Done() <-chan struct{} { + return o.iterable.Done() } -func (o *observable) Reduce(apply Function2) OptionalSingle { - out := make(chan Optional) - go func() { - var acc interface{} - empty := true - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - empty = false - acc = apply(acc, item) - } else { - break - } - } - if empty { - out <- EmptyOptional() - } else { - out <- Of(acc) - } - close(out) - }() - return NewOptionalSingleFromChannel(out) +func (o *observable) Error() <-chan error { + return o.iterable.Error() } -// Repeat returns an Observable that repeats the sequence of items emitted by the source Observable -// at most count times, at a particular frequency. -func (o *observable) Repeat(count int64, frequency Duration) Observable { - if count != Infinite { - if count < 0 { - count = 0 - } - } - - f := func(out chan interface{}) { - persist := make([]interface{}, 0) - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - persist = append(persist, item) - } else { - break - } - } - for { - if count != Infinite { - if count == 0 { - break - } - } - - if frequency != nil { - time.Sleep(frequency.duration()) - } - - for _, v := range persist { - out <- v - } - - count = count - 1 - } - close(out) - } - return newColdObservableFromFunction(f) +func (o *observable) Next() <-chan interface{} { + return o.iterable.Next() } -// Sample returns an Observable that emits the most recent items emitted by the source -// ObservableSource whenever the input Observable emits an item. -func (o *observable) Sample(obs Observable) Observable { - f := func(out chan interface{}) { - mainChan := make(chan interface{}) - obsChan := make(chan interface{}) - var lastEmittedItem interface{} - isItemWaitingToBeEmitted := false - ctx, cancel := context.WithCancel(context.Background()) - - go func() { - it := o.coldIterable.Iterator(ctx) - for { - if item, err := it.Next(ctx); err == nil { - mainChan <- item - } else { - break - } - } - close(mainChan) - }() - - go func() { - it := obs.Iterator(ctx) - for { - if item, err := it.Next(ctx); err == nil { - obsChan <- item - } else { - break - } - } - close(obsChan) - }() - - defer cancel() - defer close(out) - +func (o *observable) ForEach(ctx context.Context, nextFunc NextFunc, errFunc ErrFunc, doneFunc DoneFunc) { + handler := func(ctx context.Context, nextSrc <-chan interface{}, errsSrc <-chan error, nextDst chan<- interface{}, errsDst chan<- error) { for { select { - case item, ok := <-mainChan: - if ok { - lastEmittedItem = item - isItemWaitingToBeEmitted = true - } else { - return - } - case _, ok := <-obsChan: - if ok { - if isItemWaitingToBeEmitted { - out <- lastEmittedItem - isItemWaitingToBeEmitted = false - } - } else { - return - } - } - } - } - - return newColdObservableFromFunction(f) -} - -// Scan applies Function2 predicate to each item in the original -// Observable sequentially and emits each successive value on a new Observable. -func (o *observable) Scan(apply Function2) Observable { - f := func(out chan interface{}) { - var current interface{} - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - tmp := apply(current, item) - out <- tmp - current = tmp - } else { - break + case <-ctx.Done(): + doneFunc() + return + case i := <-nextSrc: + nextFunc(i) + case err := <-errsSrc: + errFunc(err) + return } } - close(out) } - return newColdObservableFromFunction(f) + newObservable(ctx, o, handler) } -// SequenceEqual emits true if an Observable and the input Observable emit the same items, -// in the same order, with the same termination state. Otherwise, it emits false. -func (o *observable) SequenceEqual(obs Observable) Single { - oChan := make(chan interface{}) - obsChan := make(chan interface{}) - - go func() { - it := obs.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - obsChan <- item - } else { - break - } - } - close(obsChan) - }() - - go func() { - it := o.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - oChan <- item - } else { - break - } - } - close(oChan) - }() - - f := func(out chan interface{}) { - var mainSequence []interface{} - var obsSequence []interface{} - areCorrect := true - isMainChannelClosed := false - isObsChannelClosed := false - - mainLoop: +func (o *observable) Map(ctx context.Context, apply Func) Observable { + cancel, cancelFunc := context.WithCancel(ctx) + handler := func(ctx context.Context, nextSrc <-chan interface{}, errsSrc <-chan error, nextDst chan<- interface{}, errsDst chan<- error) { for { select { - case item, ok := <-oChan: - if ok { - mainSequence = append(mainSequence, item) - areCorrect, mainSequence, obsSequence = popAndCompareFirstItems(mainSequence, obsSequence) - } else { - isMainChannelClosed = true - } - case item, ok := <-obsChan: - if ok { - obsSequence = append(obsSequence, item) - areCorrect, mainSequence, obsSequence = popAndCompareFirstItems(mainSequence, obsSequence) - } else { - isObsChannelClosed = true - } - } - - if !areCorrect || (isMainChannelClosed && isObsChannelClosed) { - break mainLoop - } - } - - out <- areCorrect && len(mainSequence) == 0 && len(obsSequence) == 0 - close(out) - } - - return newColdSingle(f) -} - -// Skip suppresses the first n items in the original Observable and -// returns a new Observable with the rest items. -func (o *observable) Skip(nth uint) Observable { - f := func(out chan interface{}) { - skipCount := 0 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if skipCount < int(nth) { - skipCount++ - continue - } - out <- item - } else { - break - } - } - close(out) - } - - return newColdObservableFromFunction(f) -} - -// SkipLast suppresses the last n items in the original Observable and -// returns a new Observable with the rest items. -func (o *observable) SkipLast(nth uint) Observable { - f := func(out chan interface{}) { - buf := make(chan interface{}, nth) - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - select { - case buf <- item: - default: - out <- (<-buf) - buf <- item - } - } else { - break - } - } - close(buf) - close(out) - } - return newColdObservableFromFunction(f) -} - -// StartWithItems returns an Observable that emits the specified items before it begins to emit items emitted -// by the source Observable. -func (o *observable) StartWithItems(item interface{}, items ...interface{}) Observable { - f := func(out chan interface{}) { - out <- item - for _, item := range items { - out <- item - } - - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - - close(out) - } - return newColdObservableFromFunction(f) -} - -// StartWithIterable returns an Observable that emits the items in a specified Iterable before it begins to -// emit items emitted by the source Observable. -func (o *observable) StartWithIterable(iterable Iterable) Observable { - f := func(out chan interface{}) { - it := iterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - - it = o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - - close(out) - } - return newColdObservableFromFunction(f) -} - -// StartWithObservable returns an Observable that emits the items in a specified Observable before it begins to -// emit items emitted by the source Observable. -func (o *observable) StartWithObservable(obs Observable) Observable { - f := func(out chan interface{}) { - it := obs.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - - it = o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - - close(out) - } - return newColdObservableFromFunction(f) -} - -// SkipWhile discard items emitted by an Observable until a specified condition becomes false. -func (o *observable) SkipWhile(apply Predicate) Observable { - f := func(out chan interface{}) { - skip := true - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if !skip { - out <- item - } else { - if !apply(item) { - out <- item - skip = false - } - } - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// Subscribe subscribes an EventHandler and returns a Subscription channel. -func (o *observable) Subscribe(handler EventHandler, opts ...Option) Observer { - ob := NewObserver(handler) - - if o.errorOnSubscription != nil { - err := ob.OnError(o.errorOnSubscription) - if err != nil { - panic(errors.Wrap(err, "error while sending error item from observable")) - } - return ob - } - - o.subscribeStrategy(o, ob) - return ob -} - -// SumInt64 calculates the average of integers emitted by an Observable and emits an int64. -func (o *observable) SumInt64() Single { - f := func(out chan interface{}) { - var sum int64 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case int: - sum += int64(item) - case int8: - sum += int64(item) - case int16: - sum += int64(item) - case int32: - sum += int64(item) - case int64: - sum += item - default: - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: (int|int8|int16|int32|int64), got: %t", item)) - close(out) - return - } - } else { - break - } - } - out <- sum - close(out) - } - return newColdSingle(f) -} - -// SumFloat32 calculates the average of float32 emitted by an Observable and emits a float32. -func (o *observable) SumFloat32() Single { - f := func(out chan interface{}) { - var sum float32 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case int: - sum += float32(item) - case int8: - sum += float32(item) - case int16: - sum += float32(item) - case int32: - sum += float32(item) - case int64: - sum += float32(item) - case float32: - sum += item - default: - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: (float32|int|int8|int16|int32|int64), got: %t", item)) - close(out) - return - } - } else { - break - } - } - out <- sum - close(out) - } - return newColdSingle(f) -} - -// SumFloat64 calculates the average of float64 emitted by an Observable and emits a float64. -func (o *observable) SumFloat64() Single { - f := func(out chan interface{}) { - var sum float64 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case int: - sum += float64(item) - case int8: - sum += float64(item) - case int16: - sum += float64(item) - case int32: - sum += float64(item) - case int64: - sum += float64(item) - case float32: - sum += float64(item) - case float64: - sum += item - default: - out <- errors.Wrap(&IllegalInputError{}, - fmt.Sprintf("expected type: (float32|float64|int|int8|int16|int32|int64), got: %t", item)) - close(out) + case <-ctx.Done(): + cancelFunc() + case i := <-nextSrc: + res, err := apply(i) + if err != nil { + errsDst <- err return } - } else { - break - } - } - out <- sum - close(out) - } - return newColdSingle(f) -} - -// Take takes first n items in the original Obserable and returns -// a new Observable with the taken items. -func (o *observable) Take(nth uint) Observable { - f := func(out chan interface{}) { - takeCount := 0 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if takeCount < int(nth) { - takeCount++ - out <- item - continue - } - break - } else { - break + nextDst <- res + case err := <-errsSrc: + errsDst <- err } } - close(out) } - return newColdObservableFromFunction(f) -} - -// TakeLast takes last n items in the original Observable and returns -// a new Observable with the taken items. -func (o *observable) TakeLast(nth uint) Observable { - f := func(out chan interface{}) { - n := int(nth) - r := ring.New(n) - count := 0 - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - count++ - r.Value = item - r = r.Next() - } else { - break - } - } - if count < n { - remaining := n - count - if remaining <= count { - r = r.Move(n - count) - } else { - r = r.Move(-count) - } - n = count - } - for i := 0; i < n; i++ { - out <- r.Value - r = r.Next() - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// TakeUntil returns an Observable that emits items emitted by the source Observable, -// checks the specified predicate for each item, and then completes when the condition is satisfied. -func (o *observable) TakeUntil(apply Predicate) Observable { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - if apply(item) { - break - } - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -// TakeWhile returns an Observable that emits items emitted by the source ObservableSource so long as each -// item satisfied a specified condition, and then completes as soon as this condition is not satisfied. -func (o *observable) TakeWhile(apply Predicate) Observable { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - if apply(item) { - out <- item - continue - } - break - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -func (o *observable) Timeout(ctx context.Context) Observable { - f := func(out chan interface{}) { - it := o.Iterator(context.Background()) - go func() { - for { - if item, err := it.Next(ctx); err == nil { - out <- item - } else { - out <- err - break - } - } - close(out) - }() - } - - return newColdObservableFromFunction(f) -} - -// ToChannel collects all items from an Observable and emit them in a channel -func (o *observable) ToChannel(opts ...Option) Channel { - options := ParseOptions(opts...) - var ch chan interface{} - if options.Buffer() != 0 { - ch = make(chan interface{}, options.Buffer()) - } else { - ch = make(chan interface{}) - } - - go func() { - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - ch <- item - } else { - break - } - } - close(ch) - }() - - return ch -} - -// ToSlice collects all items from an Observable and emit them as a single slice. -func (o *observable) ToSlice() Single { - f := func(out chan interface{}) { - s := make([]interface{}, 0) - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - s = append(s, item) - } else { - break - } - } - out <- s - close(out) - } - return newColdSingle(f) -} - -// ToMap convert the sequence of items emitted by an Observable -// into a map keyed by a specified key function -func (o *observable) ToMap(keySelector Function) Single { - f := func(out chan interface{}) { - m := make(map[interface{}]interface{}) - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - m[keySelector(item)] = item - } else { - break - } - } - out <- m - close(out) - } - return newColdSingle(f) -} - -// ToMapWithValueSelector convert the sequence of items emitted by an Observable -// into a map keyed by a specified key function and valued by another -// value function -func (o *observable) ToMapWithValueSelector(keySelector, valueSelector Function) Single { - f := func(out chan interface{}) { - m := make(map[interface{}]interface{}) - it := o.coldIterable.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - m[keySelector(item)] = valueSelector(item) - } else { - break - } - } - out <- m - close(out) - } - return newColdSingle(f) -} - -func (o *observable) Unmarshal(unmarshaler Unmarshaler, factory func() interface{}, opts ...Option) Observable { - return o.Map(func(i interface{}) interface{} { - v := factory() - err := unmarshaler(i.([]byte), v) - if err != nil { - return err - } - return v - }, opts...) -} - -// ZipFromObservable che emissions of multiple Observables together via a specified function -// and emit single items for each combination based on the results of this function -func (o *observable) ZipFromObservable(publisher Observable, zipper Function2) Observable { - f := func(out chan interface{}) { - it := o.coldIterable.Iterator(context.Background()) - it2 := publisher.Iterator(context.Background()) - OuterLoop: - for { - if item1, err := it.Next(context.Background()); err == nil { - for { - if item2, err := it2.Next(context.Background()); err == nil { - out <- zipper(item1, item2) - continue OuterLoop - } else { - break - } - } - break OuterLoop - } else { - break - } - } - close(out) - } - return newColdObservableFromFunction(f) -} - -func (o *observable) getCustomErrorStrategy() func(Observable, Observer, error) error { - return o.customErrorStrategy -} - -func (o *observable) getNextStrategy() func(Observer, interface{}) error { - return o.nextStrategy + return newObservable(cancel, o, handler) } diff --git a/observable_test.go b/observable_test.go index dfa197dd..e37c33fa 100644 --- a/observable_test.go +++ b/observable_test.go @@ -2,1868 +2,37 @@ package rxgo import ( "context" - "encoding/json" "errors" - "net/http" - "strconv" - "sync" - "testing" - "time" - "github.com/stretchr/testify/assert" + "testing" ) -type jsonTest struct { - ID int `json:"id"` -} - -func TestCheckEventHandler(t *testing.T) { - if testing.Short() { - t.Skip("Skip testing of unexported testCheckEventHandler") - } - - testtext := "" - - df := DoneFunc(func() { - testtext += "done" - }) - - myObserver := NewObserver(df) - - ob1 := NewObserver(myObserver) - ob2 := NewObserver(df) - - err := ob1.OnDone() - assert.NoError(t, err) - assert.Equal(t, "done", testtext) - - err = ob2.OnDone() - assert.NoError(t, err) - assert.Equal(t, "donedone", testtext) -} - -func TestEmptyOperator(t *testing.T) { - myStream := Empty() - text := "" - - onDone := DoneFunc(func() { - text += "done" - }) - - myStream.Subscribe(onDone).Block() - assert.Equal(t, "done", text) -} - -func TestJustOperator(t *testing.T) { - myStream := Just(1, 2.01, "foo", map[string]string{"bar": "baz"}, 'a') - //numItems := 5 - //yes := make(chan struct{}) - stuff := []interface{}{} - - onNext := NextFunc(func(item interface{}) { - stuff = append(stuff, item) - }) - - myStream.Subscribe(onNext).Block() - expected := []interface{}{ - 1, 2.01, "foo", map[string]string{"bar": "baz"}, 'a', - } - - assert.Exactly(t, expected, stuff) -} - -func TestFromOperator(t *testing.T) { - myStream := Just(1, 3.1416, &struct{ foo string }{"bar"}) - nums := []interface{}{} - - onNext := NextFunc(func(item interface{}) { - switch item := item.(type) { - case int, float64: - nums = append(nums, item) - } - }) - - myStream.Subscribe(onNext).Block() - expected := []interface{}{1, 3.1416} - assert.Len(t, nums, 2) - for n, num := range nums { - assert.Equal(t, expected[n], num) - } -} - -func fakeGet(_ string, delay time.Duration, result interface{}) (interface{}, error) { - <-time.After(delay) - if err, isErr := result.(error); isErr { - return nil, err - } - - return result, nil -} - -func TestStartOperator(t *testing.T) { - - responseCodes := []int{} - done := false - - d1 := Supplier(func() interface{} { - result := &http.Response{ - Status: "200 OK", - StatusCode: 200, - } - - res, err := fakeGet("somehost.com", 0*time.Millisecond, result) - if err != nil { - return err - } - return res - }) - - d2 := Supplier(func() interface{} { - result := &http.Response{ - Status: "301 Moved Permanently", - StatusCode: 301, - } - - res, err := fakeGet("somehost.com", 50*time.Millisecond, result) - if err != nil { - return err - } - return res - }) - - d3 := Supplier(func() interface{} { - result := &http.Response{ - Status: "500 Server Error", - StatusCode: 500, - } - - res, err := fakeGet("badserver.co", 100*time.Millisecond, result) - if err != nil { - return err - } - return res - }) - - e1 := Supplier(func() interface{} { - err := errors.New("Bad URL") - res, err := fakeGet("badurl.err", 150*time.Millisecond, err) - if err != nil { - return err - } - return res - }) - - d4 := Supplier(func() interface{} { - result := &http.Response{ - Status: "404 Not Found", - StatusCode: 400, - } - res, err := fakeGet("notfound.org", 200*time.Millisecond, result) - if err != nil { - return err - } - return res - }) - - onNext := NextFunc(func(item interface{}) { - if res, ok := item.(*http.Response); ok { - responseCodes = append(responseCodes, res.StatusCode) - } - }) - - onError := ErrFunc(func(err error) { - t.Log(err) - }) - - onDone := DoneFunc(func() { - done = true - }) - - myObserver := NewObserver(onNext, onError, onDone) - - myStream := Start(d1, d3, d4, e1, d2) - - myStream.Subscribe(myObserver).Block() - - assert.Exactly(t, []int{200, 301, 500}, responseCodes) - assert.False(t, done) -} - -func TestSubscribeToNextFunc(t *testing.T) { - myStream := Just(1, 2, 3, errors.New("4"), 5) - mynum := 0 - - nf := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - mynum += num - } - }) - - myStream.Subscribe(nf).Block() - assert.Equal(t, 6, mynum) -} - -// FIXME -//func TestSubscribeToErrFunc(t *testing.T) { -// myStream := Just(1, "hello", errors.New("bang"), 43.5) -// -// var myerr error -// -// ef := handlers.ErrFunc(func(err error) { -// myerr = err -// }) -// -// sub := myStream.Subscribe(ef).Block() -// -// assert.Equal(t, "bang", myerr.Error()) -// assert.Equal(t, "bang", sub.Error()) -//} - -func TestSubscribeToDoneFunc(t *testing.T) { - myStream := Just(nil) - - donetext := "" - - df := DoneFunc(func() { - donetext = "done" - }) - - myStream.Subscribe(df).Block() - assert.Equal(t, "done", donetext) -} - -func TestSubscribeToObserver(t *testing.T) { - assert := assert.New(t) - - myStream := Just("foo", "bar", "baz", 'a', 'b', errors.New("bang"), 99) - - words := []string{} - chars := []rune{} - integers := []int{} - finished := false - - onNext := NextFunc(func(item interface{}) { - switch item := item.(type) { - case string: - words = append(words, item) - case rune: - chars = append(chars, item) - case int: - integers = append(integers, item) - } - }) - - onError := ErrFunc(func(err error) { - t.Logf("Error emitted in the stream: %v\n", err) - }) - - onDone := DoneFunc(func() { - finished = true - }) - - ob := NewObserver(onNext, onError, onDone) - - done := myStream.Subscribe(ob) - done.Block() - - assert.Empty(integers) - assert.False(finished) - - expectedWords := []string{"foo", "bar", "baz"} - assert.Len(words, len(expectedWords)) - for n, word := range words { - assert.Equal(expectedWords[n], word) - } - - expectedChars := []rune{'a', 'b'} - assert.Len(chars, len(expectedChars)) - for n, char := range chars { - assert.Equal(expectedChars[n], char) - } -} - -func TestObservableMap(t *testing.T) { - stream := Just(1, 2, 3).Map(func(i interface{}) interface{} { - return i.(int) * 10 - }) - - AssertObservable(t, stream, HasItems(10, 20, 30)) -} - -func TestObservableTake(t *testing.T) { - stream := Just(1, 2, 3, 4, 5).Take(3) - AssertObservable(t, stream, HasItems(1, 2, 3)) -} - -func TestObservableTakeWithEmpty(t *testing.T) { - stream1 := Empty() - stream2 := stream1.Take(3) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{}, nums) -} - -func TestObservableTakeLast(t *testing.T) { - stream1 := Just(1, 2, 3, 4, 5) - stream2 := stream1.TakeLast(3) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{3, 4, 5}, nums) -} - -func TestObservableTakeLastLessThanNth(t *testing.T) { - stream1 := Just(4, 5) - stream2 := stream1.TakeLast(3) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{4, 5}, nums) -} - -func TestObservableTakeLastLessThanNth2(t *testing.T) { - stream1 := Just(4, 5) - stream2 := stream1.TakeLast(100000) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{4, 5}, nums) -} - -/* -func TestObservableTakeLastWithEmpty(t *testing.T) { - stream1 := Empty() - stream2 := stream1.TakeLast(3) - - nums := []int{} - onNext := handlers.NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - sub := stream2.Subscribe(onNext) - <-sub - - assert.Exactly(t, []int{}, nums) -}*/ - -func TestObservableFilter(t *testing.T) { - stream1 := Just(1, 2, 3, 120, []byte("baz"), 7, 10, 13) - - lt := func(target interface{}) Predicate { - return func(item interface{}) bool { - if num, ok := item.(int); ok { - if num < 9 { - return true - } - } - return false - } - } - - stream2 := stream1.Filter(lt(9)) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{1, 2, 3, 7}, nums) -} - -func TestObservableFirst(t *testing.T) { - stream1 := Just(0, 1, 3) - stream2 := stream1.First() - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{0}, nums) -} - -func TestObservableFirstWithEmpty(t *testing.T) { - stream1 := Empty() - - stream2 := stream1.First() - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{}, nums) -} - -func TestObservableLast(t *testing.T) { - stream1 := Just(0, 1, 3) - - stream2 := stream1.Last() - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{3}, nums) -} - -// FIXME Data race -//func TestParallelSubscribeToObserver(t *testing.T) { -// assert := assert.New(t) -// myStream := Just("foo", "bar", "baz", 'a', 'b', 99) -// -// var wordsCount uint64 -// var charsCount uint64 -// var integersCount uint64 -// finished := false -// -// onNext := handlers.NextFunc(func(item interface{}) { -// switch item.(type) { -// case string: -// atomic.AddUint64(&wordsCount, 1) -// case rune: -// atomic.AddUint64(&charsCount, 1) -// case int: -// atomic.AddUint64(&integersCount, 1) -// } -// }) -// -// onError := handlers.ErrFunc(func(err error) { -// t.Logf("Error emitted in the stream: %v\n", err) -// }) -// -// onDone := handlers.DoneFunc(func() { -// finished = true -// }) -// -// ob := NewObserver(onNext, onError, onDone) -// -// myStream.Subscribe(ob, options.WithParallelism(2)).Block() -// -// assert.True(finished) -// -// assert.Equal(integersCount, uint64(0x1)) -// assert.Equal(wordsCount, uint64(0x3)) -// assert.Equal(charsCount, uint64(0x2)) -//} - -// FIXME Data race -//func TestParallelSubscribeToObserverWithError(t *testing.T) { -// assert := assert.New(t) -// -// myStream := Just("foo", "bar", "baz", 'a', 'b', 99, errors.New("error")) -// -// finished := false -// -// onNext := handlers.NextFunc(func(item interface{}) { -// }) -// -// onError := handlers.ErrFunc(func(err error) { -// t.Logf("Error emitted in the stream: %v\n", err) -// }) -// -// onDone := handlers.DoneFunc(func() { -// finished = true -// }) -// -// ob := NewObserver(onNext, onError, onDone) -// -// myStream.Subscribe(ob, options.WithParallelism(2)).Block() -// -// assert.False(finished) -//} - -func TestObservableLastWithEmpty(t *testing.T) { - stream1 := Empty() - - stream2 := stream1.Last() - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{}, nums) -} - -func TestObservableSkip(t *testing.T) { - stream1 := Just(0, 1, 3, 5, 1, 8) - - stream2 := stream1.Skip(3) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{5, 1, 8}, nums) -} - -func TestObservableSkipWithEmpty(t *testing.T) { - stream1 := Empty() - - stream2 := stream1.Skip(3) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{}, nums) -} - -func TestObservableSkipLast(t *testing.T) { - stream1 := Just(0, 1, 3, 5, 1, 8) - - stream2 := stream1.SkipLast(3) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{0, 1, 3}, nums) -} - -func TestObservableSkipLastWithEmpty(t *testing.T) { - stream1 := Empty() - - stream2 := stream1.SkipLast(3) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{}, nums) -} - -func TestObservableDistinct(t *testing.T) { - stream1 := Just(1, 2, 2, 1, 3) - - id := func(item interface{}) interface{} { - return item - } - - stream2 := stream1.Distinct(id) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{1, 2, 3}, nums) -} - -func TestObservableDistinctUntilChanged(t *testing.T) { - stream1 := Just(1, 2, 2, 1, 3) - - id := func(item interface{}) interface{} { - return item - } - - stream2 := stream1.DistinctUntilChanged(id) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{1, 2, 1, 3}, nums) -} - -func TestObservableSequenceEqualWithCorrectSequence(t *testing.T) { - sequence := Just(2, 5, 12, 43, 98, 100, 213) - result := Just(2, 5, 12, 43, 98, 100, 213).SequenceEqual(sequence) - AssertSingle(t, result, HasValue(true)) -} - -func TestObservableSequenceEqualWithIncorrectSequence(t *testing.T) { - sequence := Just(2, 5, 12, 43, 98, 100, 213) - result := Just(2, 5, 12, 43, 15, 100, 213).SequenceEqual(sequence) - AssertSingle(t, result, HasValue(false)) -} - -func TestObservableSequenceEqualWithDifferentLengthSequence(t *testing.T) { - sequenceShorter := Just(2, 5, 12, 43, 98, 100) - sequenceLonger := Just(2, 5, 12, 43, 98, 100, 213, 512) - - resultForShorter := Just(2, 5, 12, 43, 98, 100, 213).SequenceEqual(sequenceShorter) - AssertSingle(t, resultForShorter, HasValue(false)) - - resultForLonger := Just(2, 5, 12, 43, 98, 100, 213).SequenceEqual(sequenceLonger) - AssertSingle(t, resultForLonger, HasValue(false)) -} - -func TestObservableSequenceEqualWithEmpty(t *testing.T) { - result := Empty().SequenceEqual(Empty()) - AssertSingle(t, result, HasValue(true)) -} - -func TestObservableScanWithIntegers(t *testing.T) { - stream1 := Just(0, 1, 3, 5, 1, 8) - - stream2 := stream1.Scan(func(x, y interface{}) interface{} { - var v1, v2 int - - if x, ok := x.(int); ok { - v1 = x - } - - if y, ok := y.(int); ok { - v2 = y - } - - return v1 + v2 - }) - - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - - stream2.Subscribe(onNext).Block() - assert.Exactly(t, []int{0, 1, 4, 9, 10, 18}, nums) -} - -func TestObservableScanWithString(t *testing.T) { - stream1 := Just("hello", "world", "this", "is", "foo") - - stream2 := stream1.Scan(func(x, y interface{}) interface{} { - var w1, w2 string - - if x, ok := x.(string); ok { - w1 = x - } - - if y, ok := y.(string); ok { - w2 = y - } - - return w1 + w2 - }) - - words := []string{} - onNext := NextFunc(func(item interface{}) { - if word, ok := item.(string); ok { - words = append(words, word) - } - }) - - stream2.Subscribe(onNext).Block() - expected := []string{ - "hello", - "helloworld", - "helloworldthis", - "helloworldthisis", - "helloworldthisisfoo", - } - - assert.Exactly(t, expected, words) -} - -func TestElementAt(t *testing.T) { - got := 0 - just := Just(0, 1, 2, 3, 4) - single := just.ElementAt(2) - single.Subscribe(NextFunc(func(i interface{}) { - switch i := i.(type) { - case int: - got = i - } - })).Block() - assert.Equal(t, 2, got) -} - -func TestElementAtWithError(t *testing.T) { - got := 0 - just := Just(0, 1, 2, 3, 4) - single := just.ElementAt(10) - single.Subscribe(ErrFunc(func(error) { - got = 10 - })).Block() - assert.Equal(t, 10, got) -} - -func TestObservableReduce(t *testing.T) { - stream1 := Just(1, 2, 3, 4, 5) - add := func(acc interface{}, elem interface{}) interface{} { - if a, ok := acc.(int); ok { - if b, ok := elem.(int); ok { - return a + b - } - } - return 0 - } - - var got Optional - stream1.Reduce(add).Subscribe(NextFunc(func(i interface{}) { - got = i.(Optional) - })).Block() - assert.False(t, got.IsEmpty()) - assert.Exactly(t, Of(14), got) -} - -func TestObservableReduceEmpty(t *testing.T) { - add := func(acc interface{}, elem interface{}) interface{} { - if a, ok := acc.(int); ok { - if b, ok := elem.(int); ok { - return a + b - } - } - return 0 - } - stream := Empty() - - var got Optional - stream.Reduce(add).Subscribe(NextFunc(func(i interface{}) { - got = i.(Optional) - })).Block() - assert.True(t, got.IsEmpty()) -} - -func TestObservableReduceNil(t *testing.T) { - stream := Just(1, 2, 3, 4, 5) - nilReduce := func(acc interface{}, elem interface{}) interface{} { - return nil - } - var got Optional - stream.Reduce(nilReduce).Subscribe(NextFunc(func(i interface{}) { - got = i.(Optional) - })).Block() - assert.False(t, got.IsEmpty()) - g, err := got.Get() - assert.Nil(t, err) - assert.Nil(t, g) -} - -func TestObservableCount(t *testing.T) { - stream := Just(1, 2, 3, "foo", "bar", errors.New("error")) - obs := stream.Count() - AssertSingle(t, obs, HasValue(int64(6))) -} - -func TestObservableFirstOrDefault(t *testing.T) { - obs := Empty().FirstOrDefault(7) - AssertSingle(t, obs, HasValue(7)) -} - -func TestObservableFirstOrDefaultWithValue(t *testing.T) { - obs := Just(0, 1, 2).FirstOrDefault(7) - AssertSingle(t, obs, HasValue(0)) -} - -func TestObservableLastOrDefault(t *testing.T) { - obs := Empty().LastOrDefault(7) - AssertSingle(t, obs, HasValue(7)) -} - -func TestObservableLastOrDefaultWithValue(t *testing.T) { - obs := Just(0, 1, 3).LastOrDefault(7) - AssertSingle(t, obs, HasValue(3)) -} - -func TestObservableSkipWhile(t *testing.T) { - got := []interface{}{} - Just(1, 2, 3, 4, 5).SkipWhile(func(i interface{}) bool { - switch i := i.(type) { - case int: - return i != 3 - default: - return true - } - }).Subscribe(NextFunc(func(i interface{}) { - got = append(got, i) - })).Block() - assert.Equal(t, []interface{}{3, 4, 5}, got) -} - -func TestObservableSkipWhileWithEmpty(t *testing.T) { - got := []interface{}{} - Empty().SkipWhile(func(i interface{}) bool { - switch i := i.(type) { - case int: - return i != 3 - default: - return false - } - }).Subscribe(NextFunc(func(i interface{}) { - got = append(got, i) - })).Block() - assert.Equal(t, []interface{}{}, got) -} - -func TestObservableZip(t *testing.T) { - stream1 := Just(1, 2, 3) - stream2 := Just(10, 20, 30) - zipper := func(elem1 interface{}, elem2 interface{}) interface{} { - switch v1 := elem1.(type) { - case int: - switch v2 := elem2.(type) { - case int: - return v1 + v2 - } - } - return 0 - } - zip := stream1.ZipFromObservable(stream2, zipper) - AssertObservable(t, zip, HasItems(11, 22, 33)) -} - -func TestObservableZipWithDifferentLength1(t *testing.T) { - stream1 := Just(1, 2, 3) - stream2 := Just(10, 20) - zipper := func(elem1 interface{}, elem2 interface{}) interface{} { - switch v1 := elem1.(type) { - case int: - switch v2 := elem2.(type) { - case int: - return v1 + v2 - } - } - return 0 - } - zip := stream1.ZipFromObservable(stream2, zipper) - AssertObservable(t, zip, HasItems(11, 22)) -} - -func TestObservableZipWithDifferentLength2(t *testing.T) { - stream1 := Just(1, 2) - stream2 := Just(10, 20, 30) - zipper := func(elem1 interface{}, elem2 interface{}) interface{} { - switch v1 := elem1.(type) { - case int: - switch v2 := elem2.(type) { - case int: - return v1 + v2 - } - } - return 0 - } - zip := stream1.ZipFromObservable(stream2, zipper) - AssertObservable(t, zip, HasItems(11, 22)) -} - -func TestObservableZipWithEmpty(t *testing.T) { - stream1 := Empty() - stream2 := Empty() - zipper := func(elem1 interface{}, elem2 interface{}) interface{} { - return 0 - } - zip := stream1.ZipFromObservable(stream2, zipper) - nums := []int{} - onNext := NextFunc(func(item interface{}) { - if num, ok := item.(int); ok { - nums = append(nums, num) - } - }) - zip.Subscribe(onNext).Block() - assert.Exactly(t, []int{}, nums) -} - -func TestCheckEventHandlers(t *testing.T) { - i := 0 - nf := NextFunc(func(interface{}) { - i += 2 - }) - df := DoneFunc(func() { - i += 5 - }) - ob1 := NewObserver(nf, df) - err := ob1.OnNext("") - assert.NoError(t, err) - - err = ob1.OnDone() - assert.NoError(t, err) - - assert.Equal(t, 7, i) -} - -func TestObservableForEach(t *testing.T) { - assert := assert.New(t) - myStream := Just("foo", "bar", "baz", 'a', 'b', errors.New("bang"), 99) - words := []string{} - chars := []rune{} - integers := []int{} - finished := false - onNext := NextFunc(func(item interface{}) { - switch item := item.(type) { - case string: - words = append(words, item) - case rune: - chars = append(chars, item) - case int: - integers = append(integers, item) - } - }) - onError := ErrFunc(func(err error) { - t.Logf("Error emitted in the stream: %v\n", err) - }) - onDone := DoneFunc(func() { - finished = true - }) - myStream.ForEach(onNext, onError, onDone).Block() - assert.Empty(integers) - assert.False(finished) - expectedWords := []string{"foo", "bar", "baz"} - assert.Len(words, len(expectedWords)) - for n, word := range words { - assert.Equal(expectedWords[n], word) - } - expectedChars := []rune{'a', 'b'} - assert.Len(chars, len(expectedChars)) - for n, char := range chars { - assert.Equal(expectedChars[n], char) - } -} - -func TestAll(t *testing.T) { - predicateAllInt := func(i interface{}) bool { - switch i.(type) { - case int: - return true - default: - return false - } - } - - AssertSingle(t, Just(1, 2, 3).All(predicateAllInt), - HasValue(true), HasNotRaisedAnyError()) - - AssertSingle(t, Just(1, "x", 3).All(predicateAllInt), - HasValue(false), HasNotRaisedAnyError()) -} - -func TestContain(t *testing.T) { - predicate := func(i interface{}) bool { - switch i := i.(type) { - case int: - return i == 2 - default: - return false - } - } - - var got1, got2 bool - - Just(1, 2, 3).Contains(predicate). - Subscribe(NextFunc(func(i interface{}) { - got1 = i.(bool) - })).Block() - assert.True(t, got1) - - Just(1, 5, 3).Contains(predicate). - Subscribe(NextFunc(func(i interface{}) { - got2 = i.(bool) - })).Block() - assert.False(t, got2) -} - -func TestDefaultIfEmpty(t *testing.T) { - got1 := 0 - Empty().DefaultIfEmpty(3).Subscribe(NextFunc(func(i interface{}) { - got1 = i.(int) - })).Block() - assert.Equal(t, 3, got1) -} - -func TestDefaultIfEmptyWithNonEmpty(t *testing.T) { - got1 := 0 - Just(1).DefaultIfEmpty(3).Subscribe(NextFunc(func(i interface{}) { - got1 = i.(int) - })).Block() - assert.Equal(t, 1, got1) -} - -func TestDoOnEach(t *testing.T) { - sum := 0 - stream := Just(1, 2, 3).DoOnEach(func(i interface{}) { - sum += i.(int) - }) - - AssertObservable(t, stream, HasItems(1, 2, 3)) - assert.Equal(t, 6, sum) -} - -func TestDoOnEachWithEmpty(t *testing.T) { - sum := 0 - stream := Empty().DoOnEach(func(i interface{}) { - sum += i.(int) - }) - - AssertObservable(t, stream, HasSize(0)) - assert.Equal(t, 0, sum) -} - -func TestRepeat(t *testing.T) { - repeat := Just(1, 2, 3).Repeat(1, nil) - AssertObservable(t, repeat, HasItems(1, 2, 3, 1, 2, 3)) -} - -func TestRepeatZeroTimes(t *testing.T) { - repeat := Just(1, 2, 3).Repeat(0, nil) - AssertObservable(t, repeat, HasItems(1, 2, 3)) -} - -func TestRepeatWithNegativeCount(t *testing.T) { - repeat := Just(1, 2, 3).Repeat(-2, nil) - AssertObservable(t, repeat, HasItems(1, 2, 3)) -} - -func TestRepeatWithFrequency(t *testing.T) { - frequency := new(mockDuration) - frequency.On("duration").Return(time.Millisecond) - - repeat := Just(1, 2, 3).Repeat(1, frequency) - AssertObservable(t, repeat, HasItems(1, 2, 3, 1, 2, 3)) - frequency.AssertNumberOfCalls(t, "duration", 1) - frequency.AssertExpectations(t) -} - -func TestAverageInt(t *testing.T) { - AssertSingle(t, Just(1, 2, 3).AverageInt(), HasValue(2)) - AssertSingle(t, Just(1, 20).AverageInt(), HasValue(10)) - AssertSingle(t, Empty().AverageInt(), HasValue(0)) - AssertSingle(t, Just(1.1, 2.2, 3.3).AverageInt(), HasRaisedAnError()) -} - -func TestAverageInt8(t *testing.T) { - AssertSingle(t, Just(int8(1), int8(2), int8(3)).AverageInt8(), HasValue(int8(2))) - AssertSingle(t, Just(int8(1), int8(20)).AverageInt8(), HasValue(int8(10))) - AssertSingle(t, Empty().AverageInt8(), HasValue(0)) - AssertSingle(t, Just(1.1, 2.2, 3.3).AverageInt8(), HasRaisedAnError()) -} - -func TestAverageInt16(t *testing.T) { - AssertSingle(t, Just(int16(1), int16(2), int16(3)).AverageInt16(), HasValue(int16(2))) - AssertSingle(t, Just(int16(1), int16(20)).AverageInt16(), HasValue(int16(10))) - AssertSingle(t, Empty().AverageInt16(), HasValue(0)) - AssertSingle(t, Just(1.1, 2.2, 3.3).AverageInt16(), HasRaisedAnError()) -} - -func TestAverageInt32(t *testing.T) { - AssertSingle(t, Just(int32(1), int32(2), int32(3)).AverageInt32(), HasValue(int32(2))) - AssertSingle(t, Just(int32(1), int32(20)).AverageInt32(), HasValue(int32(10))) - AssertSingle(t, Empty().AverageInt32(), HasValue(0)) - AssertSingle(t, Just(1.1, 2.2, 3.3).AverageInt32(), HasRaisedAnError()) -} - -func TestAverageInt64(t *testing.T) { - AssertSingle(t, Just(int64(1), int64(2), int64(3)).AverageInt64(), HasValue(int64(2))) - AssertSingle(t, Just(int64(1), int64(20)).AverageInt64(), HasValue(int64(10))) - AssertSingle(t, Empty().AverageInt64(), HasValue(0)) - AssertSingle(t, Just(1.1, 2.2, 3.3).AverageInt64(), HasRaisedAnError()) -} - -func TestAverageFloat32(t *testing.T) { - AssertSingle(t, Just(float32(1), float32(2), float32(3)).AverageFloat32(), HasValue(float32(2))) - AssertSingle(t, Just(float32(1), float32(20)).AverageFloat32(), HasValue(float32(10.5))) - AssertSingle(t, Empty().AverageFloat32(), HasValue(0)) - AssertSingle(t, Just("x").AverageFloat32(), HasRaisedAnError()) -} - -func TestAverageFloat64(t *testing.T) { - AssertSingle(t, Just(float64(1), float64(2), float64(3)).AverageFloat64(), HasValue(float64(2))) - AssertSingle(t, Just(float64(1), float64(20)).AverageFloat64(), HasValue(float64(10.5))) - AssertSingle(t, Empty().AverageFloat64(), HasValue(0)) - AssertSingle(t, Just("x").AverageFloat64(), HasRaisedAnError()) -} - -func TestMax(t *testing.T) { - comparator := func(e1, e2 interface{}) Comparison { - i1 := e1.(int) - i2 := e2.(int) - if i1 > i2 { - return Greater - } else if i1 < i2 { - return Smaller - } else { - return Equals - } - } - - optionalSingle := Just(1, 5, 1).Max(comparator) - AssertOptionalSingle(t, optionalSingle, HasValue(5)) -} - -func TestMaxWithEmpty(t *testing.T) { - comparator := func(interface{}, interface{}) Comparison { - return Equals - } - - optionalSingle := Empty().Max(comparator) - AssertOptionalSingle(t, optionalSingle, IsEmpty()) -} - -func TestMin(t *testing.T) { - comparator := func(e1, e2 interface{}) Comparison { - i1 := e1.(int) - i2 := e2.(int) - if i1 > i2 { - return Greater - } else if i1 < i2 { - return Smaller - } else { - return Equals - } - } - - optionalSingle := Just(5, 1, 5).Min(comparator) - AssertOptionalSingle(t, optionalSingle, HasValue(1)) -} - -func TestMinWithEmpty(t *testing.T) { - comparator := func(interface{}, interface{}) Comparison { - return Equals - } - - optionalSingle := Empty().Min(comparator) - AssertOptionalSingle(t, optionalSingle, IsEmpty()) -} - -func TestBufferWithCountWithCountAndSkipEqual(t *testing.T) { - obs := Just(1, 2, 3, 4, 5, 6).BufferWithCount(3, 3) - AssertObservable(t, obs, HasItems([]interface{}{1, 2, 3}, []interface{}{4, 5, 6})) -} - -func TestBufferWithCountWithCountAndSkipNotEqual(t *testing.T) { - obs := Just(1, 2, 3, 4, 5, 6).BufferWithCount(2, 3) - AssertObservable(t, obs, HasItems([]interface{}{1, 2}, []interface{}{4, 5})) -} - -func TestBufferWithCountWithEmpty(t *testing.T) { - obs := Empty().BufferWithCount(2, 3) - AssertObservable(t, obs, IsEmpty()) -} - -func TestBufferWithCountWithIncompleteLastItem(t *testing.T) { - obs := Just(1, 2, 3, 4).BufferWithCount(2, 3) - AssertObservable(t, obs, HasItems([]interface{}{1, 2}, []interface{}{4})) -} - -func TestBufferWithCountWithError(t *testing.T) { - obs := Just(1, 2, 3, 4, errors.New("")).BufferWithCount(3, 3) - AssertObservable(t, obs, HasItems([]interface{}{1, 2, 3}, []interface{}{4})) - AssertObservable(t, obs, HasRaisedError(errors.New(""))) -} - -func TestBufferWithInvalidInputs(t *testing.T) { - // TODO HasRaisedAnErrorWithCause - obs := Just(1, 2, 3, 4).BufferWithCount(0, 5) - AssertObservable(t, obs, HasRaisedAnError()) - - obs = Just(1, 2, 3, 4).BufferWithCount(5, 0) - AssertObservable(t, obs, HasRaisedAnError()) -} - -func TestBufferWithTimeWithMockedTime(t *testing.T) { - just := Just(1, 2, 3) - - timespan := new(mockDuration) - timespan.On("duration").Return(10 * time.Second) +func Test_ForEach(t *testing.T) { + count := 0 + var gotErr error - timeshift := new(mockDuration) - timeshift.On("duration").Return(10 * time.Second) - - obs := just.BufferWithTime(timespan, timeshift) - - AssertObservable(t, obs, HasItems([]interface{}{1, 2, 3})) - timespan.AssertCalled(t, "duration") - timeshift.AssertNotCalled(t, "duration") -} - -func TestBufferWithTime_MinorMockedTime(t *testing.T) { - ch := make(chan interface{}) - from := FromIterator(newIteratorFromChannel(ch)) - - timespan := new(mockDuration) - timespan.On("duration").Return(1 * time.Millisecond) - - timeshift := new(mockDuration) - timeshift.On("duration").Return(1 * time.Millisecond) - - obs := from.BufferWithTime(timespan, timeshift) - - ch <- 1 - close(ch) - - obs.Subscribe(nil).Block() - timespan.AssertCalled(t, "duration") -} - -func TestBufferWithTimeWithIllegalInput(t *testing.T) { - AssertObservable(t, Empty().BufferWithTime(nil, nil), HasRaisedAnError()) - AssertObservable(t, Empty().BufferWithTime(WithDuration(0*time.Second), nil), HasRaisedAnError()) -} - -func TestBufferWithTimeWithNilTimeshift(t *testing.T) { - just := Just(1, 2, 3) - obs := just.BufferWithTime(WithDuration(1*time.Second), nil) - AssertObservable(t, obs, IsNotEmpty()) -} + next := make(chan interface{}) + errs := make(chan error) -func TestBufferWithTimeWithError(t *testing.T) { - just := Just(1, 2, 3, errors.New("")) - obs := just.BufferWithTime(WithDuration(1*time.Second), nil) - AssertObservable(t, obs, HasItems([]interface{}{1, 2, 3}), HasRaisedAnError()) -} - -func TestBufferWithTimeWithEmpty(t *testing.T) { - obs := Empty().BufferWithTime(WithDuration(1*time.Second), WithDuration(1*time.Second)) - AssertObservable(t, obs, IsEmpty()) -} - -func TestBufferWithTimeOrCountWithInvalidInputs(t *testing.T) { - obs := Empty().BufferWithTimeOrCount(nil, 5) - AssertObservable(t, obs, HasRaisedAnError()) - - obs = Empty().BufferWithTimeOrCount(WithDuration(0), 5) - AssertObservable(t, obs, HasRaisedAnError()) - - obs = Empty().BufferWithTimeOrCount(WithDuration(time.Millisecond*5), 0) - AssertObservable(t, obs, HasRaisedAnError()) -} - -func TestBufferWithTimeOrCountWithCount(t *testing.T) { - just := Just(1, 2, 3) - obs := just.BufferWithTimeOrCount(WithDuration(1*time.Second), 2) - AssertObservable(t, obs, HasItems([]interface{}{1, 2}, []interface{}{3})) -} - -func TestBufferWithTimeOrCountWithTime(t *testing.T) { - ch := make(chan interface{}) - from := FromIterator(newIteratorFromChannel(ch)) - - got := make([]interface{}, 0) - - obs := from.BufferWithTimeOrCount(WithDuration(1*time.Millisecond), 10). - Subscribe(NextFunc(func(i interface{}) { - got = append(got, i) - })) - ch <- 1 - time.Sleep(50 * time.Millisecond) - ch <- 2 - close(ch) - obs.Block() - - // Check items are included - got1 := false - got2 := false - for _, v := range got { - switch v := v.(type) { - case []interface{}: - if len(v) == 1 { - if v[0] == 1 { - got1 = true - } else if v[0] == 2 { - got2 = true - } - } - } - } - - assert.True(t, got1) - assert.True(t, got2) -} - -func TestBufferWithTimeOrCountWithMockedTime(t *testing.T) { - ch := make(chan interface{}) - from := FromIterator(newIteratorFromChannel(ch)) - - timespan := new(mockDuration) - timespan.On("duration").Return(1 * time.Millisecond) - - obs := from.BufferWithTimeOrCount(timespan, 5) - - time.Sleep(50 * time.Millisecond) - ch <- 1 - close(ch) - - obs.Subscribe(nil).Block() - timespan.AssertCalled(t, "duration") -} - -func TestBufferWithTimeOrCountWithError(t *testing.T) { - just := Just(1, 2, 3, errors.New(""), 4) - obs := just.BufferWithTimeOrCount(WithDuration(10*time.Second), 2) - AssertObservable(t, obs, HasItems([]interface{}{1, 2}, []interface{}{3}), - HasRaisedAnError()) -} - -func TestSumInt64(t *testing.T) { - AssertSingle(t, Just(1, 2, 3).SumInt64(), HasValue(int64(6))) - AssertSingle(t, Just(int8(1), int(2), int16(3), int32(4), int64(5)).SumInt64(), - HasValue(int64(15))) - AssertSingle(t, Just(1.1, 2.2, 3.3).SumInt64(), HasRaisedAnError()) - AssertSingle(t, Empty().SumInt64(), HasValue(int64(0))) -} - -func TestSumFloat32(t *testing.T) { - AssertSingle(t, Just(float32(1.0), float32(2.0), float32(3.0)).SumFloat32(), - HasValue(float32(6.))) - AssertSingle(t, Just(float32(1.1), 2, int8(3), int16(1), int32(1), int64(1)).SumFloat32(), - HasValue(float32(9.1))) - AssertSingle(t, Just(1.1, 2.2, 3.3).SumFloat32(), HasRaisedAnError()) - AssertSingle(t, Empty().SumFloat32(), HasValue(float32(0))) -} - -func TestSumFloat64(t *testing.T) { - AssertSingle(t, Just(1.1, 2.2, 3.3).SumFloat64(), - HasValue(6.6)) - AssertSingle(t, Just(float32(1.0), 2, int8(3), 4., int16(1), int32(1), int64(1)).SumFloat64(), - HasValue(float64(13.))) - AssertSingle(t, Just("x").SumFloat64(), HasRaisedAnError()) - AssertSingle(t, Empty().SumFloat64(), HasValue(float64(0))) -} - -func TestMapWithWorkerPool(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - just := Just(1, 2, 3, 4, 5).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }, WithNewWorkerPool(3), WithContext(ctx)) - - AssertObservable(t, just, HasItemsNoOrder(2, 3, 4, 5, 6)) -} - -func TestMapWithReusedWorkerPool(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - wp := newWorkerPool(ctx, 3) - - just := Just(1, 2, 3, 4, 5).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }, WithWorkerPool(&wp), WithContext(ctx)) - AssertObservable(t, just, HasItemsNoOrder(2, 3, 4, 5, 6)) - - just = Just(1, 2, 3, 4, 5).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }, WithWorkerPool(&wp), WithContext(ctx)) - AssertObservable(t, just, HasItemsNoOrder(2, 3, 4, 5, 6)) -} - -func TestMapWithTwoSubscription(t *testing.T) { - just := Just(1).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }) - - AssertObservable(t, just, HasItems(3)) - AssertObservable(t, just, HasItems(3)) -} - -func TestMapWithConcurrentSubscriptions(t *testing.T) { - just := Just(1).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }) - - wg := sync.WaitGroup{} - for i := 0; i < 100; i++ { - wg.Add(1) - go func() { - defer wg.Done() - AssertObservable(t, just, HasItems(3)) - }() - } - - wg.Wait() -} - -func TestStartWithItems(t *testing.T) { - obs := Just(1, 2, 3).StartWithItems(10, 20) - AssertObservable(t, obs, HasItems(10, 20, 1, 2, 3)) -} - -func TestIgnoreElements(t *testing.T) { - obs := Just(1, 2, 3).IgnoreElements() - AssertObservable(t, obs, IsEmpty()) -} - -func TestIgnoreElements_WithError(t *testing.T) { - err := errors.New("") - obs := Just(1, err, 3).IgnoreElements() - AssertObservable(t, obs, HasRaisedError(err)) -} - -func TestOnErrorReturn(t *testing.T) { - obs := Just(1, 2, errors.New("3"), 4).OnErrorReturn(func(e error) interface{} { - i, _ := strconv.Atoi(e.Error()) - return i - }) - AssertObservable(t, obs, HasItems(1, 2, 3, 4)) -} - -func TestOnErrorReturnItem(t *testing.T) { - obs := Just(1, 2, errors.New("3"), 4).OnErrorReturnItem(3) - AssertObservable(t, obs, HasItems(1, 2, 3, 4)) -} - -func TestOnErrorResumeNext(t *testing.T) { - err := errors.New("8") - obs := Just(1, 2, errors.New("3"), 4).OnErrorResumeNext(func(e error) Observable { - return Just(5, 6, err, 9) - }) - AssertObservable(t, obs, HasItems(1, 2, 5, 6), HasRaisedError(err)) -} - -func TestToSlice(t *testing.T) { - single := Just(1, 2, 3).ToSlice() - AssertSingle(t, single, HasValue([]interface{}{1, 2, 3})) -} - -func TestToSlice_Empty(t *testing.T) { - single := Empty().ToSlice() - AssertSingle(t, single, HasValue([]interface{}{})) -} - -func TestToMap(t *testing.T) { - single := Just(3, 4, 5, true, false).ToMap(func(i interface{}) interface{} { - switch v := i.(type) { - case int: - return v - case bool: - if v { - return 0 - } - return 1 - default: - return i - } - }) - AssertSingle(t, single, HasValue(map[interface{}]interface{}{ - 3: 3, - 4: 4, - 5: 5, - 0: true, - 1: false, - })) -} - -func TestToMap_Empty(t *testing.T) { - single := Empty().ToMap(func(i interface{}) interface{} { - return i - }) - AssertSingle(t, single, HasValue(map[interface{}]interface{}{})) -} - -func TestToMapWithValueSelector(t *testing.T) { - keySelector := func(i interface{}) interface{} { - switch v := i.(type) { - case int: - return v - case bool: - if v { - return 0 - } - return 1 - default: - return i - } - } - valueSelector := func(i interface{}) interface{} { - switch v := i.(type) { - case int: - return v * 10 - case bool: - return v - default: - return i - } - } - single := Just(3, 4, 5, true, false).ToMapWithValueSelector(keySelector, valueSelector) - AssertSingle(t, single, HasValue(map[interface{}]interface{}{ - 3: 30, - 4: 40, - 5: 50, - 0: true, - 1: false, - })) -} - -func TestToMapWithValueSelector_Empty(t *testing.T) { - single := Empty().ToMapWithValueSelector(func(i interface{}) interface{} { - return i - }, func(i interface{}) interface{} { - return i - }) - AssertSingle(t, single, HasValue(map[interface{}]interface{}{})) -} - -func channel(ch chan interface{}, t time.Duration) (item interface{}, closed bool, cancelled bool) { - ctx, cancel := context.WithTimeout(context.Background(), t) - defer cancel() - - select { - case i, ok := <-ch: - if ok { - return i, false, false - } - return nil, true, false - case <-ctx.Done(): - return nil, false, true - } -} - -func TestToChannel(t *testing.T) { - ch := Just(1, 2, 3).ToChannel() - item, _, _ := channel(ch, testWaitTime) - assert.Equal(t, 1, item) - item, _, _ = channel(ch, testWaitTime) - assert.Equal(t, 2, item) - item, _, _ = channel(ch, testWaitTime) - assert.Equal(t, 3, item) - _, closed, _ := channel(ch, testWaitTime) - assert.True(t, closed) -} - -func TestToChannel_BufferedChannel(t *testing.T) { - ch := Just(1, 2, 3).ToChannel(WithBufferedChannel(3)) - item, _, _ := channel(ch, testWaitTime) - assert.Equal(t, 1, item) - item, _, _ = channel(ch, testWaitTime) - assert.Equal(t, 2, item) - item, _, _ = channel(ch, testWaitTime) - assert.Equal(t, 3, item) - _, closed, _ := channel(ch, testWaitTime) - assert.True(t, closed) -} - -func TestTakeWhile(t *testing.T) { - obs := Just(1, 2, 3, 4, 5).TakeWhile(func(item interface{}) bool { - return item != 3 - }) - AssertObservable(t, obs, HasItems(1, 2)) -} - -func TestTakeWhile_Empty(t *testing.T) { - obs := Empty().TakeWhile(func(item interface{}) bool { - return item != 3 - }) - AssertObservable(t, obs, IsEmpty()) -} - -// FIXME Non deterministic -func TestSample(t *testing.T) { - ctx1, cancel1 := context.WithCancel(context.Background()) - defer cancel1() - ctx2, cancel2 := context.WithCancel(context.Background()) - defer cancel2() - obs := Interval(ctx1, 50*time.Millisecond). - Sample(Interval(ctx2, 250*time.Millisecond)). - Take(2) - - AssertObservable(t, obs, HasItems(3, 8)) -} - -func TestSample_NotRepeatedItems(t *testing.T) { - observables, _ := causality(` -o o -1 -2 - 0 -3 -4 -5 - 0 -6 - 0 -7 -8 - 0 - 0 -9 - 0 -x - x -`) - obs := observables[0].Sample(observables[1]) - - AssertObservable(t, obs, HasItems(2, 5, 6, 8, 9)) -} - -func TestSample_SourceObsClosedBeforeIntervalFired(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - obs := Just(1).Sample(Interval(ctx, time.Second)) - AssertObservable(t, obs, IsEmpty()) -} - -func TestSample_TimerFiredBeforeSourceObsEmitted(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - frequence50ms := new(mockDuration) - frequence50ms.On("duration").Return(50 * time.Millisecond) - obs := Interval(ctx, time.Second).Sample(Timer(frequence50ms)) - AssertObservable(t, obs, IsEmpty()) -} -func TestSample_Empty(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - obs := Empty().Sample(Interval(ctx, 50*time.Millisecond)) - AssertObservable(t, obs, IsEmpty()) -} - -func TestTakeUntil(t *testing.T) { - obs := Just(1, 2, 3, 4, 5).TakeUntil(func(item interface{}) bool { - return item == 3 - }) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -func TestTakeUntil_Empty(t *testing.T) { - obs := Empty().TakeUntil(func(item interface{}) bool { - return item == 3 - }) - AssertObservable(t, obs, IsEmpty()) -} - -func TestStartWith(t *testing.T) { - obs := Just(1, 2, 3).StartWithItems(10, 20) - AssertObservable(t, obs, HasItems(10, 20, 1, 2, 3)) -} - -func TestStartWith_WithError(t *testing.T) { - err := errors.New("") - obs := Just(1, 2, 3).StartWithItems(10, err) - AssertObservable(t, obs, HasItems(10), HasRaisedError(err)) -} - -func TestStartWith_Empty(t *testing.T) { - obs := Empty().StartWithItems(10, 20) - AssertObservable(t, obs, HasItems(10, 20)) -} - -func TestStartWithIterable(t *testing.T) { - ch := make(chan interface{}, 2) - obs := Just(1, 2, 3).StartWithIterable(newIterableFromChannel(ch)) - ch <- 10 - ch <- 20 - close(ch) - AssertObservable(t, obs, HasItems(10, 20, 1, 2, 3)) -} - -func TestStartWithIterable_WithError(t *testing.T) { - err := errors.New("") - ch := make(chan interface{}, 2) - obs := Just(1, 2, 3).StartWithIterable(newIterableFromChannel(ch)) - ch <- 10 - ch <- err - close(ch) - AssertObservable(t, obs, HasItems(10), HasRaisedError(err)) -} - -func TestStartWithIterable_Empty(t *testing.T) { - ch := make(chan interface{}, 2) - obs := Empty().StartWithIterable(newIterableFromChannel(ch)) - ch <- 10 - ch <- 20 - close(ch) - AssertObservable(t, obs, HasItems(10, 20)) -} - -func TestStartWithIterable_WithoutItems(t *testing.T) { - ch := make(chan interface{}) - obs := Just(1, 2, 3).StartWithIterable(newIterableFromChannel(ch)) - close(ch) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -func TestStartWithObservable(t *testing.T) { - obs := Just(1, 2, 3).StartWithObservable(Just(10, 20)) - AssertObservable(t, obs, HasItems(10, 20, 1, 2, 3)) -} - -func TestStartWithObservable_WithError(t *testing.T) { - err := errors.New("") - obs := Just(1, 2, 3).StartWithObservable(Just(10, err)) - AssertObservable(t, obs, HasItems(10), HasRaisedError(err)) -} - -func TestStartWithObservable_Empty1(t *testing.T) { - obs := Empty().StartWithObservable(Just(10, 20)) - AssertObservable(t, obs, HasItems(10, 20)) -} - -func TestStartWithObservable_Empty2(t *testing.T) { - obs := Just(1, 2, 3).StartWithObservable(Empty()) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -//func TestTimeout(t *testing.T) { -// observables := causality(t, ` -//1 -//2 -//3 -// 0 -//4 -//5 -//x -// x -//`) -// obs := observables[0].Timeout(observables[1]) -// AssertObservable(t, obs, HasItems(1, 2, 3)) -//} -// -//func TestTimeout_ClosedChannel(t *testing.T) { -// observables := causality(t, ` -//1 -//2 -//3 -//x -// 0 -// x -//`) -// obs := observables[0].Timeout(observables[1]) -// AssertObservable(t, obs, HasItems(1, 2, 3)) -//} - -func TestTimeout(t *testing.T) { - observables, contexts := causality(` -o c -1 -2 -3 - x -4 -5 -6 -x -`) - obs := observables[0].Timeout(contexts[0]) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -func TestTimeout_ClosedChannel(t *testing.T) { - observables, contexts := causality(` -o c -1 -2 -3 -x - x -`) - obs := observables[0].Timeout(contexts[0]) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -var x interface{} - -func BenchmarkSubscribe(b *testing.B) { - ch := make(chan interface{}) - obs := FromChannel(ch) - o := obs.Subscribe(NextFunc(func(i interface{}) { - x = i - })) - b.ResetTimer() - for i := 0; i < b.N; i++ { - ch <- i - } - err := o.OnDone() - if err != nil { - panic(err) - } -} - -func TestSend(t *testing.T) { - ch := make(chan interface{}, 3) - Just(1, 2, 3).Send(ch) - assert.Equal(t, 1, <-ch) - assert.Equal(t, 2, <-ch) - assert.Equal(t, 3, <-ch) - assert.Equal(t, nil, <-ch) -} - -func TestMarshal(t *testing.T) { - obs := Just(jsonTest{ - ID: 1, - }, jsonTest{ - ID: 2, - }).Marshal(json.Marshal) - AssertObservable(t, obs, HasItems([]byte(`{"id":1}`), []byte(`{"id":2}`))) -} - -func TestUnmarshal(t *testing.T) { - obs := Just([]byte(`{"id":1}`), []byte(`{"id":2}`)).Unmarshal(json.Unmarshal, func() interface{} { - return &jsonTest{} - }) - AssertObservable(t, obs, HasItems(&jsonTest{ - ID: 1, - }, &jsonTest{ - ID: 2, - })) -} - -func TestUnmarshal_WithError(t *testing.T) { - obs := Just([]byte(`{"id":1`), []byte(`{"id":2}`)).Unmarshal(json.Unmarshal, func() interface{} { - return &jsonTest{} - }) - AssertObservable(t, obs, HasRaisedAnError()) + expectedErr := errors.New("foo") + go func() { + next <- 1 + next <- 2 + next <- 3 + errs <- expectedErr + cancel() + }() + + obs := FromChannel(ctx, next, errs) + obs.ForEach(ctx, func(i interface{}) { + count += i.(int) + }, func(err error) { + gotErr = err + }, func() {}) + + // We avoid using the assertion API on purpose + <-ctx.Done() + assert.Equal(t, 6, count) + assert.Equal(t, expectedErr, gotErr) } diff --git a/observablecreate.go b/observablecreate.go deleted file mode 100644 index 2030994d..00000000 --- a/observablecreate.go +++ /dev/null @@ -1,461 +0,0 @@ -package rxgo - -import ( - "context" - "fmt" - "math" - "sync" - "sync/atomic" - "time" - - "github.com/pkg/errors" -) - -func isClosed(ch <-chan interface{}) bool { - select { - case <-ch: - return true - default: - } - - return false -} - -func newDefaultObservable() *observable { - return &observable{ - subscribeStrategy: coldSubscribe(), - nextStrategy: onNext(), - } -} - -// newColdObservableFromChannel creates an Observable from a given channel -func newColdObservableFromChannel(ch chan interface{}) Observable { - obs := newDefaultObservable() - obs.coldIterable = newIterableFromChannel(ch) - return obs -} - -// newColdObservableFromFunction creates a cold observable -func newColdObservableFromFunction(f func(chan interface{})) Observable { - obs := newDefaultObservable() - obs.coldIterable = newIterableFromFunc(f) - return obs -} - -func newHotObservableFromChannel(ch chan interface{}, opts ...Option) Observable { - parsedOptions := ParseOptions(opts...) - - obs := newDefaultObservable() - - obs.hotObservers = make([]Observer, 0) - obs.hotSubscribers = make([]chan<- interface{}, 0) - obs.hotItemChannel = ch - - stategy := parsedOptions.BackpressureStrategy() - switch stategy { - default: - panic(fmt.Sprintf("unknown stategy: %v", stategy)) - case None: - obs.subscribeStrategy = hotSubscribeStrategyNoneBackPressure() - go func() { - for { - if next, ok := <-obs.hotItemChannel; ok { - obs.hotObserversMutex.Lock() - for _, observer := range obs.hotObservers { - observer.Handle(next) - } - obs.hotObserversMutex.Unlock() - } else { - return - } - } - }() - case Drop: - panic("drop strategy not implemented yet") - case Buffer: - obs.subscribeStrategy = hotSubscribeStrategyBufferBackPressure(parsedOptions.Buffer()) - go func() { - for { - if next, ok := <-obs.hotItemChannel; ok { - obs.hotObserversMutex.Lock() - for _, ch := range obs.hotSubscribers { - select { - case ch <- next: - default: - } - } - obs.hotObserversMutex.Unlock() - } else { - return - } - } - }() - } - - return obs -} - -// newObservableFromIterable creates an Observable from a given iterable -func newObservableFromIterable(it Iterable) Observable { - obs := newDefaultObservable() - obs.coldIterable = it - return obs -} - -// newObservableFromRange creates an Observable from a range. -func newObservableFromRange(start, count int) Observable { - obs := newDefaultObservable() - obs.coldIterable = newIterableFromRange(start, count) - return obs -} - -// newObservableFromSlice creates an Observable from a given channel -func newObservableFromSlice(s []interface{}) Observable { - obs := newDefaultObservable() - obs.coldIterable = newIterableFromSlice(s) - return obs -} - -// Amb take several Observables, emit all of the items from only the first of these Observables -// to emit an item or notification -func Amb(observable Observable, observables ...Observable) Observable { - out := make(chan interface{}) - once := sync.Once{} - - f := func(o Observable) { - it := o.Iterator(context.Background()) - item, err := it.Next(context.Background()) - once.Do(func() { - if err == nil { - out <- item - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - close(out) - return - } - } - } else { - close(out) - return - } - }) - } - - go f(observable) - for _, o := range observables { - go f(o) - } - - return newColdObservableFromChannel(out) -} - -// CombineLatest combine the latest item emitted by each Observable via a specified function -// and emit items based on the results of this function -func CombineLatest(f FunctionN, observable Observable, observables ...Observable) Observable { - out := make(chan interface{}) - go func() { - var size = uint32(len(observables)) + 1 - var counter uint32 - s := make([]interface{}, size) - cancels := make([]context.CancelFunc, size) - mutex := sync.Mutex{} - wg := sync.WaitGroup{} - wg.Add(int(size)) - errCh := make(chan interface{}) - - handler := func(it Iterator, i int) { - for { - if item, err := it.Next(context.Background()); err == nil { - switch v := item.(type) { - case error: - out <- v - errCh <- nil - wg.Done() - return - default: - if s[i] == nil { - atomic.AddUint32(&counter, 1) - } - mutex.Lock() - s[i] = v - mutex.Unlock() - if atomic.LoadUint32(&counter) == size { - out <- f(s...) - } - } - } else { - wg.Done() - return - } - } - } - - ctx, cancel := context.WithCancel(context.Background()) - it := observable.Iterator(ctx) - go handler(it, 0) - cancels[0] = cancel - for i, o := range observables { - ctx, cancel := context.WithCancel(context.Background()) - it = o.Iterator(ctx) - go handler(it, i+1) - cancels[i+1] = cancel - } - - go func() { - for range errCh { - for _, cancel := range cancels { - cancel() - } - } - }() - - wg.Wait() - close(out) - }() - return newColdObservableFromChannel(out) -} - -// Concat emit the emissions from two or more Observables without interleaving them -func Concat(observable1 Observable, observables ...Observable) Observable { - out := make(chan interface{}) - go func() { - it := observable1.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - - for _, obs := range observables { - it := obs.Iterator(context.Background()) - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - } - - close(out) - }() - return newColdObservableFromChannel(out) -} - -// Create observable from based on source function. Keep it mind to call emitter.OnDone() -// to signal sequence's end. -// Example: -// - emitting none elements -// observable.Create(emitter observer.Observer, disposed bool) { emitter.OnDone() }) -// - emitting one element -// observable.Create(func(emitter observer.Observer, disposed bool) { -// emitter.OnNext("one element") -// emitter.OnDone() -// }) -func Create(source func(emitter Observer, disposed bool)) Observable { - out := make(chan interface{}) - emitter := NewObserver( - NextFunc(func(el interface{}) { - if !isClosed(out) { - out <- el - } - }), ErrFunc(func(err error) { - // decide how to deal with errors - if !isClosed(out) { - close(out) - } - }), DoneFunc(func() { - if !isClosed(out) { - close(out) - } - }), - ) - - go func() { - source(emitter, isClosed(out)) - }() - - return newColdObservableFromChannel(out) -} - -// Empty creates an Observable with no item and terminate immediately. -func Empty() Observable { - out := make(chan interface{}) - go func() { - close(out) - }() - return newColdObservableFromChannel(out) -} - -// Error returns an Observable that invokes an Observer's onError method -// when the Observer subscribes to it. -func Error(err error) Observable { - return &observable{ - errorOnSubscription: err, - } -} - -// FromChannel creates a cold observable from a channel -func FromChannel(ch chan interface{}) Observable { - return newColdObservableFromChannel(ch) -} - -// FromEventSource creates a hot observable -func FromEventSource(ch chan interface{}, opts ...Option) Observable { - return newHotObservableFromChannel(ch, opts...) -} - -// FromIterable creates a cold observable from an iterable -func FromIterable(it Iterable) Observable { - return newObservableFromIterable(it) -} - -// FromIterator creates a new Observable from an Iterator. -func FromIterator(it Iterator) Observable { - out := make(chan interface{}) - go func() { - for { - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - break - } - } - close(out) - }() - return newColdObservableFromChannel(out) -} - -// FromSlice creates a new Observable from a slice. -func FromSlice(s []interface{}) Observable { - return newObservableFromSlice(s) -} - -// Interval creates an Observable emitting incremental integers infinitely between -// each given time interval. -func Interval(ctx context.Context, interval time.Duration) Observable { - out := make(chan interface{}) - go func() { - i := 0 - for { - select { - case <-time.After(interval): - out <- i - i++ - case <-ctx.Done(): - close(out) - return - } - } - }() - return newColdObservableFromChannel(out) -} - -// Just creates an Observable with the provided item(s). -func Just(item interface{}, items ...interface{}) Observable { - if len(items) > 0 { - items = append([]interface{}{item}, items...) - } else { - items = []interface{}{item} - } - - return newObservableFromSlice(items) -} - -// Merge combines multiple Observables into one by merging their emissions -func Merge(observable Observable, observables ...Observable) Observable { - out := make(chan interface{}) - wg := sync.WaitGroup{} - - f := func(o Observable) { - for { - it := o.Iterator(context.Background()) - if item, err := it.Next(context.Background()); err == nil { - out <- item - } else { - wg.Done() - break - } - } - } - - wg.Add(1) - go f(observable) - for _, o := range observables { - wg.Add(1) - go f(o) - } - - go func() { - wg.Wait() - close(out) - }() - - return newColdObservableFromChannel(out) -} - -// Never create an Observable that emits no items and does not terminate -func Never() Observable { - out := make(chan interface{}) - return newColdObservableFromChannel(out) -} - -// Range creates an Observable that emits a particular range of sequential integers. -func Range(start, count int) (Observable, error) { - if count < 0 { - return nil, errors.Wrap(&IllegalInputError{}, "count must be positive") - } - if start+count-1 > math.MaxInt32 { - return nil, errors.Wrap(&IllegalInputError{}, "max value is bigger than math.MaxInt32") - } - - return newObservableFromRange(start, count), nil -} - -// Start creates an Observable from one or more directive-like Supplier -// and emits the result of each operation asynchronously on a new Observable. -func Start(f Supplier, fs ...Supplier) Observable { - if len(fs) > 0 { - fs = append([]Supplier{f}, fs...) - } else { - fs = []Supplier{f} - } - - out := make(chan interface{}) - - var wg sync.WaitGroup - for _, f := range fs { - wg.Add(1) - go func(f Supplier) { - out <- f() - wg.Done() - }(f) - } - - // Wait in another goroutine to not block - go func() { - wg.Wait() - close(out) - }() - - return newColdObservableFromChannel(out) -} - -// Timer returns an Observable that emits an empty structure after a specified delay, and then completes. -func Timer(d Duration) Observable { - out := make(chan interface{}) - go func() { - if d == nil { - time.Sleep(0) - } else { - time.Sleep(d.duration()) - } - out <- struct{}{} - close(out) - }() - return newColdObservableFromChannel(out) -} diff --git a/observablecreate_test.go b/observablecreate_test.go deleted file mode 100644 index fced670d..00000000 --- a/observablecreate_test.go +++ /dev/null @@ -1,439 +0,0 @@ -package rxgo - -import ( - "context" - "testing" - - "errors" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestEmitsNoElements(t *testing.T) { - // given - mockedObserver := NewObserverMock() - - // and - sequence := Create(func(emitter Observer, disposed bool) { - err := emitter.OnDone() - assert.NoError(t, err) - }) - - // when - sequence.Subscribe(mockedObserver.Capture()).Block() - - // then emits no elements - mockedObserver.AssertNotCalled(t, "OnNext", mock.Anything) - mockedObserver.AssertNotCalled(t, "OnError", mock.Anything) - mockedObserver.AssertCalled(t, "OnDone") -} - -func TestEmitsElements(t *testing.T) { - // given - mockedObserver := NewObserverMock() - - // and - elementsToEmit := []int{1, 2, 3, 4, 5} - - // and - sequence := Create(func(emitter Observer, disposed bool) { - for _, el := range elementsToEmit { - err := emitter.OnNext(el) - assert.NoError(t, err) - } - err := emitter.OnDone() - assert.NoError(t, err) - }) - - // when - sequence.Subscribe(mockedObserver.Capture()).Block() - - // then emits elements - for _, emitted := range elementsToEmit { - mockedObserver.AssertCalled(t, "OnNext", emitted) - } - mockedObserver.AssertNotCalled(t, "OnError", mock.Anything) - mockedObserver.AssertCalled(t, "OnDone") -} - -func TestOnlyFirstDoneCounts(t *testing.T) { - // given - mockedObserver := NewObserverMock() - - // and - sequence := Create(func(emitter Observer, disposed bool) { - err := emitter.OnDone() - assert.NoError(t, err) - - err = emitter.OnDone() - assert.Error(t, err) - assert.IsType(t, &ClosedObserverError{}, err) - }) - - // when - sequence.Subscribe(mockedObserver.Capture()).Block() - - // then emits first done - mockedObserver.AssertNotCalled(t, "OnError", mock.Anything) - mockedObserver.AssertNotCalled(t, "OnNext", mock.Anything) - mockedObserver.AssertNumberOfCalls(t, "OnDone", 1) -} - -func TestDoesntEmitElementsAfterDone(t *testing.T) { - // given - mockedObserver := NewObserverMock() - - // and - sequence := Create(func(emitter Observer, disposed bool) { - err := emitter.OnDone() - assert.NoError(t, err) - - err = emitter.OnNext("it cannot be emitted") - assert.Error(t, err) - assert.IsType(t, &ClosedObserverError{}, err) - }) - - // when - sequence.Subscribe(mockedObserver.Capture()).Block() - - // then stops emission after done - mockedObserver.AssertNotCalled(t, "OnError", mock.Anything) - mockedObserver.AssertNotCalled(t, "OnNext", mock.Anything) - mockedObserver.AssertCalled(t, "OnDone") -} - -func TestError(t *testing.T) { - var got error - err := errors.New("foo") - stream := Error(err) - stream.Subscribe(ErrFunc(func(e error) { - got = e - })).Block() - assert.Equal(t, err, got) -} - -// FIXME -//func TestIntervalOperator(t *testing.T) { -// fin := make(chan struct{}) -// ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) -// defer cancel() -// myStream := Interval(10*time.Millisecond, ctx) -// nums := []int{} -// -// onNext := handlers.NextFunc(func(item interface{}) { -// if num, ok := item.(int); ok { -// if num >= 5 { -// fin <- struct{}{} -// close(fin) -// } -// nums = append(nums, num) -// } -// }) -// -// myStream.Subscribe(onNext).Block() -// -// assert.Exactly(t, []int{0, 1, 2, 3, 4, 5}, nums) -//} - -func TestEmptyCompletesSequence(t *testing.T) { - // given - emissionObserver := NewObserverMock() - - // and empty sequence - sequence := Empty() - - // when subscribes to the sequence - sequence.Subscribe(emissionObserver.Capture()).Block() - - // then completes without any emission - emissionObserver.AssertNotCalled(t, "OnNext", mock.Anything) - emissionObserver.AssertNotCalled(t, "OnError", mock.Anything) - emissionObserver.AssertCalled(t, "OnDone") -} - -func TestNever(t *testing.T) { - never := Never() - assert.NotNil(t, never) -} - -func TestConcatWithOneObservable(t *testing.T) { - obs := Concat(Just(1, 2, 3)) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -func TestConcatWithTwoObservables(t *testing.T) { - obs := Concat(Just(1, 2, 3), Just(4, 5, 6)) - AssertObservable(t, obs, HasItems(1, 2, 3, 4, 5, 6)) -} - -func TestConcatWithMoreThanTwoObservables(t *testing.T) { - obs := Concat(Just(1, 2, 3), Just(4, 5, 6), Just(7, 8, 9)) - AssertObservable(t, obs, HasItems(1, 2, 3, 4, 5, 6, 7, 8, 9)) -} - -func TestConcatWithEmptyObservables(t *testing.T) { - obs := Concat(Empty(), Empty(), Empty()) - AssertObservable(t, obs, IsEmpty()) -} - -func TestConcatWithAnEmptyObservable(t *testing.T) { - obs := Concat(Empty(), Just(1, 2, 3)) - AssertObservable(t, obs, HasItems(1, 2, 3)) - - obs = Concat(Just(1, 2, 3), Empty()) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -func TestFromSlice(t *testing.T) { - obs := FromSlice([]interface{}{1, 2, 3}) - AssertObservable(t, obs, HasItems(1, 2, 3)) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -func TestFromChannel(t *testing.T) { - ch := make(chan interface{}, 3) - obs := FromChannel(ch) - - ch <- 1 - ch <- 2 - ch <- 3 - close(ch) - - AssertObservable(t, obs, HasItems(1, 2, 3)) - AssertObservable(t, obs, IsEmpty()) -} - -func TestJust(t *testing.T) { - obs := Just(1, 2, 3) - AssertObservable(t, obs, HasItems(1, 2, 3)) - AssertObservable(t, obs, HasItems(1, 2, 3)) -} - -type statefulIterable struct { - count int -} - -func (it *statefulIterable) Next(ctx context.Context) (interface{}, error) { - it.count++ - if it.count < 3 { - return it.count, nil - } - return nil, &NoSuchElementError{} -} - -func (it *statefulIterable) Value() interface{} { - return it.count -} - -func (it *statefulIterable) Iterator(ctx context.Context) Iterator { - return it -} - -func TestFromStatefulIterable(t *testing.T) { - obs := FromIterable(&statefulIterable{ - count: -1, - }) - - AssertObservable(t, obs, HasItems(0, 1, 2)) - AssertObservable(t, obs, IsEmpty()) -} - -func TestRange(t *testing.T) { - obs, err := Range(5, 3) - assert.NoError(t, err) - AssertObservable(t, obs, HasItems(5, 6, 7, 8)) - AssertObservable(t, obs, HasItems(5, 6, 7, 8)) -} - -func TestRangeWithNegativeCount(t *testing.T) { - r, err := Range(1, -5) - assert.NotNil(t, err) - assert.Nil(t, r) -} - -func TestRangeWithMaximumExceeded(t *testing.T) { - r, err := Range(1<<31, 1) - assert.NotNil(t, err) - assert.Nil(t, r) -} - -func TestTimer(t *testing.T) { - d := new(mockDuration) - d.On("duration").Return(1 * time.Millisecond) - - obs := Timer(d) - - AssertObservable(t, obs, HasItems(struct{}{})) - d.AssertCalled(t, "duration") -} - -func TestTimerWithNilDuration(t *testing.T) { - obs := Timer(nil) - - AssertObservable(t, obs, HasItems(struct{}{})) -} - -func TestMerge(t *testing.T) { - ch1 := make(chan interface{}, 10) - ch2 := make(chan interface{}, 10) - ch3 := make(chan interface{}, 10) - obs1 := FromChannel(ch1) - obs2 := FromChannel(ch2) - obs3 := FromChannel(ch3) - obs := Merge(obs1, obs2, obs3) - ch3 <- 1 - ch2 <- 2 - ch1 <- 3 - ch1 <- 4 - ch3 <- 5 - close(ch1) - close(ch2) - close(ch3) - // TODO HasItemsInDifferentOrder - AssertObservable(t, obs, HasSize(5)) -} - -func TestAmb(t *testing.T) { - observables, _ := causality(` -o o o -1 -2 -x - 3 - 4 - x - 5 - x -`) - obs := Amb(observables[0], observables[1]) - AssertObservable(t, obs, HasItems(1, 2)) -} - -func TestCombineLatest(t *testing.T) { - observables, _ := causality(` -o o -1 - 10 -2 - 11 -102 -x - x -`) - obs := CombineLatest(func(ii ...interface{}) interface{} { - sum := 0 - for _, v := range ii { - sum += v.(int) - } - return sum - }, observables[0], observables[1]) - AssertObservable(t, obs, HasItems(11, 12, 13, 113), HasNotRaisedAnyError()) -} - -func TestCombineLatest_Error(t *testing.T) { - observables, _ := causality(` -o o -1 - 10 -2 - e -102 -x -`) - obs := CombineLatest(func(ii ...interface{}) interface{} { - sum := 0 - for _, v := range ii { - sum += v.(int) - } - return sum - }, observables[0], observables[1]) - AssertObservable(t, obs, HasItems(11, 12), HasRaisedAnError()) -} - -// FIXME -//Context("when creating a hot observable with FromEventSource operator without back-pressure strategy", func() { -// ch := make(chan interface{}, 10) -// ch <- 1 -// observable := FromEventSource(ch, options.WithoutBackpressureStrategy()) -// outNext1 := make(chan interface{}, 1) -// It("should drop an item if there is no subscriber", func() { -// Eventually(len(ch), timeout, pollingInterval).Should(Equal(0)) -// }) -// It("an observer should receive items depending on the moment it subscribed", func() { -// observable.Subscribe(nextHandler(outNext1)) -// ch <- 2 -// ch <- 3 -// Expect(pollItem(outNext1, timeout)).Should(Equal(2)) -// Expect(pollItem(outNext1, timeout)).Should(Equal(3)) -// Expect(pollItem(outNext1, timeout)).Should(Equal(noData)) -// }) -// It("another observer should receive items depending on the moment it subscribed", func() { -// outNext2 := make(chan interface{}, 1) -// observable.Subscribe(nextHandler(outNext2)) -// ch <- 4 -// ch <- 5 -// Expect(pollItem(outNext1, timeout)).Should(Equal(4)) -// Expect(pollItem(outNext1, timeout)).Should(Equal(5)) -// Expect(pollItem(outNext2, timeout)).Should(Equal(4)) -// Expect(pollItem(outNext2, timeout)).Should(Equal(5)) -// Expect(pollItem(outNext2, timeout)).Should(Equal(noData)) -// }) -//}) - -// FIXME -//Context("when creating a hot observable with FromEventSource operator and a buffer back-pressure strategy", func() { -// ch := make(chan interface{}, 10) -// ch <- 1 -// observable := FromEventSource(ch, options.WithBufferBackpressureStrategy(2)) -// outNext1 := make(chan interface{}) -// It("should drop an item if there is no subscriber", func() { -// Eventually(len(ch), timeout, pollingInterval).Should(Equal(0)) -// }) -// Context("an observer subscribes", func() { -// observable.Subscribe(nextHandler(outNext1)) -// ch <- 2 -// ch <- 3 -// ch <- 4 -// ch <- 5 -// It("should consume the messages from the channel", func() { -// Eventually(len(ch), timeout, pollingInterval).Should(Equal(0)) -// }) -// It("should receive only the buffered items", func() { -// Expect(len(pollItems(outNext1, timeout))).Should(Equal(3)) -// }) -// }) -//}) - -// FIXME -//Context("when creating a hot observable with FromEventSource operator and a buffer back-pressure strategy", func() { -// ch := make(chan interface{}, 10) -// ch <- 1 -// observable := FromEventSource(ch, options.WithBufferBackpressureStrategy(2)) -// outNext1 := make(chan interface{}) -// outNext2 := make(chan interface{}) -// It("should drop an item if there is no subscriber", func() { -// Eventually(len(ch), timeout, pollingInterval).Should(Equal(0)) -// }) -// Context("two observer subscribe", func() { -// observable.Subscribe(nextHandler(outNext1)) -// ch <- 2 -// ch <- 3 -// ch <- 4 -// ch <- 5 -// It("should consume the messages from the channel", func() { -// Eventually(len(ch), timeout, pollingInterval).Should(Equal(0)) -// }) -// observable.Subscribe(nextHandler(outNext2)) -// ch <- 6 -// ch <- 7 -// It("should consume the messages from the channel", func() { -// Eventually(len(ch), timeout, pollingInterval).Should(Equal(0)) -// }) -// It("the two observer should receive only the buffered items", func() { -// Expect(len(pollItems(outNext1, timeout))).Should(Equal(3)) -// Expect(len(pollItems(outNext2, timeout))).Should(Equal(2)) -// }) -// }) -//}) diff --git a/observer.go b/observer.go index b5d517c4..984a2c02 100644 --- a/observer.go +++ b/observer.go @@ -1,140 +1,19 @@ package rxgo -// ClosedObserverError is thrown when an item or a signal is sent to a closed observer -type ClosedObserverError struct { -} - -// Observer represents a group of EventHandlers. -type Observer interface { - EventHandler - Disposable - - OnNext(item interface{}) error - OnError(err error) error - OnDone() error - +type BlockingObserver interface { Block() - setItemChannel(chan interface{}) - getItemChannel() chan interface{} } type observer struct { - // itemChannel is the internal channel used to receive items from the parent observable - itemChannel chan interface{} - // nextHandler is the handler for the next items - nextHandler NextFunc - // errHandler is the error handler - errHandler ErrFunc - // doneHandler is the handler once an observable is done - doneHandler DoneFunc - // disposedChannel is the notification channel used when an observer is disposed disposedChannel chan struct{} } -func (c *ClosedObserverError) Error() string { - return "closed observer" -} - -func (o *observer) setItemChannel(ch chan interface{}) { - o.itemChannel = ch -} - -func (o *observer) getItemChannel() chan interface{} { - return o.itemChannel -} - -// NewObserver constructs a new Observer instance with default Observer and accept -// any number of EventHandler -func NewObserver(eventHandlers ...EventHandler) Observer { - ob := observer{ +func NewObserver(nextFunc func(interface{}), errFunc func(error), doneFunc func()) BlockingObserver { + return &observer{ disposedChannel: make(chan struct{}), } - - if len(eventHandlers) > 0 { - for _, handler := range eventHandlers { - switch handler := handler.(type) { - case NextFunc: - ob.nextHandler = handler - case ErrFunc: - ob.errHandler = handler - case DoneFunc: - ob.doneHandler = handler - case *observer: - ob = *handler - } - } - } - - if ob.nextHandler == nil { - ob.nextHandler = func(interface{}) {} - } - if ob.errHandler == nil { - ob.errHandler = func(err error) {} - } - if ob.doneHandler == nil { - ob.doneHandler = func() {} - } - - return &ob -} - -// Handle registers Observer to EventHandler. -func (o *observer) Handle(item interface{}) { - switch item := item.(type) { - default: - o.nextHandler(item) - case error: - o.errHandler(item) - } -} - -func (o *observer) Dispose() { - close(o.disposedChannel) -} - -func (o *observer) Notify(ch chan<- struct{}) { - ch <- struct{}{} -} - -func (o *observer) IsDisposed() bool { - select { - case <-o.disposedChannel: - return true - default: - return false - } -} - -// OnNext applies Observer's NextHandler to an Item -func (o *observer) OnNext(item interface{}) error { - if !o.IsDisposed() { - o.nextHandler(item) - return nil - } - return &ClosedObserverError{} -} - -// OnError applies Observer's ErrHandler to an error -func (o *observer) OnError(err error) error { - if !o.IsDisposed() { - o.errHandler(err) - o.Dispose() - return nil - } - return &ClosedObserverError{} -} - -// OnDone terminates the Observer's internal Observable -func (o *observer) OnDone() error { - if !o.IsDisposed() { - o.doneHandler() - o.Dispose() - return nil - } - return &ClosedObserverError{} } -// OnDone terminates the Observer's internal Observable func (o *observer) Block() { <-o.disposedChannel } diff --git a/observer_mock.go b/observer_mock.go deleted file mode 100644 index f8026d65..00000000 --- a/observer_mock.go +++ /dev/null @@ -1,51 +0,0 @@ -package rxgo - -import ( - "github.com/stretchr/testify/mock" -) - -// NewObserverMock creates a mock observer -func NewObserverMock() *ObserverMock { - obMock := new(ObserverMock) - obMock.On("OnDone").Return() - obMock.On("OnNext", mock.Anything).Return() - obMock.On("OnError", mock.Anything).Return() - return obMock -} - -// ObserverMock is a mock observer -// TODO Visibility -type ObserverMock struct { - mock.Mock -} - -// OnDone provides a mock function with given fields: -func (m *ObserverMock) OnDone() { - m.Called() -} - -// OnError provides a mock function with given fields: err -func (m *ObserverMock) OnError(err error) { - m.Called(err) -} - -// OnNext provides a mock function with given fields: item -func (m *ObserverMock) OnNext(item interface{}) { - m.Called(item) -} - -// Capture captures observer inputs -func (m *ObserverMock) Capture() Observer { - ob := NewObserver( - NextFunc(func(el interface{}) { - m.OnNext(el) - }), - ErrFunc(func(err error) { - m.OnError(err) - }), - DoneFunc(func() { - m.OnDone() - }), - ) - return ob -} diff --git a/observer_test.go b/observer_test.go index 5ceb9d16..af920502 100644 --- a/observer_test.go +++ b/observer_test.go @@ -1,71 +1,27 @@ package rxgo -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateNewObserverWithConstructor(t *testing.T) { - ob := NewObserver() - err := ob.OnDone() - assert.NoError(t, err) - - err = ob.OnNext("a") - assert.Error(t, err) - assert.IsType(t, &ClosedObserverError{}, err) -} - -func TestCreateNewObserverWithObserver(t *testing.T) { - nexttext := "" - donetext := "" - - nextf := NextFunc(func(item interface{}) { - if text, ok := item.(string); ok { - nexttext = text - } - }) - - donef := DoneFunc(func() { - donetext = "Hello" - }) - - ob := NewObserver(donef, nextf) - - err := ob.OnNext("Next") - assert.NoError(t, err) - err = ob.OnDone() - assert.NoError(t, err) - - assert.Equal(t, "Next", nexttext) - assert.Equal(t, "Hello", donetext) -} - -func TestHandle(t *testing.T) { - i := 0 - - nextf := NextFunc(func(item interface{}) { - i += 5 - }) - - errorf := ErrFunc(func(error) { - i += 2 - }) - - ob := NewObserver(nextf, errorf) - ob.Handle("") - ob.Handle(errors.New("")) - assert.Equal(t, 7, i) -} - -func BenchmarkObserver_IsDisposed(b *testing.B) { - for n := 0; n < b.N; n++ { - o := NewObserver() - for i := 0; i < 10; i++ { - o.IsDisposed() - } - o.Dispose() - o.IsDisposed() - } -} +//func Test_NewObserver(t *testing.T) { +// count := 0 +// var gotErr error +// done := false +// +// observer := NewObserver(func(i interface{}) { +// count += i.(int) +// }, func(err error) { +// gotErr = err +// }, func() { +// done = true +// }) +// +// observer.OnNext(3) +// observer.OnNext(2) +// observer.OnNext(5) +// expectedErr := errors.New("foo") +// observer.OnError(expectedErr) +// observer.OnDone() +// observer.Block() +// +// assert.Equal(t, 10, count) +// assert.Equal(t, expectedErr, gotErr) +// assert.True(t, done) +//} diff --git a/optional.go b/optional.go deleted file mode 100644 index 1942f745..00000000 --- a/optional.go +++ /dev/null @@ -1,52 +0,0 @@ -package rxgo - -import "github.com/pkg/errors" - -var emptyOptional = new(empty) - -// Optional defines a container for empty values -type Optional interface { - // Get returns the content and an optional error is the optional is empty - Get() (interface{}, error) - // IsEmpty returns whether the optional is empty - IsEmpty() bool -} - -type some struct { - content interface{} -} - -type empty struct { -} - -// Get returns the content and an optional error is the optional is empty -func (s *some) Get() (interface{}, error) { - return s.content, nil -} - -// IsEmpty returns whether the optional is empty -func (s *some) IsEmpty() bool { - return false -} - -// Get returns the content and an optional error is the optional is empty -func (e *empty) Get() (interface{}, error) { - return nil, errors.Wrap(&NoSuchElementError{}, "empty does not contain any element") -} - -// IsEmpty returns whether the optional is empty -func (e *empty) IsEmpty() bool { - return true -} - -// Of returns a non-empty optional -func Of(data interface{}) Optional { - return &some{ - content: data, - } -} - -// EmptyOptional returns an empty optional -func EmptyOptional() Optional { - return emptyOptional -} diff --git a/optional_test.go b/optional_test.go deleted file mode 100644 index 235b73ad..00000000 --- a/optional_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package rxgo - -import ( - "testing" - - "github.com/pkg/errors" - - "github.com/stretchr/testify/assert" -) - -func TestOf(t *testing.T) { - // Of something test - some1 := Of("foo") - got, err := some1.Get() - - assert.False(t, some1.IsEmpty()) - assert.Nil(t, err) - assert.Exactly(t, got, "foo") -} - -func TestOfEmpty(t *testing.T) { - some := Of(nil) - got, err := some.Get() - - assert.False(t, some.IsEmpty()) - assert.Nil(t, err) - assert.Exactly(t, got, nil) -} - -func TestEmpty(t *testing.T) { - empty := EmptyOptional() - got, err := empty.Get() - assert.True(t, empty.IsEmpty()) - assert.Error(t, err) - assert.IsType(t, &NoSuchElementError{}, errors.Cause(err)) - assert.Exactly(t, got, nil) -} diff --git a/options.go b/options.go deleted file mode 100644 index 8b4968a3..00000000 --- a/options.go +++ /dev/null @@ -1,133 +0,0 @@ -package rxgo - -import "context" - -// BackpressureStrategy is the backpressure strategy type -type BackpressureStrategy uint32 - -const ( - // None means backpressure management - None BackpressureStrategy = iota - // Drop least recent items - Drop - // Buffer items - Buffer -) - -// Option is the configuration of an observable -type Option interface { - apply(*funcOption) - Buffer() int - BackpressureStrategy() BackpressureStrategy - Context() context.Context - NewWorkerPool() int - WorkerPool() *workerPool -} - -// funcOption wraps a function that modifies options into an -// implementation of the Option interface. -type funcOption struct { - f func(*funcOption) - buffer int - bpStrategy BackpressureStrategy - ctx context.Context - newWorkerPool int - workerPool *workerPool -} - -func (fdo *funcOption) Buffer() int { - return fdo.buffer -} - -func (fdo *funcOption) BackpressureStrategy() BackpressureStrategy { - return fdo.bpStrategy -} - -func (fdo *funcOption) Context() context.Context { - return fdo.ctx -} - -func (fdo *funcOption) NewWorkerPool() int { - return fdo.newWorkerPool -} - -func (fdo *funcOption) WorkerPool() *workerPool { - return fdo.workerPool -} - -func (fdo *funcOption) apply(do *funcOption) { - fdo.f(do) -} - -func newFuncOption(f func(*funcOption)) *funcOption { - return &funcOption{ - f: f, - } -} - -// ParseOptions parse the given options and mutate the options -// structure -func ParseOptions(opts ...Option) Option { - o := new(funcOption) - for _, opt := range opts { - opt.apply(o) - } - return o -} - -// WithBufferedChannel allows to configure the capacity of a buffered channel -func WithBufferedChannel(capacity int) Option { - return newFuncOption(func(options *funcOption) { - options.buffer = capacity - }) -} - -// WithoutBackpressureStrategy indicates to apply the None backpressure strategy -func WithoutBackpressureStrategy() Option { - return newFuncOption(func(options *funcOption) { - options.bpStrategy = None - }) -} - -// WithDropBackpressureStrategy indicates to apply the Drop backpressure strategy -func WithDropBackpressureStrategy() Option { - return newFuncOption(func(options *funcOption) { - options.bpStrategy = Drop - }) -} - -// WithBufferBackpressureStrategy indicates to apply the Drop backpressure strategy -func WithBufferBackpressureStrategy(buffer int) Option { - return newFuncOption(func(options *funcOption) { - options.bpStrategy = Buffer - options.buffer = buffer - }) -} - -// WithContext passes a given context -func WithContext(ctx context.Context) Option { - return newFuncOption(func(options *funcOption) { - options.ctx = ctx - }) -} - -// WithCPUPool indicates to apply a pool size based on GOMAXPROCS -func WithCPUPool() Option { - return newFuncOption(func(options *funcOption) { - options.workerPool = &cpuPool - }) -} - -// WithNewWorkerPool indicates to apply a given pool size -func WithNewWorkerPool(capacity int) Option { - return newFuncOption(func(options *funcOption) { - options.newWorkerPool = capacity - }) -} - -// WithWorkerPool indicates to apply a given worker pool -func WithWorkerPool(wp *workerPool) Option { - return newFuncOption(func(options *funcOption) { - options.workerPool = wp - }) -} diff --git a/options_test.go b/options_test.go deleted file mode 100644 index 851d20c1..00000000 --- a/options_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package rxgo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestOptions(t *testing.T) { - option := ParseOptions(WithBufferedChannel(2)) - assert.Equal(t, option.Buffer(), 2) -} diff --git a/single.go b/single.go deleted file mode 100644 index 206ab342..00000000 --- a/single.go +++ /dev/null @@ -1,151 +0,0 @@ -package rxgo - -import ( - "context" - - "github.com/pkg/errors" -) - -// Single is similar to an Observable but emits only one single element or an error notification. -type Single interface { - Iterable - Filter(apply Predicate) OptionalSingle - Map(apply Function) Single - Subscribe(handler EventHandler, opts ...Option) Observer -} - -// OptionalSingle represents an optional single observable type -type OptionalSingle interface { - Subscribe(handler EventHandler, opts ...Option) Observer -} - -type single struct { - iterable Iterable -} - -type optionalSingle struct { - itemChannel chan Optional -} - -func newSingleFrom(item interface{}) Single { - f := func(out chan interface{}) { - out <- item - close(out) - } - return newColdSingle(f) -} - -func newOptionalSingleFrom(opt Optional) OptionalSingle { - s := optionalSingle{ - itemChannel: make(chan Optional), - } - - go func() { - s.itemChannel <- opt - close(s.itemChannel) - }() - - return &s -} - -func newColdSingle(f func(chan interface{})) Single { - return &single{ - iterable: newIterableFromFunc(f), - } -} - -// NewOptionalSingleFromChannel creates a new OptionalSingle from a channel input -func NewOptionalSingleFromChannel(ch chan Optional) OptionalSingle { - return &optionalSingle{ - itemChannel: ch, - } -} - -func (s *single) Iterator(ctx context.Context) Iterator { - return s.iterable.Iterator(context.Background()) -} - -func (s *single) Filter(apply Predicate) OptionalSingle { - out := make(chan Optional) - go func() { - it := s.iterable.Iterator(context.Background()) - if item, err := it.Next(context.Background()); err == nil { - if apply(item) { - out <- Of(item) - } else { - out <- EmptyOptional() - } - close(out) - return - } - }() - - return &optionalSingle{ - itemChannel: out, - } -} - -func (s *single) Map(apply Function) Single { - f := func(out chan interface{}) { - it := s.iterable.Iterator(context.Background()) - if item, err := it.Next(context.Background()); err == nil { - out <- apply(item) - close(out) - return - } - } - return newColdSingle(f) -} - -func (s *single) Subscribe(handler EventHandler, opts ...Option) Observer { - ob := NewObserver(handler) - - go func() { - it := s.iterable.Iterator(context.Background()) - if item, err := it.Next(context.Background()); err == nil { - switch item := item.(type) { - case error: - err := ob.OnError(item) - if err != nil { - panic(errors.Wrap(err, "error while sending error item from single")) - } - default: - err := ob.OnNext(item) - if err != nil { - panic(errors.Wrap(err, "error while sending next item from single")) - } - ob.Dispose() - } - } else { - err := ob.OnDone() - if err != nil { - panic(errors.Wrap(err, "error while sending done signal from single")) - } - } - }() - - return ob -} - -func (s *optionalSingle) Subscribe(handler EventHandler, opts ...Option) Observer { - ob := NewObserver(handler) - - go func() { - item := <-s.itemChannel - switch item := item.(type) { - case error: - err := ob.OnError(item) - if err != nil { - panic(errors.Wrap(err, "error while sending error item from optional single")) - } - default: - err := ob.OnNext(item) - if err != nil { - panic(errors.Wrap(err, "error while sending next item from optional single")) - } - ob.Dispose() - } - }() - - return ob -} diff --git a/single_test.go b/single_test.go deleted file mode 100644 index 885323f9..00000000 --- a/single_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package rxgo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSingleFilterNotMatching(t *testing.T) { - got := 0 - - Just(1, 2, 3).ElementAt(1).Filter(func(i interface{}) bool { - switch i := i.(type) { - case int: - if i == 2 { - return true - } - } - return false - }).Subscribe(NextFunc(func(i interface{}) { - switch i := i.(type) { - case Optional: - if !i.IsEmpty() { - g, _ := i.Get() - got = g.(int) - } - } - })).Block() - assert.Equal(t, 2, got) -} - -func TestSingleFilterMatching(t *testing.T) { - got := 0 - - Just(1, 2, 3).ElementAt(1).Filter(func(i interface{}) bool { - switch i := i.(type) { - case int: - if i == 2 { - return false - } - } - return true - }).Subscribe(NextFunc(func(i interface{}) { - switch i := i.(type) { - case Optional: - if !i.IsEmpty() { - g, _ := i.Get() - got = g.(int) - } - } - })).Block() - assert.Equal(t, 0, got) -} - -func TestSingleMap(t *testing.T) { - got := 0 - - Just(1, 2, 3).ElementAt(1).Map(func(i interface{}) interface{} { - return i - }).Subscribe(NextFunc(func(i interface{}) { - got = i.(int) + 10 - })).Block() - assert.Equal(t, 12, got) -} - -func TestSingleMapWithTwoSubscription(t *testing.T) { - just := newSingleFrom(1).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }).Map(func(i interface{}) interface{} { - return 1 + i.(int) - }) - - AssertSingle(t, just, HasValue(3)) - AssertSingle(t, just, HasValue(3)) -} diff --git a/types.go b/types.go new file mode 100644 index 00000000..414ac39d --- /dev/null +++ b/types.go @@ -0,0 +1,12 @@ +package rxgo + +import "context" + +type ( + Func func(interface{}) (interface{}, error) + Handler func(ctx context.Context, nextSrc <-chan interface{}, errsSrc <-chan error, nextDst chan<- interface{}, errsDst chan<- error) + + NextFunc func(interface{}) + ErrFunc func(error) + DoneFunc func() +) diff --git a/worker.go b/worker.go deleted file mode 100644 index 925ad4d8..00000000 --- a/worker.go +++ /dev/null @@ -1,61 +0,0 @@ -package rxgo - -import ( - "context" - "runtime" - "sync" -) - -type workerPool struct { - ctx context.Context - input chan<- task -} - -type task struct { - item interface{} - apply Function - output chan<- interface{} -} - -var cpuPool = newWorkerPool(context.Background(), runtime.NumCPU()) - -func newWorkerPool(ctx context.Context, capacity int) workerPool { - input := make(chan task, capacity) - for i := 0; i < capacity; i++ { - go func() { - for { - select { - case <-ctx.Done(): - return - case t := <-input: - t.output <- t.apply(t.item) - } - } - }() - } - return workerPool{ - ctx: ctx, - input: input, - } -} - -func (wp *workerPool) sendTask(item interface{}, apply Function, output chan<- interface{}, wg *sync.WaitGroup) { - wg.Add(1) - wp.input <- task{ - item: item, - apply: apply, - output: output, - } -} - -func (wp *workerPool) wait(f func(interface{}), output chan interface{}, wg *sync.WaitGroup) { - go func() { - // TODO Cancel context - for o := range output { - f(o) - wg.Done() - } - }() - - wg.Wait() -}