go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/sync/promise/promise_test.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package promise
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/smartystreets/goconvey/convey"
    24  
    25  	"go.chromium.org/luci/common/clock"
    26  	"go.chromium.org/luci/common/clock/testclock"
    27  )
    28  
    29  func TestPromise(t *testing.T) {
    30  	t.Parallel()
    31  
    32  	Convey(`An instrumented Promise instance`, t, func() {
    33  		ctx, tc := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC))
    34  
    35  		type operation struct {
    36  			d any
    37  			e error
    38  		}
    39  
    40  		opC := make(chan operation)
    41  		p := New(ctx, func(context.Context) (any, error) {
    42  			op := <-opC
    43  			return op.d, op.e
    44  		})
    45  
    46  		Convey(`Has no data by default.`, func() {
    47  			data, err := p.Peek()
    48  			So(data, ShouldBeNil)
    49  			So(err, ShouldEqual, ErrNoData)
    50  		})
    51  
    52  		Convey(`Will timeout with no data.`, func() {
    53  			// Wait until our Promise starts its timer. Then signal it.
    54  			readyC := make(chan struct{})
    55  			tc.SetTimerCallback(func(_ time.Duration, _ clock.Timer) {
    56  				close(readyC)
    57  			})
    58  			go func() {
    59  				<-readyC
    60  				tc.Add(1 * time.Second)
    61  			}()
    62  
    63  			ctx, cancel := clock.WithTimeout(ctx, 1*time.Second)
    64  			defer cancel()
    65  			data, err := p.Get(ctx)
    66  			So(data, ShouldBeNil)
    67  			So(err.Error(), ShouldEqual, context.DeadlineExceeded.Error())
    68  		})
    69  
    70  		Convey(`With data already added`, func() {
    71  			e := errors.New("promise: fake test error")
    72  			opC <- operation{"DATA", e}
    73  
    74  			data, err := p.Get(ctx)
    75  			So(data, ShouldEqual, "DATA")
    76  			So(err, ShouldEqual, e)
    77  
    78  			Convey(`Will return data immediately.`, func() {
    79  				data, err = p.Peek()
    80  				So(data, ShouldEqual, "DATA")
    81  				So(err, ShouldEqual, e)
    82  			})
    83  
    84  			Convey(`Will return data instead of timing out.`, func() {
    85  				ctx, cancelFunc := context.WithCancel(ctx)
    86  				cancelFunc()
    87  
    88  				data, err = p.Get(ctx)
    89  				So(data, ShouldEqual, "DATA")
    90  				So(err, ShouldEqual, e)
    91  			})
    92  		})
    93  	})
    94  }
    95  
    96  func TestDeferredPromise(t *testing.T) {
    97  	t.Parallel()
    98  
    99  	Convey(`A deferred Promise instance`, t, func() {
   100  		c := context.Background()
   101  
   102  		Convey(`Will defer running until Get is called, and will panic the Get goroutine.`, func() {
   103  			// Since our Get will cause the generator to be run in this goroutine,
   104  			// calling Get with a generator that panics should cause a panic.
   105  			p := NewDeferred(func(context.Context) (any, error) { panic("test panic") })
   106  			So(func() { p.Get(c) }, ShouldPanic)
   107  		})
   108  
   109  		Convey(`Can output data.`, func() {
   110  			p := NewDeferred(func(context.Context) (any, error) {
   111  				return "hello", nil
   112  			})
   113  			v, err := p.Get(c)
   114  			So(err, ShouldBeNil)
   115  			So(v, ShouldEqual, "hello")
   116  		})
   117  	})
   118  }
   119  
   120  func TestPromiseSmoke(t *testing.T) {
   121  	t.Parallel()
   122  
   123  	Convey(`A Promise instance with multiple consumers will block.`, t, func() {
   124  		ctx, _ := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC))
   125  		dataC := make(chan any)
   126  		p := New(ctx, func(context.Context) (any, error) {
   127  			return <-dataC, nil
   128  		})
   129  
   130  		finishedC := make(chan string)
   131  		for i := uint(0); i < 100; i++ {
   132  			go func() {
   133  				data, _ := p.Get(ctx)
   134  				finishedC <- data.(string)
   135  			}()
   136  			go func() {
   137  				ctx, cancel := clock.WithTimeout(ctx, 1*time.Hour)
   138  				defer cancel()
   139  				data, _ := p.Get(ctx)
   140  				finishedC <- data.(string)
   141  			}()
   142  		}
   143  
   144  		dataC <- "DATA"
   145  		for i := uint(0); i < 200; i++ {
   146  			So(<-finishedC, ShouldEqual, "DATA")
   147  		}
   148  	})
   149  }