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  }