github.com/prysmaticlabs/prysm@v1.4.4/shared/aggregation/maxcover_test.go (about)

     1  package aggregation
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/prysmaticlabs/go-bitfield"
     9  	aggtesting "github.com/prysmaticlabs/prysm/shared/aggregation/testing"
    10  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    11  )
    12  
    13  func TestMaxCover_MaxCoverCandidates_filter(t *testing.T) {
    14  	type args struct {
    15  		covered       bitfield.Bitlist
    16  		allowOverlaps bool
    17  	}
    18  	var problem MaxCoverCandidates
    19  	tests := []struct {
    20  		name string
    21  		cl   MaxCoverCandidates
    22  		args args
    23  		want *MaxCoverCandidates
    24  	}{
    25  		{
    26  			name: "nil list",
    27  			cl:   nil,
    28  			args: args{},
    29  			want: &problem,
    30  		},
    31  		{
    32  			name: "empty list",
    33  			cl:   MaxCoverCandidates{},
    34  			args: args{},
    35  			want: &MaxCoverCandidates{},
    36  		},
    37  		{
    38  			name: "all processed",
    39  			cl: MaxCoverCandidates{
    40  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
    41  				{2, &bitfield.Bitlist{0b01000010, 0b1}, 2, true},
    42  				{3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
    43  				{4, &bitfield.Bitlist{0b01000010, 0b1}, 2, true},
    44  				{4, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
    45  			},
    46  			args: args{},
    47  			want: &MaxCoverCandidates{},
    48  		},
    49  		{
    50  			name: "partially processed",
    51  			cl: MaxCoverCandidates{
    52  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
    53  				{2, &bitfield.Bitlist{0b01000010, 0b1}, 2, false},
    54  				{3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
    55  				{4, &bitfield.Bitlist{0b01000010, 0b1}, 2, false},
    56  				{4, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
    57  			},
    58  			args: args{
    59  				covered: bitfield.NewBitlist(8),
    60  			},
    61  			want: &MaxCoverCandidates{
    62  				{2, &bitfield.Bitlist{0b01000010, 0b1}, 2, false},
    63  				{4, &bitfield.Bitlist{0b01000010, 0b1}, 2, false},
    64  			},
    65  		},
    66  		{
    67  			name: "all overlapping",
    68  			cl: MaxCoverCandidates{
    69  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
    70  				{2, &bitfield.Bitlist{0b01000010, 0b1}, 2, false},
    71  				{3, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
    72  				{4, &bitfield.Bitlist{0b01000010, 0b1}, 2, false},
    73  				{4, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
    74  			},
    75  			args: args{
    76  				covered: aggtesting.BitlistWithAllBitsSet(8),
    77  			},
    78  			want: &MaxCoverCandidates{},
    79  		},
    80  		{
    81  			name: "partially overlapping",
    82  			cl: MaxCoverCandidates{
    83  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
    84  				{2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false},
    85  				{3, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
    86  				{4, &bitfield.Bitlist{0b01000011, 0b1}, 2, false},
    87  				{4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false},
    88  			},
    89  			args: args{
    90  				covered: bitfield.Bitlist{0b10000001, 0b1},
    91  			},
    92  			want: &MaxCoverCandidates{
    93  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
    94  				{3, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
    95  			},
    96  		},
    97  		{
    98  			name: "overlapping and processed and pending",
    99  			cl: MaxCoverCandidates{
   100  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
   101  				{2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false},
   102  				{3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
   103  				{4, &bitfield.Bitlist{0b01000011, 0b1}, 2, false},
   104  				{4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false},
   105  			},
   106  			args: args{
   107  				covered: bitfield.Bitlist{0b10000001, 0b1},
   108  			},
   109  			want: &MaxCoverCandidates{
   110  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
   111  			},
   112  		},
   113  		{
   114  			name: "overlapping and processed and pending - allow overlaps",
   115  			cl: MaxCoverCandidates{
   116  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
   117  				{2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false},
   118  				{3, &bitfield.Bitlist{0b00001010, 0b1}, 2, true},
   119  				{4, &bitfield.Bitlist{0b01000011, 0b1}, 0, false},
   120  				{4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false},
   121  			},
   122  			args: args{
   123  				covered:       bitfield.Bitlist{0b11111111, 0b1},
   124  				allowOverlaps: true,
   125  			},
   126  			want: &MaxCoverCandidates{
   127  				{0, &bitfield.Bitlist{0b00001010, 0b1}, 2, false},
   128  				{2, &bitfield.Bitlist{0b11000010, 0b1}, 2, false},
   129  				{4, &bitfield.Bitlist{0b10001010, 0b1}, 2, false},
   130  			},
   131  		},
   132  	}
   133  	for _, tt := range tests {
   134  		t.Run(tt.name, func(t *testing.T) {
   135  			got, err := tt.cl.filter(tt.args.covered, tt.args.allowOverlaps)
   136  			if err != nil {
   137  				t.Error(err)
   138  			}
   139  			sort.Slice(*got, func(i, j int) bool {
   140  				return (*got)[i].key < (*got)[j].key
   141  			})
   142  			sort.Slice(*tt.want, func(i, j int) bool {
   143  				return (*tt.want)[i].key < (*tt.want)[j].key
   144  			})
   145  			assert.DeepEqual(t, tt.want, got)
   146  		})
   147  	}
   148  }
   149  
   150  func TestMaxCover_MaxCoverCandidates_sort(t *testing.T) {
   151  	var problem MaxCoverCandidates
   152  	tests := []struct {
   153  		name string
   154  		cl   MaxCoverCandidates
   155  		want *MaxCoverCandidates
   156  	}{
   157  		{
   158  			name: "nil list",
   159  			cl:   nil,
   160  			want: &problem,
   161  		},
   162  		{
   163  			name: "empty list",
   164  			cl:   MaxCoverCandidates{},
   165  			want: &MaxCoverCandidates{},
   166  		},
   167  		{
   168  			name: "single item",
   169  			cl: MaxCoverCandidates{
   170  				{0, &bitfield.Bitlist{}, 5, false},
   171  			},
   172  			want: &MaxCoverCandidates{
   173  				{0, &bitfield.Bitlist{}, 5, false},
   174  			},
   175  		},
   176  		{
   177  			name: "already sorted",
   178  			cl: MaxCoverCandidates{
   179  				{5, &bitfield.Bitlist{}, 5, false},
   180  				{3, &bitfield.Bitlist{}, 4, false},
   181  				{4, &bitfield.Bitlist{}, 4, false},
   182  				{2, &bitfield.Bitlist{}, 2, false},
   183  				{1, &bitfield.Bitlist{}, 1, false},
   184  			},
   185  			want: &MaxCoverCandidates{
   186  				{5, &bitfield.Bitlist{}, 5, false},
   187  				{3, &bitfield.Bitlist{}, 4, false},
   188  				{4, &bitfield.Bitlist{}, 4, false},
   189  				{2, &bitfield.Bitlist{}, 2, false},
   190  				{1, &bitfield.Bitlist{}, 1, false},
   191  			},
   192  		},
   193  		{
   194  			name: "all equal",
   195  			cl: MaxCoverCandidates{
   196  				{0, &bitfield.Bitlist{}, 5, false},
   197  				{0, &bitfield.Bitlist{}, 5, false},
   198  				{0, &bitfield.Bitlist{}, 5, false},
   199  				{0, &bitfield.Bitlist{}, 5, false},
   200  				{0, &bitfield.Bitlist{}, 5, false},
   201  			},
   202  			want: &MaxCoverCandidates{
   203  				{0, &bitfield.Bitlist{}, 5, false},
   204  				{0, &bitfield.Bitlist{}, 5, false},
   205  				{0, &bitfield.Bitlist{}, 5, false},
   206  				{0, &bitfield.Bitlist{}, 5, false},
   207  				{0, &bitfield.Bitlist{}, 5, false},
   208  			},
   209  		},
   210  		{
   211  			name: "unsorted",
   212  			cl: MaxCoverCandidates{
   213  				{2, &bitfield.Bitlist{}, 2, false},
   214  				{4, &bitfield.Bitlist{}, 4, false},
   215  				{3, &bitfield.Bitlist{}, 4, false},
   216  				{5, &bitfield.Bitlist{}, 5, false},
   217  				{1, &bitfield.Bitlist{}, 1, false},
   218  			},
   219  			want: &MaxCoverCandidates{
   220  				{5, &bitfield.Bitlist{}, 5, false},
   221  				{3, &bitfield.Bitlist{}, 4, false},
   222  				{4, &bitfield.Bitlist{}, 4, false},
   223  				{2, &bitfield.Bitlist{}, 2, false},
   224  				{1, &bitfield.Bitlist{}, 1, false},
   225  			},
   226  		},
   227  	}
   228  	for _, tt := range tests {
   229  		t.Run(tt.name, func(t *testing.T) {
   230  			if got := tt.cl.sort(); !reflect.DeepEqual(got, tt.want) {
   231  				t.Errorf("sort() = %v, want %v", got, tt.want)
   232  			}
   233  		})
   234  	}
   235  }
   236  
   237  func TestMaxCover_MaxCoverCandidates_union(t *testing.T) {
   238  	tests := []struct {
   239  		name string
   240  		cl   MaxCoverCandidates
   241  		want bitfield.Bitlist
   242  	}{
   243  		{
   244  			name: "nil",
   245  			cl:   nil,
   246  			want: bitfield.Bitlist(nil),
   247  		},
   248  		{
   249  			name: "single empty candidate",
   250  			cl: MaxCoverCandidates{
   251  				{0, &bitfield.Bitlist{0b00000000, 0b1}, 0, false},
   252  			},
   253  			want: bitfield.Bitlist{0b00000000, 0b1},
   254  		},
   255  		{
   256  			name: "single full candidate",
   257  			cl: MaxCoverCandidates{
   258  				{0, &bitfield.Bitlist{0b11111111, 0b1}, 8, false},
   259  			},
   260  			want: aggtesting.BitlistWithAllBitsSet(8),
   261  		},
   262  		{
   263  			name: "mixed",
   264  			cl: MaxCoverCandidates{
   265  				{1, &bitfield.Bitlist{0b00000000, 0b00001110, 0b00001110, 0b1}, 6, false},
   266  				{2, &bitfield.Bitlist{0b00000000, 0b01110000, 0b01110000, 0b1}, 6, false},
   267  				{3, &bitfield.Bitlist{0b00000111, 0b10000001, 0b10000000, 0b1}, 6, false},
   268  				{4, &bitfield.Bitlist{0b00000000, 0b00000110, 0b00000110, 0b1}, 4, false},
   269  				{5, &bitfield.Bitlist{0b10000000, 0b00000001, 0b01100010, 0b1}, 4, false},
   270  				{6, &bitfield.Bitlist{0b00001000, 0b00001000, 0b10000010, 0b1}, 4, false},
   271  				{7, &bitfield.Bitlist{0b00000000, 0b00000001, 0b11111110, 0b1}, 8, false},
   272  			},
   273  			want: bitfield.Bitlist{0b10001111, 0b11111111, 0b11111110, 0b1},
   274  		},
   275  	}
   276  	for _, tt := range tests {
   277  		t.Run(tt.name, func(t *testing.T) {
   278  			if got, err := tt.cl.union(); !reflect.DeepEqual(got, tt.want) || err != nil {
   279  				t.Errorf("union(), got: %#b, %v, want: %#b", got, err, tt.want)
   280  			}
   281  		})
   282  	}
   283  }
   284  
   285  func TestMaxCover_MaxCoverCandidates_score(t *testing.T) {
   286  	var problem MaxCoverCandidates
   287  	tests := []struct {
   288  		name      string
   289  		cl        MaxCoverCandidates
   290  		uncovered bitfield.Bitlist
   291  		want      *MaxCoverCandidates
   292  	}{
   293  		{
   294  			name: "nil",
   295  			cl:   nil,
   296  			want: &problem,
   297  		},
   298  		{
   299  			name: "uncovered set is empty",
   300  			cl: MaxCoverCandidates{
   301  				{0, &bitfield.Bitlist{0b00000100, 0b1}, 1, false},
   302  				{1, &bitfield.Bitlist{0b00011011, 0b1}, 4, false},
   303  				{2, &bitfield.Bitlist{0b00011011, 0b1}, 4, false},
   304  				{3, &bitfield.Bitlist{0b00000001, 0b1}, 1, false},
   305  				{4, &bitfield.Bitlist{0b00011010, 0b1}, 3, false},
   306  			},
   307  			uncovered: bitfield.NewBitlist(8),
   308  			want: &MaxCoverCandidates{
   309  				{0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false},
   310  				{1, &bitfield.Bitlist{0b00011011, 0b1}, 0, false},
   311  				{2, &bitfield.Bitlist{0b00011011, 0b1}, 0, false},
   312  				{3, &bitfield.Bitlist{0b00000001, 0b1}, 0, false},
   313  				{4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false},
   314  			},
   315  		},
   316  		{
   317  			name: "completely uncovered",
   318  			cl: MaxCoverCandidates{
   319  				{0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false},
   320  				{1, &bitfield.Bitlist{0b00011011, 0b1}, 0, false},
   321  				{2, &bitfield.Bitlist{0b00011011, 0b1}, 0, false},
   322  				{3, &bitfield.Bitlist{0b00000001, 0b1}, 0, false},
   323  				{4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false},
   324  			},
   325  			uncovered: aggtesting.BitlistWithAllBitsSet(8),
   326  			want: &MaxCoverCandidates{
   327  				{0, &bitfield.Bitlist{0b00000100, 0b1}, 1, false},
   328  				{1, &bitfield.Bitlist{0b00011011, 0b1}, 4, false},
   329  				{2, &bitfield.Bitlist{0b00011011, 0b1}, 4, false},
   330  				{3, &bitfield.Bitlist{0b00000001, 0b1}, 1, false},
   331  				{4, &bitfield.Bitlist{0b00011010, 0b1}, 3, false},
   332  			},
   333  		},
   334  		{
   335  			name: "partial uncovered set",
   336  			cl: MaxCoverCandidates{
   337  				{0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false},
   338  				{1, &bitfield.Bitlist{0b00011011, 0b1}, 1, false},
   339  				{2, &bitfield.Bitlist{0b10011011, 0b1}, 0, false},
   340  				{3, &bitfield.Bitlist{0b11111111, 0b1}, 1, false},
   341  				{4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false},
   342  			},
   343  			uncovered: bitfield.Bitlist{0b11010010, 0b1},
   344  			want: &MaxCoverCandidates{
   345  				{0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false},
   346  				{1, &bitfield.Bitlist{0b00011011, 0b1}, 2, false},
   347  				{2, &bitfield.Bitlist{0b10011011, 0b1}, 3, false},
   348  				{3, &bitfield.Bitlist{0b11111111, 0b1}, 4, false},
   349  				{4, &bitfield.Bitlist{0b00011010, 0b1}, 2, false},
   350  			},
   351  		},
   352  	}
   353  	for _, tt := range tests {
   354  		t.Run(tt.name, func(t *testing.T) {
   355  			if got, err := tt.cl.score(tt.uncovered); !reflect.DeepEqual(got, tt.want) || err != nil {
   356  				t.Errorf("score() = %v, %v, want %v", got, err, tt.want)
   357  			}
   358  		})
   359  	}
   360  }
   361  
   362  func TestMaxCover_MaxCoverProblem_Cover(t *testing.T) {
   363  	problemSet := func() MaxCoverCandidates {
   364  		// test vectors originally from:
   365  		// https://github.com/sigp/lighthouse/blob/master/beacon_node/operation_pool/src/max_cover.rs
   366  		return MaxCoverCandidates{
   367  			{0, &bitfield.Bitlist{0b00000100, 0b1}, 0, false},
   368  			{1, &bitfield.Bitlist{0b00011011, 0b1}, 0, false},
   369  			{2, &bitfield.Bitlist{0b00011011, 0b1}, 0, false},
   370  			{3, &bitfield.Bitlist{0b00000001, 0b1}, 0, false},
   371  			{4, &bitfield.Bitlist{0b00011010, 0b1}, 0, false},
   372  		}
   373  	}
   374  	type args struct {
   375  		k             int
   376  		candidates    MaxCoverCandidates
   377  		allowOverlaps bool
   378  	}
   379  	tests := []struct {
   380  		name      string
   381  		args      args
   382  		want      *Aggregation
   383  		wantedErr string
   384  	}{
   385  		{
   386  			name:      "nil problem",
   387  			args:      args{},
   388  			wantedErr: ErrInvalidMaxCoverProblem.Error(),
   389  		},
   390  		{
   391  			name: "different bitlengths",
   392  			args: args{k: 3, candidates: MaxCoverCandidates{
   393  				{0, &bitfield.Bitlist{0b00000000, 0b00011111, 0xf1}, 0, false},
   394  				{2, &bitfield.Bitlist{0b00000001, 0b11100000, 0b1}, 0, false},
   395  				{3, &bitfield.Bitlist{0b00000110, 0b00000000, 0b1}, 0, false},
   396  			}},
   397  			want: &Aggregation{
   398  				Coverage: bitfield.Bitlist{0b00000000, 0b00011111, 0xf1},
   399  				Keys:     []int{0},
   400  			},
   401  		},
   402  		{
   403  			name: "k=0",
   404  			args: args{k: 0, candidates: problemSet()},
   405  			want: &Aggregation{
   406  				Coverage: bitfield.Bitlist{0b0000000, 0b1},
   407  				Keys:     []int{},
   408  			},
   409  		},
   410  		{
   411  			name: "k=1",
   412  			args: args{k: 1, candidates: problemSet()},
   413  			want: &Aggregation{
   414  				Coverage: bitfield.Bitlist{0b0011011, 0b1},
   415  				Keys:     []int{1},
   416  			},
   417  		},
   418  		{
   419  			name: "k=2",
   420  			args: args{k: 2, candidates: problemSet()},
   421  			want: &Aggregation{
   422  				Coverage: bitfield.Bitlist{0b0011111, 0b1},
   423  				Keys:     []int{1, 0},
   424  			},
   425  		},
   426  		{
   427  			name: "k=3",
   428  			args: args{k: 3, candidates: problemSet()},
   429  			want: &Aggregation{
   430  				Coverage: bitfield.Bitlist{0b0011111, 0b1},
   431  				Keys:     []int{1, 0},
   432  			},
   433  		},
   434  		{
   435  			name: "k=5",
   436  			args: args{k: 5, candidates: problemSet()},
   437  			want: &Aggregation{
   438  				Coverage: bitfield.Bitlist{0b0011111, 0b1},
   439  				Keys:     []int{1, 0},
   440  			},
   441  		},
   442  		{
   443  			name: "k=50",
   444  			args: args{k: 50, candidates: problemSet()},
   445  			want: &Aggregation{
   446  				Coverage: bitfield.Bitlist{0b0011111, 0b1},
   447  				Keys:     []int{1, 0},
   448  			},
   449  		},
   450  		{
   451  			name: "suboptimal", // Greedy algorithm selects: 0, 2, 3, while 1,4,5 is optimal.
   452  			args: args{k: 3, candidates: MaxCoverCandidates{
   453  				{0, &bitfield.Bitlist{0b00000000, 0b00011111, 0b1}, 0, false},
   454  				{2, &bitfield.Bitlist{0b00000001, 0b11100000, 0b1}, 0, false},
   455  				{3, &bitfield.Bitlist{0b00000110, 0b00000000, 0b1}, 0, false},
   456  				{1, &bitfield.Bitlist{0b00110000, 0b01110000, 0b1}, 0, false},
   457  				{4, &bitfield.Bitlist{0b00000110, 0b10001100, 0b1}, 0, false},
   458  				{5, &bitfield.Bitlist{0b01001001, 0b00000011, 0b1}, 0, false},
   459  			}},
   460  			want: &Aggregation{
   461  				Coverage: bitfield.Bitlist{0b00000111, 0b11111111, 0b1},
   462  				Keys:     []int{0, 2, 3},
   463  			},
   464  		},
   465  		{
   466  			name: "allow overlaps",
   467  			args: args{k: 5, allowOverlaps: true, candidates: MaxCoverCandidates{
   468  				{0, &bitfield.Bitlist{0b00000000, 0b00000001, 0b11111110, 0b1}, 0, false},
   469  				{1, &bitfield.Bitlist{0b00000000, 0b00001110, 0b00001110, 0b1}, 0, false},
   470  				{2, &bitfield.Bitlist{0b00000000, 0b01110000, 0b01110000, 0b1}, 0, false},
   471  				{3, &bitfield.Bitlist{0b00000111, 0b10000001, 0b10000000, 0b1}, 0, false},
   472  				{4, &bitfield.Bitlist{0b00000000, 0b00000110, 0b00000110, 0b1}, 0, false},
   473  				{5, &bitfield.Bitlist{0b00000000, 0b00000001, 0b01100010, 0b1}, 0, false},
   474  				{6, &bitfield.Bitlist{0b00001000, 0b00001000, 0b10000010, 0b1}, 0, false},
   475  			}},
   476  			want: &Aggregation{
   477  				Coverage: bitfield.Bitlist{0b00001111, 0xff, 0b11111110, 0b1},
   478  				Keys:     []int{0, 3, 1, 2, 6},
   479  			},
   480  		},
   481  		{
   482  			name: "empty bitlists",
   483  			args: args{k: 5, allowOverlaps: true, candidates: MaxCoverCandidates{
   484  				{1, &bitfield.Bitlist{0b0}, 0, false},
   485  				{0, &bitfield.Bitlist{0b00000000, 0b00000000, 0b00000000, 0b1}, 0, false},
   486  				{2, &bitfield.Bitlist{0b00000000, 0b00000000, 0b00000000, 0b1}, 0, false},
   487  			}},
   488  			wantedErr: "empty bitlists: invalid max_cover problem",
   489  		},
   490  		{
   491  			name: "overlapping solution dropped",
   492  			args: args{k: 5, allowOverlaps: false, candidates: MaxCoverCandidates{
   493  				{0, &bitfield.Bitlist{0b11111111, 0b11000111, 0b11111111, 0b1}, 0, false},
   494  				// All remaining bitlists will overlap, so will be dropped.
   495  				{1, &bitfield.Bitlist{0b11111111, 0b00001100, 0b11111111, 0b1}, 0, false},
   496  				{2, &bitfield.Bitlist{0b00000000, 0b01110000, 0b01110000, 0b1}, 0, false},
   497  				{3, &bitfield.Bitlist{0b00000111, 0b10000001, 0b10000000, 0b1}, 0, false},
   498  				{4, &bitfield.Bitlist{0b00000000, 0b00000110, 0b00000110, 0b1}, 0, false},
   499  				{5, &bitfield.Bitlist{0b00000000, 0b00000001, 0b01100010, 0b1}, 0, false},
   500  				{6, &bitfield.Bitlist{0b00001000, 0b00001000, 0b10000010, 0b1}, 0, false},
   501  			}},
   502  			want: &Aggregation{
   503  				Coverage: bitfield.Bitlist{0xff, 0b11000111, 0xff, 0b1},
   504  				Keys:     []int{0},
   505  			},
   506  		},
   507  	}
   508  	for _, tt := range tests {
   509  		t.Run(tt.name, func(t *testing.T) {
   510  			mc := &MaxCoverProblem{
   511  				Candidates: tt.args.candidates,
   512  			}
   513  			got, err := mc.Cover(tt.args.k, tt.args.allowOverlaps)
   514  			if tt.wantedErr != "" {
   515  				assert.ErrorContains(t, tt.wantedErr, err)
   516  			} else {
   517  				assert.NoError(t, err)
   518  				assert.DeepEqual(t, tt.want, got)
   519  			}
   520  		})
   521  	}
   522  }
   523  
   524  func TestMaxCover_MaxCover(t *testing.T) {
   525  	problemSet := func() []*bitfield.Bitlist64 {
   526  		return []*bitfield.Bitlist64{
   527  			bitfield.NewBitlist64From([]uint64{0b00000100}),
   528  			bitfield.NewBitlist64From([]uint64{0b00011011}),
   529  			bitfield.NewBitlist64From([]uint64{0b00011011}),
   530  			bitfield.NewBitlist64From([]uint64{0b00000001}),
   531  			bitfield.NewBitlist64From([]uint64{0b00011010}),
   532  		}
   533  	}
   534  	type args struct {
   535  		k             int
   536  		candidates    []*bitfield.Bitlist64
   537  		allowOverlaps bool
   538  	}
   539  	type BitSetAggregation struct {
   540  		Coverage *bitfield.Bitlist64
   541  		Keys     []int
   542  	}
   543  	tests := []struct {
   544  		name      string
   545  		args      args
   546  		want      *BitSetAggregation
   547  		wantedErr string
   548  	}{
   549  		{
   550  			name:      "nil problem",
   551  			args:      args{},
   552  			wantedErr: ErrInvalidMaxCoverProblem.Error(),
   553  		},
   554  		{
   555  			name: "different bitlengths (pick first, combine with third)",
   556  			args: args{k: 3, candidates: []*bitfield.Bitlist64{
   557  				bitfield.NewBitlist64From([]uint64{0b00000001, 0b11100000, 0b10000000}),
   558  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}),
   559  				bitfield.NewBitlist64From([]uint64{0b00000110, 0b00000000, 0b01000000}),
   560  			}},
   561  			want: &BitSetAggregation{
   562  				Coverage: bitfield.NewBitlist64From([]uint64{0b00000111, 0b11100000, 0b11000000}),
   563  				Keys:     []int{0, 2},
   564  			},
   565  		},
   566  		{
   567  			name: "different bitlengths (pick first, no other combination)",
   568  			args: args{k: 3, candidates: []*bitfield.Bitlist64{
   569  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}),
   570  				bitfield.NewBitlist64From([]uint64{0b00000001, 0b11100000, 0b1}),
   571  				bitfield.NewBitlist64From([]uint64{0b00000110, 0b00000000, 0b1}),
   572  			}},
   573  			want: &BitSetAggregation{
   574  				Coverage: bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}),
   575  				Keys:     []int{0},
   576  			},
   577  		},
   578  		{
   579  			name: "k=0",
   580  			args: args{k: 0, candidates: problemSet()},
   581  			want: &BitSetAggregation{
   582  				Coverage: bitfield.NewBitlist64From([]uint64{0b0}),
   583  				Keys:     []int{},
   584  			},
   585  		},
   586  		{
   587  			name: "k=1",
   588  			args: args{k: 1, candidates: problemSet()},
   589  			want: &BitSetAggregation{
   590  				Coverage: bitfield.NewBitlist64From([]uint64{0b0011011}),
   591  				Keys:     []int{1},
   592  			},
   593  		},
   594  		{
   595  			name: "k=2",
   596  			args: args{k: 2, candidates: problemSet()},
   597  			want: &BitSetAggregation{
   598  				Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}),
   599  				Keys:     []int{0, 1},
   600  			},
   601  		},
   602  		{
   603  			name: "k=3",
   604  			args: args{k: 3, candidates: problemSet()},
   605  			want: &BitSetAggregation{
   606  				Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}),
   607  				Keys:     []int{0, 1},
   608  			},
   609  		},
   610  		{
   611  			name: "k=5",
   612  			args: args{k: 5, candidates: problemSet()},
   613  			want: &BitSetAggregation{
   614  				Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}),
   615  				Keys:     []int{0, 1},
   616  			},
   617  		},
   618  		{
   619  			name: "k=50",
   620  			args: args{k: 50, candidates: problemSet()},
   621  			want: &BitSetAggregation{
   622  				Coverage: bitfield.NewBitlist64From([]uint64{0b0011111}),
   623  				Keys:     []int{0, 1},
   624  			},
   625  		},
   626  		{
   627  			name: "suboptimal", // Greedy algorithm selects: 0, 2, 3, while 1,4,5 is optimal.
   628  			args: args{k: 3, candidates: []*bitfield.Bitlist64{
   629  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00011111}),
   630  				bitfield.NewBitlist64From([]uint64{0b00000001, 0b11100000}),
   631  				bitfield.NewBitlist64From([]uint64{0b00000110, 0b00000000}),
   632  				bitfield.NewBitlist64From([]uint64{0b00110000, 0b01110000}),
   633  				bitfield.NewBitlist64From([]uint64{0b00000110, 0b10001100}),
   634  				bitfield.NewBitlist64From([]uint64{0b01001001, 0b00000011}),
   635  			}},
   636  			want: &BitSetAggregation{
   637  				Coverage: bitfield.NewBitlist64From([]uint64{0b00000111, 0b11111111}),
   638  				Keys:     []int{0, 1, 2},
   639  			},
   640  		},
   641  		{
   642  			name: "allow overlaps",
   643  			args: args{k: 5, allowOverlaps: true, candidates: []*bitfield.Bitlist64{
   644  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00000001, 0b11111110}),
   645  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00001110, 0b00001110}),
   646  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b01110000, 0b01110000}),
   647  				bitfield.NewBitlist64From([]uint64{0b00000111, 0b10000001, 0b10000000}),
   648  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00000110, 0b00000110}),
   649  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00000001, 0b01100010}),
   650  				bitfield.NewBitlist64From([]uint64{0b00001000, 0b00001000, 0b10000010}),
   651  			}},
   652  			want: &BitSetAggregation{
   653  				Coverage: bitfield.NewBitlist64From([]uint64{0b00001111, 0xff, 0b11111110}),
   654  				Keys:     []int{0, 1, 2, 3, 6},
   655  			},
   656  		},
   657  		{
   658  			name: "empty bitlists",
   659  			args: args{k: 5, allowOverlaps: true, candidates: []*bitfield.Bitlist64{
   660  				bitfield.NewBitlist64From([]uint64{}),
   661  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b00001110, 0b00001110}),
   662  				bitfield.NewBitlist64From([]uint64{0b00000000, 0b01110000, 0b01110000}),
   663  			}},
   664  			wantedErr: "empty bitlists: invalid max_cover problem",
   665  		},
   666  	}
   667  	for _, tt := range tests {
   668  		t.Run(tt.name, func(t *testing.T) {
   669  			selectedCandidates, coverage, err := MaxCover(tt.args.candidates, tt.args.k, tt.args.allowOverlaps)
   670  			if tt.wantedErr != "" {
   671  				assert.ErrorContains(t, tt.wantedErr, err)
   672  			} else {
   673  				assert.NoError(t, err)
   674  				assert.DeepEqual(t, tt.want.Coverage, coverage)
   675  				selectedKeys := make([]int, selectedCandidates.Count())
   676  				selectedCandidates.NoAllocBitIndices(selectedKeys)
   677  				assert.DeepEqual(t, tt.want.Keys, selectedKeys)
   678  			}
   679  		})
   680  	}
   681  }