You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							477 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							477 lines
						
					
					
						
							12 KiB
						
					
					
				| package needle_map | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"math/rand" | |
| 	"reflect" | |
| 	"testing" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/types" | |
| ) | |
| 
 | |
| func TestSegmentBsearchKey(t *testing.T) { | |
| 	testSegment := &CompactMapSegment{ | |
| 		list: []CompactNeedleValue{ | |
| 			CompactNeedleValue{key: 10}, | |
| 			CompactNeedleValue{key: 20}, | |
| 			CompactNeedleValue{key: 21}, | |
| 			CompactNeedleValue{key: 26}, | |
| 			CompactNeedleValue{key: 30}, | |
| 		}, | |
| 		firstKey: 10, | |
| 		lastKey:  30, | |
| 	} | |
| 
 | |
| 	testCases := []struct { | |
| 		name      string | |
| 		cs        *CompactMapSegment | |
| 		key       types.NeedleId | |
| 		wantIndex int | |
| 		wantFound bool | |
| 	}{ | |
| 		{ | |
| 			name:      "empty segment", | |
| 			cs:        newCompactMapSegment(0), | |
| 			key:       123, | |
| 			wantIndex: 0, | |
| 			wantFound: false, | |
| 		}, | |
| 		{ | |
| 			name:      "new key, insert at beggining", | |
| 			cs:        testSegment, | |
| 			key:       5, | |
| 			wantIndex: 0, | |
| 			wantFound: false, | |
| 		}, | |
| 		{ | |
| 			name:      "new key, insert at end", | |
| 			cs:        testSegment, | |
| 			key:       100, | |
| 			wantIndex: 5, | |
| 			wantFound: false, | |
| 		}, | |
| 		{ | |
| 			name:      "new key, insert second", | |
| 			cs:        testSegment, | |
| 			key:       12, | |
| 			wantIndex: 1, | |
| 			wantFound: false, | |
| 		}, | |
| 		{ | |
| 			name:      "new key, insert in middle", | |
| 			cs:        testSegment, | |
| 			key:       23, | |
| 			wantIndex: 3, | |
| 			wantFound: false, | |
| 		}, | |
| 		{ | |
| 			name:      "key #1", | |
| 			cs:        testSegment, | |
| 			key:       10, | |
| 			wantIndex: 0, | |
| 			wantFound: true, | |
| 		}, | |
| 		{ | |
| 			name:      "key #2", | |
| 			cs:        testSegment, | |
| 			key:       20, | |
| 			wantIndex: 1, | |
| 			wantFound: true, | |
| 		}, | |
| 		{ | |
| 			name:      "key #3", | |
| 			cs:        testSegment, | |
| 			key:       21, | |
| 			wantIndex: 2, | |
| 			wantFound: true, | |
| 		}, | |
| 		{ | |
| 			name:      "key #4", | |
| 			cs:        testSegment, | |
| 			key:       26, | |
| 			wantIndex: 3, | |
| 			wantFound: true, | |
| 		}, | |
| 		{ | |
| 			name:      "key #5", | |
| 			cs:        testSegment, | |
| 			key:       30, | |
| 			wantIndex: 4, | |
| 			wantFound: true, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tc := range testCases { | |
| 		t.Run(tc.name, func(t *testing.T) { | |
| 			index, found := tc.cs.bsearchKey(tc.key) | |
| 			if got, want := index, tc.wantIndex; got != want { | |
| 				t.Errorf("expected %v, got %v", want, got) | |
| 			} | |
| 			if got, want := found, tc.wantFound; got != want { | |
| 				t.Errorf("expected %v, got %v", want, got) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| func TestSegmentSet(t *testing.T) { | |
| 	testSegment := &CompactMapSegment{ | |
| 		list: []CompactNeedleValue{ | |
| 			CompactNeedleValue{key: 10, offset: OffsetToCompact(types.Uint32ToOffset(0)), size: 100}, | |
| 			CompactNeedleValue{key: 20, offset: OffsetToCompact(types.Uint32ToOffset(100)), size: 200}, | |
| 			CompactNeedleValue{key: 30, offset: OffsetToCompact(types.Uint32ToOffset(300)), size: 300}, | |
| 		}, | |
| 		firstKey: 10, | |
| 		lastKey:  30, | |
| 	} | |
| 
 | |
| 	if got, want := testSegment.len(), 3; got != want { | |
| 		t.Errorf("got starting size %d, want %d", got, want) | |
| 	} | |
| 	if got, want := testSegment.cap(), 3; got != want { | |
| 		t.Errorf("got starting capacity %d, want %d", got, want) | |
| 	} | |
| 
 | |
| 	testSets := []struct { | |
| 		name       string | |
| 		key        types.NeedleId | |
| 		offset     types.Offset | |
| 		size       types.Size | |
| 		wantOffset types.Offset | |
| 		wantSize   types.Size | |
| 	}{ | |
| 		{ | |
| 			name: "insert at beggining", | |
| 			key:  5, offset: types.Uint32ToOffset(1000), size: 123, | |
| 			wantOffset: types.Uint32ToOffset(0), wantSize: 0, | |
| 		}, | |
| 		{ | |
| 			name: "insert at end", | |
| 			key:  51, offset: types.Uint32ToOffset(7000), size: 456, | |
| 			wantOffset: types.Uint32ToOffset(0), wantSize: 0, | |
| 		}, | |
| 		{ | |
| 			name: "insert in middle", | |
| 			key:  25, offset: types.Uint32ToOffset(8000), size: 789, | |
| 			wantOffset: types.Uint32ToOffset(0), wantSize: 0, | |
| 		}, | |
| 		{ | |
| 			name: "update existing", | |
| 			key:  30, offset: types.Uint32ToOffset(9000), size: 999, | |
| 			wantOffset: types.Uint32ToOffset(300), wantSize: 300, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, ts := range testSets { | |
| 		offset, size := testSegment.set(ts.key, ts.offset, ts.size) | |
| 		if offset != ts.wantOffset { | |
| 			t.Errorf("%s: got offset %v, want %v", ts.name, offset, ts.wantOffset) | |
| 		} | |
| 		if size != ts.wantSize { | |
| 			t.Errorf("%s: got size %v, want %v", ts.name, size, ts.wantSize) | |
| 		} | |
| 	} | |
| 
 | |
| 	wantSegment := &CompactMapSegment{ | |
| 		list: []CompactNeedleValue{ | |
| 			CompactNeedleValue{key: 5, offset: OffsetToCompact(types.Uint32ToOffset(1000)), size: 123}, | |
| 			CompactNeedleValue{key: 10, offset: OffsetToCompact(types.Uint32ToOffset(0)), size: 100}, | |
| 			CompactNeedleValue{key: 20, offset: OffsetToCompact(types.Uint32ToOffset(100)), size: 200}, | |
| 			CompactNeedleValue{key: 25, offset: OffsetToCompact(types.Uint32ToOffset(8000)), size: 789}, | |
| 			CompactNeedleValue{key: 30, offset: OffsetToCompact(types.Uint32ToOffset(9000)), size: 999}, | |
| 			CompactNeedleValue{key: 51, offset: OffsetToCompact(types.Uint32ToOffset(7000)), size: 456}, | |
| 		}, | |
| 		firstKey: 5, | |
| 		lastKey:  51, | |
| 	} | |
| 	if !reflect.DeepEqual(testSegment, wantSegment) { | |
| 		t.Errorf("got result segment %v, want %v", testSegment, wantSegment) | |
| 	} | |
| 
 | |
| 	if got, want := testSegment.len(), 6; got != want { | |
| 		t.Errorf("got result size %d, want %d", got, want) | |
| 	} | |
| 	if got, want := testSegment.cap(), 6; got != want { | |
| 		t.Errorf("got result capacity %d, want %d", got, want) | |
| 	} | |
| } | |
| 
 | |
| func TestSegmentSetOrdering(t *testing.T) { | |
| 	keys := []types.NeedleId{} | |
| 	for i := 0; i < SegmentChunkSize; i++ { | |
| 		keys = append(keys, types.NeedleId(i)) | |
| 	} | |
| 
 | |
| 	r := rand.New(rand.NewSource(123456789)) | |
| 	r.Shuffle(len(keys), func(i, j int) { keys[i], keys[j] = keys[j], keys[i] }) | |
| 
 | |
| 	cs := newCompactMapSegment(0) | |
| 	for _, k := range keys { | |
| 		_, _ = cs.set(k, types.Uint32ToOffset(123), 456) | |
| 	} | |
| 	if got, want := cs.len(), SegmentChunkSize; got != want { | |
| 		t.Errorf("expected size %d, got %d", want, got) | |
| 	} | |
| 	for i := 1; i < cs.len(); i++ { | |
| 		if ka, kb := cs.list[i-1].key, cs.list[i].key; ka >= kb { | |
| 			t.Errorf("found out of order entries at (%d, %d) = (%d, %d)", i-1, i, ka, kb) | |
| 		} | |
| 	} | |
| } | |
| 
 | |
| func TestSegmentGet(t *testing.T) { | |
| 	testSegment := &CompactMapSegment{ | |
| 		list: []CompactNeedleValue{ | |
| 			CompactNeedleValue{key: 10, offset: OffsetToCompact(types.Uint32ToOffset(0)), size: 100}, | |
| 			CompactNeedleValue{key: 20, offset: OffsetToCompact(types.Uint32ToOffset(100)), size: 200}, | |
| 			CompactNeedleValue{key: 30, offset: OffsetToCompact(types.Uint32ToOffset(300)), size: 300}, | |
| 		}, | |
| 		firstKey: 10, | |
| 		lastKey:  30, | |
| 	} | |
| 
 | |
| 	testCases := []struct { | |
| 		name      string | |
| 		key       types.NeedleId | |
| 		wantValue *CompactNeedleValue | |
| 		wantFound bool | |
| 	}{ | |
| 		{ | |
| 			name:      "invalid key", | |
| 			key:       99, | |
| 			wantValue: nil, | |
| 			wantFound: false, | |
| 		}, | |
| 		{ | |
| 			name:      "key #1", | |
| 			key:       10, | |
| 			wantValue: &testSegment.list[0], | |
| 			wantFound: true, | |
| 		}, | |
| 		{ | |
| 			name:      "key #2", | |
| 			key:       20, | |
| 			wantValue: &testSegment.list[1], | |
| 			wantFound: true, | |
| 		}, | |
| 		{ | |
| 			name:      "key #3", | |
| 			key:       30, | |
| 			wantValue: &testSegment.list[2], | |
| 			wantFound: true, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tc := range testCases { | |
| 		t.Run(tc.name, func(t *testing.T) { | |
| 			value, found := testSegment.get(tc.key) | |
| 			if got, want := value, tc.wantValue; got != want { | |
| 				t.Errorf("got %v, want %v", got, want) | |
| 			} | |
| 			if got, want := found, tc.wantFound; got != want { | |
| 				t.Errorf("got %v, want %v", got, want) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| func TestSegmentDelete(t *testing.T) { | |
| 	testSegment := &CompactMapSegment{ | |
| 		list: []CompactNeedleValue{ | |
| 			CompactNeedleValue{key: 10, offset: OffsetToCompact(types.Uint32ToOffset(0)), size: 100}, | |
| 			CompactNeedleValue{key: 20, offset: OffsetToCompact(types.Uint32ToOffset(100)), size: 200}, | |
| 			CompactNeedleValue{key: 30, offset: OffsetToCompact(types.Uint32ToOffset(300)), size: 300}, | |
| 			CompactNeedleValue{key: 40, offset: OffsetToCompact(types.Uint32ToOffset(600)), size: 400}, | |
| 		}, | |
| 		firstKey: 10, | |
| 		lastKey:  40, | |
| 	} | |
| 
 | |
| 	testDeletes := []struct { | |
| 		name string | |
| 		key  types.NeedleId | |
| 		want types.Size | |
| 	}{ | |
| 		{ | |
| 			name: "invalid key", | |
| 			key:  99, | |
| 			want: 0, | |
| 		}, | |
| 		{ | |
| 			name: "delete key #2", | |
| 			key:  20, | |
| 			want: 200, | |
| 		}, | |
| 		{ | |
| 			name: "delete key #4", | |
| 			key:  40, | |
| 			want: 400, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, td := range testDeletes { | |
| 		size := testSegment.delete(td.key) | |
| 		if got, want := size, td.want; got != want { | |
| 			t.Errorf("%s: got %v, want %v", td.name, got, want) | |
| 		} | |
| 	} | |
| 
 | |
| 	wantSegment := &CompactMapSegment{ | |
| 		list: []CompactNeedleValue{ | |
| 			CompactNeedleValue{key: 10, offset: OffsetToCompact(types.Uint32ToOffset(0)), size: 100}, | |
| 			CompactNeedleValue{key: 20, offset: OffsetToCompact(types.Uint32ToOffset(100)), size: -200}, | |
| 			CompactNeedleValue{key: 30, offset: OffsetToCompact(types.Uint32ToOffset(300)), size: 300}, | |
| 			CompactNeedleValue{key: 40, offset: OffsetToCompact(types.Uint32ToOffset(600)), size: -400}, | |
| 		}, | |
| 		firstKey: 10, | |
| 		lastKey:  40, | |
| 	} | |
| 	if !reflect.DeepEqual(testSegment, wantSegment) { | |
| 		t.Errorf("got result segment %v, want %v", testSegment, wantSegment) | |
| 	} | |
| } | |
| 
 | |
| func TestSegmentForKey(t *testing.T) { | |
| 	testMap := NewCompactMap() | |
| 
 | |
| 	tests := []struct { | |
| 		name string | |
| 		key  types.NeedleId | |
| 		want *CompactMapSegment | |
| 	}{ | |
| 		{ | |
| 			name: "first segment", | |
| 			key:  12, | |
| 			want: &CompactMapSegment{ | |
| 				list:     []CompactNeedleValue{}, | |
| 				chunk:    0, | |
| 				firstKey: MaxCompactKey, | |
| 				lastKey:  0, | |
| 			}, | |
| 		}, | |
| 		{ | |
| 			name: "second segment, gapless", | |
| 			key:  SegmentChunkSize + 34, | |
| 			want: &CompactMapSegment{ | |
| 				list:     []CompactNeedleValue{}, | |
| 				chunk:    1, | |
| 				firstKey: MaxCompactKey, | |
| 				lastKey:  0, | |
| 			}, | |
| 		}, | |
| 		{ | |
| 			name: "gapped segment", | |
| 			key:  (5 * SegmentChunkSize) + 56, | |
| 			want: &CompactMapSegment{ | |
| 				list:     []CompactNeedleValue{}, | |
| 				chunk:    5, | |
| 				firstKey: MaxCompactKey, | |
| 				lastKey:  0, | |
| 			}, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tc := range tests { | |
| 		t.Run(tc.name, func(t *testing.T) { | |
| 			cs := testMap.segmentForKey(tc.key) | |
| 			if !reflect.DeepEqual(cs, tc.want) { | |
| 				t.Errorf("got segment %v, want %v", cs, tc.want) | |
| 			} | |
| 		}) | |
| 	} | |
| 
 | |
| 	wantMap := &CompactMap{ | |
| 		segments: map[Chunk]*CompactMapSegment{ | |
| 			0: &CompactMapSegment{ | |
| 				list:     []CompactNeedleValue{}, | |
| 				chunk:    0, | |
| 				firstKey: MaxCompactKey, | |
| 				lastKey:  0, | |
| 			}, | |
| 			1: &CompactMapSegment{ | |
| 				list:     []CompactNeedleValue{}, | |
| 				chunk:    1, | |
| 				firstKey: MaxCompactKey, | |
| 				lastKey:  0, | |
| 			}, | |
| 			5: &CompactMapSegment{ | |
| 				list:     []CompactNeedleValue{}, | |
| 				chunk:    5, | |
| 				firstKey: MaxCompactKey, | |
| 				lastKey:  0, | |
| 			}, | |
| 		}, | |
| 	} | |
| 	if !reflect.DeepEqual(testMap, wantMap) { | |
| 		t.Errorf("got map %v, want %v", testMap, wantMap) | |
| 	} | |
| } | |
| 
 | |
| func TestAscendingVisit(t *testing.T) { | |
| 	cm := NewCompactMap() | |
| 	for _, nid := range []types.NeedleId{20, 7, 40000, 300000, 0, 100, 500, 10000, 200000} { | |
| 		cm.Set(nid, types.Uint32ToOffset(123), 456) | |
| 	} | |
| 
 | |
| 	got := []NeedleValue{} | |
| 	err := cm.AscendingVisit(func(nv NeedleValue) error { | |
| 		got = append(got, nv) | |
| 		return nil | |
| 	}) | |
| 	if err != nil { | |
| 		t.Errorf("got error %v, expected none", err) | |
| 	} | |
| 
 | |
| 	want := []NeedleValue{ | |
| 		NeedleValue{Key: 0, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 7, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 20, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 100, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 500, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 10000, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 40000, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 200000, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 		NeedleValue{Key: 300000, Offset: types.Uint32ToOffset(123), Size: 456}, | |
| 	} | |
| 	if !reflect.DeepEqual(got, want) { | |
| 		t.Errorf("got values %v, want %v", got, want) | |
| 	} | |
| } | |
| 
 | |
| func TestRandomInsert(t *testing.T) { | |
| 	count := 8 * SegmentChunkSize | |
| 	keys := []types.NeedleId{} | |
| 	for i := 0; i < count; i++ { | |
| 		keys = append(keys, types.NeedleId(i)) | |
| 	} | |
| 
 | |
| 	r := rand.New(rand.NewSource(123456789)) | |
| 	r.Shuffle(len(keys), func(i, j int) { keys[i], keys[j] = keys[j], keys[i] }) | |
| 
 | |
| 	cm := NewCompactMap() | |
| 	for _, k := range keys { | |
| 		_, _ = cm.Set(k, types.Uint32ToOffset(123), 456) | |
| 	} | |
| 	if got, want := cm.Len(), count; got != want { | |
| 		t.Errorf("expected size %d, got %d", want, got) | |
| 	} | |
| 
 | |
| 	last := -1 | |
| 	err := cm.AscendingVisit(func(nv NeedleValue) error { | |
| 		key := int(nv.Key) | |
| 		if key <= last { | |
| 			return fmt.Errorf("found out of order entries (%d vs %d)", key, last) | |
| 		} | |
| 		last = key | |
| 		return nil | |
| 	}) | |
| 	if err != nil { | |
| 		t.Errorf("got error %v, expected none", err) | |
| 	} | |
| 
 | |
| 	// Given that we've written a integer multiple of SegmentChunkSize, all | |
| 	// segments should be fully utilized and capacity-adjusted. | |
| 	if l, c := cm.Len(), cm.Cap(); l != c { | |
| 		t.Errorf("map length (%d) doesn't match capacity (%d)", l, c) | |
| 	} | |
| }
 |