github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/tide/history/history_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package history 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "reflect" 26 "testing" 27 "time" 28 29 "cloud.google.com/go/storage" 30 "k8s.io/apimachinery/pkg/util/diff" 31 32 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 33 pkgio "sigs.k8s.io/prow/pkg/io" 34 ) 35 36 func TestHistory(t *testing.T) { 37 var nowTime = time.Now() 38 oldNow := now 39 now = func() time.Time { return nowTime } 40 defer func() { now = oldNow }() 41 42 const logSizeLimit = 3 43 nextTime := func() time.Time { 44 nowTime = nowTime.Add(time.Minute) 45 return nowTime 46 } 47 48 testMeta := func(num int, author string) prowapi.Pull { 49 return prowapi.Pull{ 50 Number: num, 51 Title: fmt.Sprintf("PR #%d", num), 52 SHA: fmt.Sprintf("SHA for %d", num), 53 Author: author, 54 } 55 } 56 57 hist, err := New(logSizeLimit, nil, "") 58 if err != nil { 59 t.Fatalf("Failed to create history client: %v", err) 60 } 61 time1 := nextTime() 62 hist.Record("pool A", "TRIGGER", "sha A", "", []prowapi.Pull{testMeta(1, "bob")}, nil) 63 nextTime() 64 hist.Record("pool B", "MERGE", "sha B1", "", []prowapi.Pull{testMeta(2, "joe")}, nil) 65 time3 := nextTime() 66 hist.Record("pool B", "MERGE", "sha B2", "", []prowapi.Pull{testMeta(3, "jeff")}, nil) 67 time4 := nextTime() 68 hist.Record("pool B", "MERGE_BATCH", "sha B3", "", []prowapi.Pull{testMeta(4, "joe"), testMeta(5, "jim")}, nil) 69 time5 := nextTime() 70 hist.Record("pool C", "TRIGGER_BATCH", "sha C1", "", []prowapi.Pull{testMeta(6, "joe"), testMeta(8, "me")}, nil) 71 time6 := nextTime() 72 hist.Record("pool B", "TRIGGER", "sha B4", "", []prowapi.Pull{testMeta(7, "abe")}, []string{}) 73 time7 := nextTime() 74 hist.Record("pool D", "TRIGGER", "sha D1", "", []prowapi.Pull{testMeta(8, "joe")}, []string{"testID"}) 75 76 expected := map[string][]*Record{ 77 "pool A": { 78 &Record{ 79 Time: time1, 80 BaseSHA: "sha A", 81 Action: "TRIGGER", 82 Target: []prowapi.Pull{ 83 testMeta(1, "bob"), 84 }, 85 }, 86 }, 87 "pool B": { 88 &Record{ 89 Time: time6, 90 BaseSHA: "sha B4", 91 Action: "TRIGGER", 92 Target: []prowapi.Pull{ 93 testMeta(7, "abe"), 94 }, 95 TenantIDs: []string{}, 96 }, 97 &Record{ 98 Time: time4, 99 BaseSHA: "sha B3", 100 Action: "MERGE_BATCH", 101 Target: []prowapi.Pull{ 102 testMeta(4, "joe"), 103 testMeta(5, "jim"), 104 }, 105 }, 106 &Record{ 107 Time: time3, 108 BaseSHA: "sha B2", 109 Action: "MERGE", 110 Target: []prowapi.Pull{ 111 testMeta(3, "jeff"), 112 }, 113 }, 114 }, 115 "pool C": { 116 &Record{ 117 Time: time5, 118 BaseSHA: "sha C1", 119 Action: "TRIGGER_BATCH", 120 Target: []prowapi.Pull{ 121 testMeta(6, "joe"), 122 testMeta(8, "me"), 123 }, 124 }, 125 }, 126 "pool D": { 127 &Record{ 128 Time: time7, 129 BaseSHA: "sha D1", 130 Action: "TRIGGER", 131 Target: []prowapi.Pull{ 132 testMeta(8, "joe"), 133 }, 134 TenantIDs: []string{"testID"}, 135 }, 136 }, 137 } 138 139 if got := hist.AllRecords(); !reflect.DeepEqual(got, expected) { 140 es, _ := json.Marshal(expected) 141 gs, _ := json.Marshal(got) 142 t.Errorf("Expected history \n%s, but got \n%s.", es, gs) 143 t.Logf("strs equal: %v.", string(es) == string(gs)) 144 } 145 } 146 147 const fakePath = "/some/random/path" 148 149 type testOpener struct { 150 content string 151 closed bool 152 dne bool 153 } 154 155 func (t *testOpener) Reader(ctx context.Context, path string) (io.ReadCloser, error) { 156 if t.dne { 157 return nil, storage.ErrObjectNotExist 158 } 159 if path != fakePath { 160 return nil, fmt.Errorf("path %q != expected %q", path, fakePath) 161 } 162 return t, nil 163 } 164 165 func (t *testOpener) Writer(ctx context.Context, path string, _ ...pkgio.WriterOptions) (io.WriteCloser, error) { 166 if path != fakePath { 167 return nil, fmt.Errorf("path %q != expected %q", path, fakePath) 168 } 169 return t, nil 170 } 171 172 func (t *testOpener) Write(p []byte) (n int, err error) { 173 if t.closed { 174 return 0, errors.New("writer is already closed") 175 } 176 t.content += string(p) 177 return len(p), nil 178 } 179 180 func (t *testOpener) Read(p []byte) (n int, err error) { 181 if t.closed { 182 return 0, errors.New("reader is already closed") 183 } 184 if len(t.content) == 0 { 185 return 0, io.EOF 186 } 187 defer func() { t.content = t.content[n:] }() 188 return copy(p, t.content), nil 189 } 190 191 func (t *testOpener) Close() error { 192 if t.closed { 193 return errors.New("already closed") 194 } 195 t.closed = true 196 return nil 197 } 198 199 func TestReadHistory(t *testing.T) { 200 tcs := []struct { 201 name string 202 raw string 203 maxRecsPerPool int 204 dne bool 205 expectedHist map[string]*recordLog 206 }{ 207 { 208 name: "read empty history", 209 raw: `{}`, 210 maxRecsPerPool: 3, 211 expectedHist: map[string]*recordLog{}, 212 }, 213 { 214 name: "read non-existent history", 215 dne: true, 216 maxRecsPerPool: 3, 217 expectedHist: map[string]*recordLog{}, 218 }, 219 { 220 name: "read simple history", 221 raw: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE"}]}`, 222 maxRecsPerPool: 3, 223 expectedHist: map[string]*recordLog{ 224 "o/r:b": {buff: []*Record{{Action: "MERGE"}}, head: 0, limit: 3}, 225 }, 226 }, 227 { 228 name: "read history with full recordLog", 229 raw: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE4"},{"time":"0001-01-01T00:00:00Z","action":"MERGE3"},{"time":"0001-01-01T00:00:00Z","action":"MERGE2"}]}`, 230 maxRecsPerPool: 3, 231 expectedHist: map[string]*recordLog{ 232 "o/r:b": {buff: []*Record{{Action: "MERGE2"}, {Action: "MERGE3"}, {Action: "MERGE4"}}, head: 2, limit: 3}, 233 }, 234 }, 235 { 236 name: "read history, with multiple pools", 237 raw: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE"}],"o/r:b2":[{"time":"0001-01-01T00:00:00Z","action":"MERGE2"},{"time":"0001-01-01T00:00:00Z","action":"MERGE"}]}`, 238 maxRecsPerPool: 3, 239 expectedHist: map[string]*recordLog{ 240 "o/r:b": {buff: []*Record{{Action: "MERGE"}}, head: 0, limit: 3}, 241 "o/r:b2": {buff: []*Record{{Action: "MERGE"}, {Action: "MERGE2"}}, head: 1, limit: 3}, 242 }, 243 }, 244 { 245 name: "read and truncate", 246 raw: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE3"},{"time":"0001-01-01T00:00:00Z","action":"MERGE2"},{"time":"0001-01-01T00:00:00Z","action":"MERGE1"}]}`, 247 maxRecsPerPool: 2, 248 expectedHist: map[string]*recordLog{ 249 "o/r:b": {buff: []*Record{{Action: "MERGE2"}, {Action: "MERGE3"}}, head: 1, limit: 2}, 250 }, 251 }, 252 { 253 name: "read and grow record log", 254 raw: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE3"},{"time":"0001-01-01T00:00:00Z","action":"MERGE2"},{"time":"0001-01-01T00:00:00Z","action":"MERGE1"}]}`, 255 maxRecsPerPool: 5, 256 expectedHist: map[string]*recordLog{ 257 "o/r:b": {buff: []*Record{{Action: "MERGE1"}, {Action: "MERGE2"}, {Action: "MERGE3"}}, head: 2, limit: 5}, 258 }, 259 }, 260 } 261 262 for _, tc := range tcs { 263 t.Run(tc.name, func(t *testing.T) { 264 obj := &testOpener{content: tc.raw, dne: tc.dne} 265 hist, err := readHistory(tc.maxRecsPerPool, obj, fakePath) 266 if err != nil { 267 t.Fatalf("Unexpected error reading history: %v.", err) 268 } 269 if !reflect.DeepEqual(hist, tc.expectedHist) { 270 t.Errorf("Unexpected diff between loaded history and expected history: %v.", diff.ObjectReflectDiff(hist, tc.expectedHist)) 271 } 272 if !obj.closed && !tc.dne { 273 t.Errorf("Reader was not closed.") 274 } 275 }) 276 } 277 } 278 279 func TestWriteHistory(t *testing.T) { 280 tcs := []struct { 281 name string 282 recMap map[string][]*Record 283 expectedWritten string 284 }{ 285 { 286 name: "write empty history", 287 recMap: map[string][]*Record{}, 288 expectedWritten: `{}`, 289 }, 290 { 291 name: "write simple history", 292 recMap: map[string][]*Record{ 293 "o/r:b": {{Action: "MERGE"}}, 294 }, 295 expectedWritten: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE","tenantids":null}]}`, 296 }, 297 { 298 name: "write history with multiple records", 299 recMap: map[string][]*Record{ 300 "o/r:b": {{Action: "MERGE3"}, {Action: "MERGE2"}, {Action: "MERGE1"}}, 301 }, 302 expectedWritten: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE3","tenantids":null},{"time":"0001-01-01T00:00:00Z","action":"MERGE2","tenantids":null},{"time":"0001-01-01T00:00:00Z","action":"MERGE1","tenantids":null}]}`, 303 }, 304 { 305 name: "write history, with multiple pools", 306 recMap: map[string][]*Record{ 307 "o/r:b": {{Action: "MERGE"}}, 308 "o/r:b2": {{Action: "MERGE2"}, {Action: "MERGE1"}}, 309 }, 310 expectedWritten: `{"o/r:b":[{"time":"0001-01-01T00:00:00Z","action":"MERGE","tenantids":null}],"o/r:b2":[{"time":"0001-01-01T00:00:00Z","action":"MERGE2","tenantids":null},{"time":"0001-01-01T00:00:00Z","action":"MERGE1","tenantids":null}]}`, 311 }, 312 } 313 314 for _, tc := range tcs { 315 t.Run(tc.name, func(t *testing.T) { 316 obj := &testOpener{} 317 if err := writeHistory(obj, fakePath, tc.recMap); err != nil { 318 t.Fatalf("Unexpected error writing history: %v.", err) 319 } 320 if obj.content != tc.expectedWritten { 321 t.Errorf("Expected write:\n%s\nbut got:\n%s", tc.expectedWritten, obj.content) 322 } 323 if !obj.closed { 324 t.Errorf("Writer was not closed.") 325 } 326 }) 327 } 328 }