1/* 2Copyright 2013 Google Inc. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package groupcache 18 19import ( 20 "context" 21 "errors" 22 "flag" 23 "log" 24 "net" 25 "net/http" 26 "os" 27 "os/exec" 28 "strconv" 29 "strings" 30 "sync" 31 "testing" 32 "time" 33) 34 35var ( 36 peerAddrs = flag.String("test_peer_addrs", "", "Comma-separated list of peer addresses; used by TestHTTPPool") 37 peerIndex = flag.Int("test_peer_index", -1, "Index of which peer this child is; used by TestHTTPPool") 38 peerChild = flag.Bool("test_peer_child", false, "True if running as a child process; used by TestHTTPPool") 39) 40 41func TestHTTPPool(t *testing.T) { 42 if *peerChild { 43 beChildForTestHTTPPool() 44 os.Exit(0) 45 } 46 47 const ( 48 nChild = 4 49 nGets = 100 50 ) 51 52 var childAddr []string 53 for i := 0; i < nChild; i++ { 54 childAddr = append(childAddr, pickFreeAddr(t)) 55 } 56 57 var cmds []*exec.Cmd 58 var wg sync.WaitGroup 59 for i := 0; i < nChild; i++ { 60 cmd := exec.Command(os.Args[0], 61 "--test.run=TestHTTPPool", 62 "--test_peer_child", 63 "--test_peer_addrs="+strings.Join(childAddr, ","), 64 "--test_peer_index="+strconv.Itoa(i), 65 ) 66 cmds = append(cmds, cmd) 67 wg.Add(1) 68 if err := cmd.Start(); err != nil { 69 t.Fatal("failed to start child process: ", err) 70 } 71 go awaitAddrReady(t, childAddr[i], &wg) 72 } 73 defer func() { 74 for i := 0; i < nChild; i++ { 75 if cmds[i].Process != nil { 76 cmds[i].Process.Kill() 77 } 78 } 79 }() 80 wg.Wait() 81 82 // Use a dummy self address so that we don't handle gets in-process. 83 p := NewHTTPPool("should-be-ignored") 84 p.Set(addrToURL(childAddr)...) 85 86 // Dummy getter function. Gets should go to children only. 87 // The only time this process will handle a get is when the 88 // children can't be contacted for some reason. 89 getter := GetterFunc(func(ctx context.Context, key string, dest Sink) error { 90 return errors.New("parent getter called; something's wrong") 91 }) 92 g := NewGroup("httpPoolTest", 1<<20, getter) 93 94 for _, key := range testKeys(nGets) { 95 var value string 96 if err := g.Get(context.TODO(), key, StringSink(&value)); err != nil { 97 t.Fatal(err) 98 } 99 if suffix := ":" + key; !strings.HasSuffix(value, suffix) { 100 t.Errorf("Get(%q) = %q, want value ending in %q", key, value, suffix) 101 } 102 t.Logf("Get key=%q, value=%q (peer:key)", key, value) 103 } 104} 105 106func testKeys(n int) (keys []string) { 107 keys = make([]string, n) 108 for i := range keys { 109 keys[i] = strconv.Itoa(i) 110 } 111 return 112} 113 114func beChildForTestHTTPPool() { 115 addrs := strings.Split(*peerAddrs, ",") 116 117 p := NewHTTPPool("http://" + addrs[*peerIndex]) 118 p.Set(addrToURL(addrs)...) 119 120 getter := GetterFunc(func(ctx context.Context, key string, dest Sink) error { 121 dest.SetString(strconv.Itoa(*peerIndex) + ":" + key) 122 return nil 123 }) 124 NewGroup("httpPoolTest", 1<<20, getter) 125 126 log.Fatal(http.ListenAndServe(addrs[*peerIndex], p)) 127} 128 129// This is racy. Another process could swoop in and steal the port between the 130// call to this function and the next listen call. Should be okay though. 131// The proper way would be to pass the l.File() as ExtraFiles to the child 132// process, and then close your copy once the child starts. 133func pickFreeAddr(t *testing.T) string { 134 l, err := net.Listen("tcp", "127.0.0.1:0") 135 if err != nil { 136 t.Fatal(err) 137 } 138 defer l.Close() 139 return l.Addr().String() 140} 141 142func addrToURL(addr []string) []string { 143 url := make([]string, len(addr)) 144 for i := range addr { 145 url[i] = "http://" + addr[i] 146 } 147 return url 148} 149 150func awaitAddrReady(t *testing.T, addr string, wg *sync.WaitGroup) { 151 defer wg.Done() 152 const max = 1 * time.Second 153 tries := 0 154 for { 155 tries++ 156 c, err := net.Dial("tcp", addr) 157 if err == nil { 158 c.Close() 159 return 160 } 161 delay := time.Duration(tries) * 25 * time.Millisecond 162 if delay > max { 163 delay = max 164 } 165 time.Sleep(delay) 166 } 167} 168