// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package maintner import ( "context" "fmt" "reflect" "testing" ) func TestSumSegSize(t *testing.T) { tests := []struct { in []fileSeg want int64 }{ { in: []fileSeg{fileSeg{size: 1}}, want: 1, }, { in: []fileSeg{fileSeg{size: 1}, fileSeg{size: 100}}, want: 101, }, { in: nil, want: 0, }, } for i, tt := range tests { got := sumSegSize(tt.in) if got != tt.want { t.Errorf("%d. sumSegSize = %v; want %v", i, got, tt.want) } } } func TestSumCommonPrefixSize(t *testing.T) { tests := []struct { a, b []fileSeg summer func(file string, n int64) string want int64 }{ { a: []fileSeg{fileSeg{size: 1, sha224: "abab"}}, b: []fileSeg{fileSeg{size: 1, sha224: "abab"}}, want: 1, }, { a: []fileSeg{fileSeg{size: 1, sha224: "abab"}}, b: []fileSeg{fileSeg{size: 1, sha224: "eeee"}}, want: 0, }, { a: []fileSeg{ fileSeg{size: 100, sha224: "abab"}, fileSeg{size: 100, sha224: "abab", file: "a.mutlog"}, }, b: []fileSeg{ fileSeg{size: 100, sha224: "abab"}, fileSeg{size: 50, sha224: "cccc"}, }, summer: func(file string, n int64) string { if file == "a.mutlog" && n == 50 { return "cccc" } return "xxx" }, want: 150, }, { a: []fileSeg{ fileSeg{size: 100, sha224: "abab"}, fileSeg{size: 50, sha224: "cccc"}, }, b: []fileSeg{ fileSeg{size: 100, sha224: "abab"}, fileSeg{size: 100, sha224: "abab", file: "b.mutlog"}, }, summer: func(file string, n int64) string { if file == "b.mutlog" && n == 50 { return "cccc" } return "xxx" }, want: 150, }, } for i, tt := range tests { summer := tt.summer if summer == nil { summer = func(file string, n int64) string { t.Errorf("%d. unexpected call to prefix summer for file=%q, n=%v", i, file, n) return "" } } ns := &netMutSource{ testHookFilePrefixSum224: summer, } got := ns.sumCommonPrefixSize(tt.a, tt.b) if got != tt.want { t.Errorf("%d. sumCommonPrefixSize = %v; want %v", i, got, tt.want) } } } func TestTrimLeadingSegBytes(t *testing.T) { tests := []struct { in []fileSeg trim int64 want []fileSeg }{ { in: []fileSeg{fileSeg{size: 100}, fileSeg{size: 50}}, trim: 0, want: []fileSeg{fileSeg{size: 100}, fileSeg{size: 50}}, }, { in: []fileSeg{fileSeg{size: 100}, fileSeg{size: 50}}, trim: 150, want: nil, }, { in: []fileSeg{fileSeg{size: 100}, fileSeg{size: 50}}, trim: 100, want: []fileSeg{fileSeg{size: 50}}, }, { in: []fileSeg{fileSeg{size: 100}, fileSeg{size: 50}}, trim: 25, want: []fileSeg{fileSeg{size: 100, skip: 25}, fileSeg{size: 50}}, }, } for i, tt := range tests { copyIn := append([]fileSeg(nil), tt.in...) got := trimLeadingSegBytes(tt.in, tt.trim) if !reflect.DeepEqual(tt.in, copyIn) { t.Fatalf("%d. trimLeadingSegBytes modified its input", i) } if !reflect.DeepEqual(got, tt.want) { t.Fatalf("%d. trim = %+v; want %+v", i, got, tt.want) } } } func TestGetNewSegments(t *testing.T) { type testCase struct { name string lastSegs []fileSeg serverSegs [][]LogSegmentJSON // prefixSum is the prefix sum to use if called. // If empty, prefixSum calls are errors. prefixSum string want []fileSeg wantSplit bool wantSumCommon int64 wantUnchanged bool } tests := []testCase{ { name: "first_download", serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 100, SHA224: "abc"}, {Number: 2, Size: 200, SHA224: "def"}, }, }, want: []fileSeg{ {seg: 1, size: 100, sha224: "abc", file: "/fake/0001.mutlog"}, {seg: 2, size: 200, sha224: "def", file: "/fake/0002.mutlog"}, }, }, { name: "incremental_download_growseg", // from first_download, segment 2 grows a bit lastSegs: []fileSeg{ {seg: 1, size: 100, sha224: "abc", file: "/fake/0001.mutlog"}, {seg: 2, size: 200, sha224: "def", file: "/fake/0002.mutlog"}, }, prefixSum: "def", serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 100, SHA224: "abc"}, {Number: 2, Size: 205, SHA224: "defdef"}, }, }, want: []fileSeg{ {seg: 2, size: 205, sha224: "defdef", skip: 200, file: "/fake/0002.mutlog"}, }, }, { name: "incremental_download_growseg_and_newseg", // from first_download, segment 2 grows, and segment 3 appears. lastSegs: []fileSeg{ {seg: 1, size: 100, sha224: "abc", file: "/fake/0001.mutlog"}, {seg: 2, size: 200, sha224: "def", file: "/fake/0002.mutlog"}, }, prefixSum: "def", serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 100, SHA224: "abc"}, {Number: 2, Size: 250, SHA224: "defdef"}, {Number: 3, Size: 300, SHA224: "fff"}, }, }, want: []fileSeg{ {seg: 2, size: 250, sha224: "defdef", skip: 200, file: "/fake/0002.mutlog"}, {seg: 3, size: 300, sha224: "fff", skip: 0, file: "/fake/0003.mutlog"}, }, }, { name: "incremental_download_newseg", // from first_download, segment 3 appears. lastSegs: []fileSeg{ {seg: 1, size: 100, sha224: "abc", file: "/fake/0001.mutlog"}, {seg: 2, size: 200, sha224: "def", file: "/fake/0002.mutlog"}, }, serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 100, SHA224: "abc"}, {Number: 2, Size: 200, SHA224: "def"}, {Number: 3, Size: 300, SHA224: "fff"}, }, }, want: []fileSeg{ {seg: 3, size: 300, sha224: "fff", skip: 0, file: "/fake/0003.mutlog"}, }, }, { name: "faulty_server_returns_no_new_data", lastSegs: []fileSeg{ {seg: 1, size: 101, sha224: "abc", file: "/fake/0001.mutlog"}, }, serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 101, SHA224: "abc"}, // Same as lastSegs, results in unchanged error. }, []LogSegmentJSON{ {Number: 1, Size: 101, SHA224: "abc"}, {Number: 2, Size: 102, SHA224: "def"}, }, }, wantUnchanged: true, }, { name: "split_error_diff_first_seg_same_size", lastSegs: []fileSeg{ {seg: 1, size: 101, sha224: "abc", file: "/fake/0001.mutlog"}, }, serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 101, SHA224: "def"}, }, }, wantSplit: true, }, { name: "split_error_diff_first_seg_and_longer", lastSegs: []fileSeg{ {seg: 1, size: 101, sha224: "abc", file: "/fake/0001.mutlog"}, }, serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 102, SHA224: "def"}, }, }, prefixSum: "ffffffffff", // no match wantSplit: true, }, { name: "split_error_diff_first_seg_and_shorter", lastSegs: []fileSeg{ {seg: 1, size: 101, sha224: "abc", file: "/fake/0001.mutlog"}, }, serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 50, SHA224: "def"}, }, }, prefixSum: "ffffffffff", // no match wantSplit: true, }, { name: "split_error_same_first_seg_but_shorter", lastSegs: []fileSeg{ {seg: 1, size: 101, sha224: "abc", file: "/fake/0001.mutlog"}, }, serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 50, SHA224: "def"}, }, }, prefixSum: "def", // match wantSplit: true, wantSumCommon: 50, }, { name: "split_error_diff_final_seg", lastSegs: []fileSeg{ {seg: 1, size: 100, sha224: "abc", file: "/fake/0001.mutlog"}, {seg: 2, size: 2, sha224: "def", file: "/fake/0002.mutlog"}, }, serverSegs: [][]LogSegmentJSON{ []LogSegmentJSON{ {Number: 1, Size: 100, SHA224: "abc"}, {Number: 2, Size: 4, SHA224: "fff"}, }, }, prefixSum: "not_def", wantSplit: true, wantSumCommon: 100, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { serverSegCalls := 0 syncSegCalls := 0 ns := &netMutSource{ last: tt.lastSegs, testHookGetServerSegments: func(_ context.Context, waitSizeNot int64) (segs []LogSegmentJSON, err error) { serverSegCalls++ if serverSegCalls%2 == 1 { return nil, fetchError{PossiblyRetryable: true, Err: fmt.Errorf("fake error to simulate the internet saying 'not this time' every now and then")} } if len(tt.serverSegs) == 0 { return nil, nil } segs = tt.serverSegs[0] if len(tt.serverSegs) > 1 { tt.serverSegs = tt.serverSegs[1:] } return segs, nil }, testHookSyncSeg: func(_ context.Context, seg LogSegmentJSON) (fileSeg, []byte, error) { syncSegCalls++ if syncSegCalls%3 == 1 { return fileSeg{}, nil, fetchError{PossiblyRetryable: true, Err: fmt.Errorf("fake error to simulate the internet saying 'not this time' every now and then")} } return fileSeg{ seg: seg.Number, size: seg.Size, sha224: seg.SHA224, file: fmt.Sprintf("/fake/%04d.mutlog", seg.Number), }, nil, nil }, testHookOnSplit: func(sumCommon int64) { if got, want := sumCommon, tt.wantSumCommon; got != want { t.Errorf("sumCommon = %v; want %v", got, want) } }, testHookFilePrefixSum224: func(file string, n int64) string { if tt.prefixSum != "" { return tt.prefixSum } t.Errorf("unexpected call to filePrefixSum224(%q, %d)", file, n) return "XXXX" }, } got, err := ns.getNewSegments(context.Background()) if tt.wantSplit { if err != ErrSplit { t.Fatalf("wanted ErrSplit; got %+v, %v", got, err) } // Success. return } if tt.wantUnchanged { if err == nil || err.Error() != "maintner.netsource: maintnerd server returned unchanged log segments" { t.Fatalf("wanted unchanged; got %+v, %v", got, err) } // Success. return } if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("mismatch\n got: %+v\nwant: %+v\n", got, tt.want) } }) } }