github.com/derat/nup@v0.0.0-20230418113745-15592ba7c620/cmd/nup/client/task_cache_test.go (about) 1 // Copyright 2022 Daniel Erat. 2 // All rights reserved. 3 4 package client 5 6 import ( 7 "errors" 8 "reflect" 9 "testing" 10 "time" 11 ) 12 13 func TestTaskCache(t *testing.T) { 14 const maxTasks = 2 15 c := NewTaskCache(maxTasks) 16 17 type res struct { 18 k string 19 v interface{} 20 err error 21 } 22 done := make(chan res) 23 24 type taskCtl struct { 25 running <-chan struct{} // closed if/when task is running 26 finish chan<- map[string]interface{} // send to tell task to finish, close to report error 27 } 28 29 taskErr := errors.New("intentional error") 30 31 var gets int 32 get := func(itemKey, taskKey string) taskCtl { 33 gets++ 34 running := make(chan struct{}) 35 finish := make(chan map[string]interface{}) 36 go func() { 37 v, err := c.Get(itemKey, taskKey, func() (map[string]interface{}, error) { 38 close(running) 39 m, ok := <-finish 40 if !ok { 41 return nil, taskErr 42 } 43 return m, nil 44 }) 45 done <- res{itemKey, v, err} 46 }() 47 return taskCtl{running, finish} 48 } 49 50 // Returns true if ch appears to be closed. 51 closed := func(ch <-chan struct{}) bool { 52 select { 53 case <-ch: 54 return true 55 case <-time.After(20 * time.Millisecond): 56 return false 57 } 58 } 59 60 // Request '0' via a task named 'a'. 61 t0 := get("0", "a") 62 <-t0.running 63 64 // Request '1' via a duplicate task that shouldn't be run. 65 t1 := get("1", "a") 66 if closed(t1.running) { 67 t.Error("Duplicate task was started eventually") 68 } 69 70 // Request '2' and wait for it to start running. 71 t2 := get("2", "b") 72 <-t2.running 73 74 // Request '3' and check that its task doesn't start immediately (since we're already running 75 // two tasks). 76 t3 := get("3", "c") 77 if closed(t3.running) { 78 t.Error("Extra task was started") 79 } 80 81 close(t2.finish) // trigger error 82 <-t3.running 83 t3.finish <- map[string]interface{}{"3": "soup"} 84 t0.finish <- map[string]interface{}{"0": "foo", "1": "bar"} 85 86 // Request '4' via a new task with the same key as the one that returned an error. 87 t4 := get("4", "b") 88 <-t4.running 89 t4.finish <- map[string]interface{}{"4": "bananas"} 90 91 if closed(t1.running) { 92 t.Error("Duplicate task was started eventually") 93 } 94 95 got := make(map[string]interface{}) 96 for i := 0; i < gets; i++ { 97 if r := <-done; r.err != nil { 98 got[r.k] = r.err 99 } else { 100 got[r.k] = r.v 101 } 102 } 103 if want := map[string]interface{}{ 104 "0": "foo", 105 "1": "bar", 106 "2": taskErr, 107 "3": "soup", 108 "4": "bananas", 109 }; !reflect.DeepEqual(got, want) { 110 t.Errorf("Got %v; want %v", got, want) 111 } 112 }