git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/netmap/selector_test.go (about)

     1  package netmap
     2  
     3  import (
     4  	"cmp"
     5  	"crypto/rand"
     6  	"encoding/binary"
     7  	"fmt"
     8  	mrand "math/rand"
     9  	"reflect"
    10  	"slices"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  
    15  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
    16  	"git.frostfs.info/TrueCloudLab/hrw"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func BenchmarkHRWSort(b *testing.B) {
    22  	const netmapSize = 1000
    23  
    24  	vectors := make([]nodes, netmapSize)
    25  	weights := make([]float64, netmapSize)
    26  	for i := range vectors {
    27  		key := make([]byte, 33)
    28  		rand.Read(key)
    29  
    30  		var node NodeInfo
    31  		node.SetPrice(1)
    32  		node.SetCapacity(100)
    33  		node.SetPublicKey(key)
    34  
    35  		vectors[i] = nodes{node}
    36  		weights[i] = float64(mrand.Uint32()%10) / 10.0
    37  	}
    38  
    39  	pivot := mrand.Uint64()
    40  	b.Run("sort by index, no weight", func(b *testing.B) {
    41  		realNodes := make([]nodes, netmapSize)
    42  		b.ResetTimer()
    43  		for range b.N {
    44  			b.StopTimer()
    45  			copy(realNodes, vectors)
    46  			b.StartTimer()
    47  
    48  			hrw.SortSliceByIndex(realNodes, pivot)
    49  		}
    50  	})
    51  	b.Run("sort by value, no weight", func(b *testing.B) {
    52  		realNodes := make([]nodes, netmapSize)
    53  		b.ResetTimer()
    54  		for range b.N {
    55  			b.StopTimer()
    56  			copy(realNodes, vectors)
    57  			b.StartTimer()
    58  
    59  			hrw.SortHasherSliceByValue(realNodes, pivot)
    60  		}
    61  	})
    62  	b.Run("only sort by index", func(b *testing.B) {
    63  		realNodes := make([]nodes, netmapSize)
    64  		b.ResetTimer()
    65  		for range b.N {
    66  			b.StopTimer()
    67  			copy(realNodes, vectors)
    68  			b.StartTimer()
    69  
    70  			hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
    71  		}
    72  	})
    73  	b.Run("sort by value", func(b *testing.B) {
    74  		realNodes := make([]nodes, netmapSize)
    75  		b.ResetTimer()
    76  		for range b.N {
    77  			b.StopTimer()
    78  			copy(realNodes, vectors)
    79  			b.StartTimer()
    80  
    81  			hrw.SortHasherSliceByWeightValue(realNodes, weights, pivot)
    82  		}
    83  	})
    84  	b.Run("sort by ID, then by index (deterministic)", func(b *testing.B) {
    85  		realNodes := make([]nodes, netmapSize)
    86  		b.ResetTimer()
    87  		for range b.N {
    88  			b.StopTimer()
    89  			copy(realNodes, vectors)
    90  			b.StartTimer()
    91  
    92  			slices.SortFunc(vectors, func(vi, vj nodes) int {
    93  				return cmp.Compare(vi[0].Hash(), vj[0].Hash())
    94  			})
    95  			hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
    96  		}
    97  	})
    98  }
    99  
   100  func BenchmarkPolicyHRWType(b *testing.B) {
   101  	const netmapSize = 100
   102  
   103  	p := newPlacementPolicy(1,
   104  		[]ReplicaDescriptor{
   105  			newReplica(1, "loc1"),
   106  			newReplica(1, "loc2"),
   107  		},
   108  		[]Selector{
   109  			newSelector("loc1", "Location", 1, "loc1", (*Selector).SelectSame),
   110  			newSelector("loc2", "Location", 1, "loc2", (*Selector).SelectSame),
   111  		},
   112  		[]Filter{
   113  			newFilter("loc1", "Location", "Shanghai", netmap.EQ),
   114  			newFilter("loc2", "Location", "Shanghai", netmap.NE),
   115  		})
   116  
   117  	nodes := make([]NodeInfo, netmapSize)
   118  	for i := range nodes {
   119  		var loc string
   120  		switch i % 20 {
   121  		case 0:
   122  			loc = "Shanghai"
   123  		default:
   124  			loc = strconv.Itoa(i % 20)
   125  		}
   126  
   127  		// Having the same price and capacity ensures equal weights for all nodes.
   128  		// This way placement is more dependent on the initial order.
   129  		nodes[i] = nodeInfoFromAttributes("Location", loc, "Price", "1", "Capacity", "10")
   130  		pub := make([]byte, 33)
   131  		pub[0] = byte(i)
   132  		nodes[i].SetPublicKey(pub)
   133  	}
   134  
   135  	var nm NetMap
   136  	nm.SetNodes(nodes)
   137  
   138  	b.ResetTimer()
   139  	for range b.N {
   140  		_, err := nm.ContainerNodes(p, []byte{1})
   141  		if err != nil {
   142  			b.Fatal()
   143  		}
   144  	}
   145  }
   146  
   147  func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
   148  	const netmapSize = 100
   149  
   150  	p := newPlacementPolicy(1,
   151  		[]ReplicaDescriptor{
   152  			newReplica(1, "loc1"),
   153  			newReplica(1, "loc2"),
   154  		},
   155  		[]Selector{
   156  			newSelector("loc1", "Location", 1, "loc1", (*Selector).SelectSame),
   157  			newSelector("loc2", "Location", 1, "loc2", (*Selector).SelectSame),
   158  		},
   159  		[]Filter{
   160  			newFilter("loc1", "Location", "Shanghai", netmap.EQ),
   161  			newFilter("loc2", "Location", "Shanghai", netmap.NE),
   162  		})
   163  
   164  	nodeList := make([]NodeInfo, netmapSize)
   165  	for i := range nodeList {
   166  		var loc string
   167  		switch i % 20 {
   168  		case 0:
   169  			loc = "Shanghai"
   170  		default:
   171  			loc = strconv.Itoa(i % 20)
   172  		}
   173  
   174  		// Having the same price and capacity ensures equal weights for all nodes.
   175  		// This way placement is more dependent on the initial order.
   176  		nodeList[i] = nodeInfoFromAttributes("Location", loc, "Price", "1", "Capacity", "10")
   177  		pub := make([]byte, 33)
   178  		pub[0] = byte(i)
   179  		nodeList[i].SetPublicKey(pub)
   180  	}
   181  
   182  	var nm NetMap
   183  	nm.SetNodes(nodeList)
   184  
   185  	getIndices := func(t *testing.T) (uint64, uint64) {
   186  		v, err := nm.ContainerNodes(p, []byte{1})
   187  		require.NoError(t, err)
   188  
   189  		nss := make([]nodes, len(v))
   190  		for i := range v {
   191  			nss[i] = v[i]
   192  		}
   193  
   194  		ns := flattenNodes(nss)
   195  		require.Equal(t, 2, len(ns))
   196  		return ns[0].Hash(), ns[1].Hash()
   197  	}
   198  
   199  	a, b := getIndices(t)
   200  	for range 10 {
   201  		x, y := getIndices(t)
   202  		require.Equal(t, a, x)
   203  		require.Equal(t, b, y)
   204  	}
   205  }
   206  
   207  func TestPlacementPolicy_ProcessSelectors(t *testing.T) {
   208  	p := newPlacementPolicy(2, nil,
   209  		[]Selector{
   210  			newSelector("SameRU", "City", 2, "FromRU", (*Selector).SelectSame),
   211  			newSelector("DistinctRU", "City", 2, "FromRU", (*Selector).SelectDistinct),
   212  			newSelector("Good", "Country", 2, "Good", (*Selector).SelectDistinct),
   213  			newSelector("Main", "Country", 3, "*", (*Selector).SelectDistinct),
   214  		},
   215  		[]Filter{
   216  			newFilter("FromRU", "Country", "Russia", netmap.EQ),
   217  			newFilter("Good", "Rating", "4", netmap.GE),
   218  		})
   219  	nodes := []NodeInfo{
   220  		nodeInfoFromAttributes("Country", "Russia", "Rating", "1", "City", "SPB"),
   221  		nodeInfoFromAttributes("Country", "Germany", "Rating", "5", "City", "Berlin"),
   222  		nodeInfoFromAttributes("Country", "Russia", "Rating", "6", "City", "Moscow"),
   223  		nodeInfoFromAttributes("Country", "France", "Rating", "4", "City", "Paris"),
   224  		nodeInfoFromAttributes("Country", "France", "Rating", "1", "City", "Lyon"),
   225  		nodeInfoFromAttributes("Country", "Russia", "Rating", "5", "City", "SPB"),
   226  		nodeInfoFromAttributes("Country", "Russia", "Rating", "7", "City", "Moscow"),
   227  		nodeInfoFromAttributes("Country", "Germany", "Rating", "3", "City", "Darmstadt"),
   228  		nodeInfoFromAttributes("Country", "Germany", "Rating", "7", "City", "Frankfurt"),
   229  		nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
   230  		nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
   231  	}
   232  
   233  	var nm NetMap
   234  	nm.SetNodes(nodes)
   235  	c := newContext(nm)
   236  	c.setCBF(p.backupFactor)
   237  	require.NoError(t, c.processFilters(p))
   238  	require.NoError(t, c.processSelectors(p))
   239  
   240  	for _, s := range p.selectors {
   241  		sel := c.selections[s.GetName()]
   242  		s := c.processedSelectors[s.GetName()]
   243  		bucketCount, nodesInBucket := calcNodesCount(*s)
   244  		nodesInBucket *= int(c.cbf)
   245  		targ := fmt.Sprintf("selector '%s'", s.GetName())
   246  		require.Equal(t, bucketCount, len(sel), targ)
   247  		fName := s.GetFilter()
   248  		for _, res := range sel {
   249  			require.Equal(t, nodesInBucket, len(res), targ)
   250  			for j := range res {
   251  				require.True(t, fName == mainFilterName || c.match(c.processedFilters[fName], res[j]), targ)
   252  			}
   253  		}
   254  	}
   255  }
   256  
   257  func TestPlacementPolicy_Like(t *testing.T) {
   258  	nodes := []NodeInfo{
   259  		nodeInfoFromAttributes("Country", "Russia"),
   260  		nodeInfoFromAttributes("Country", "Germany"),
   261  		nodeInfoFromAttributes("Country", "Belarus"),
   262  	}
   263  
   264  	var nm NetMap
   265  	nm.SetNodes(nodes)
   266  
   267  	t.Run("LIKE all", func(t *testing.T) {
   268  		ssNamed := []Selector{newSelector("X", "Country", 4, "FromRU", (*Selector).SelectDistinct)}
   269  		fsNamed := []Filter{newFilter("FromRU", "Country", "*", netmap.LIKE)}
   270  		rsNamed := []ReplicaDescriptor{newReplica(4, "X")}
   271  		pNamed := newPlacementPolicy(0, rsNamed, ssNamed, fsNamed)
   272  
   273  		n, err := nm.ContainerNodes(pNamed, []byte{1})
   274  		require.NoError(t, err)
   275  
   276  		require.Equal(t, 3, len(n[0]))
   277  		for _, n := range n[0] {
   278  			require.True(t, strings.Contains("GermanyRussiaBelarus", n.Attribute("Country")))
   279  		}
   280  	})
   281  
   282  	t.Run("LIKE no wildcard", func(t *testing.T) {
   283  		ssNamed := []Selector{newSelector("X", "Country", 1, "FromRU", (*Selector).SelectDistinct)}
   284  		fsNamed := []Filter{newFilter("FromRU", "Country", "Russia", netmap.LIKE)}
   285  		rsNamed := []ReplicaDescriptor{newReplica(1, "X")}
   286  		pNamed := newPlacementPolicy(0, rsNamed, ssNamed, fsNamed)
   287  
   288  		n, err := nm.ContainerNodes(pNamed, []byte{1})
   289  		require.NoError(t, err)
   290  
   291  		require.Equal(t, 1, len(n[0]))
   292  		for _, n := range n[0] {
   293  			require.True(t, n.Attribute("Country") == "Russia")
   294  		}
   295  	})
   296  
   297  	t.Run("LIKE prefix", func(t *testing.T) {
   298  		ssNamed := []Selector{newSelector("X", "Country", 1, "FromRU", (*Selector).SelectDistinct)}
   299  		fsNamed := []Filter{newFilter("FromRU", "Country", "Ge*", netmap.LIKE)}
   300  		rsNamed := []ReplicaDescriptor{newReplica(1, "X")}
   301  		pNamed := newPlacementPolicy(0, rsNamed, ssNamed, fsNamed)
   302  
   303  		n, err := nm.ContainerNodes(pNamed, []byte{1})
   304  		require.NoError(t, err)
   305  
   306  		require.Equal(t, 1, len(n[0]))
   307  		for _, n := range n[0] {
   308  			require.True(t, n.Attribute("Country") == "Germany")
   309  		}
   310  	})
   311  
   312  	t.Run("LIKE suffix", func(t *testing.T) {
   313  		ssNamed := []Selector{newSelector("X", "Country", 1, "FromRU", (*Selector).SelectDistinct)}
   314  		fsNamed := []Filter{newFilter("FromRU", "Country", "*sia", netmap.LIKE)}
   315  		rsNamed := []ReplicaDescriptor{newReplica(1, "X")}
   316  		pNamed := newPlacementPolicy(0, rsNamed, ssNamed, fsNamed)
   317  
   318  		n, err := nm.ContainerNodes(pNamed, []byte{1})
   319  		require.NoError(t, err)
   320  
   321  		require.Equal(t, 1, len(n[0]))
   322  		for _, n := range n[0] {
   323  			require.True(t, n.Attribute("Country") == "Russia")
   324  		}
   325  	})
   326  
   327  	t.Run("LIKE middle", func(t *testing.T) {
   328  		ssNamed := []Selector{newSelector("X", "Country", 2, "FromRU", (*Selector).SelectDistinct)}
   329  		fsNamed := []Filter{newFilter("FromRU", "Country", "*us*", netmap.LIKE)}
   330  		rsNamed := []ReplicaDescriptor{newReplica(2, "X")}
   331  		pNamed := newPlacementPolicy(0, rsNamed, ssNamed, fsNamed)
   332  
   333  		n, err := nm.ContainerNodes(pNamed, []byte{1})
   334  		require.NoError(t, err)
   335  
   336  		require.Equal(t, 2, len(n[0]))
   337  		for _, n := range n[0] {
   338  			require.True(t, strings.Contains("RussiaBelarus", n.Attribute("Country")))
   339  		}
   340  	})
   341  }
   342  
   343  func TestPlacementPolicy_Unique(t *testing.T) {
   344  	p := newPlacementPolicy(2,
   345  		[]ReplicaDescriptor{
   346  			newReplica(1, "S"),
   347  			newReplica(1, "S"),
   348  		},
   349  		[]Selector{
   350  			newSelector("S", "City", 1, "*", (*Selector).SelectSame),
   351  		},
   352  		[]Filter{})
   353  	p.unique = true
   354  
   355  	var nodes []NodeInfo
   356  	for i, city := range []string{"Moscow", "Berlin", "Shenzhen"} {
   357  		for j := range 3 {
   358  			node := nodeInfoFromAttributes("City", city)
   359  			node.SetPublicKey(binary.BigEndian.AppendUint16(nil, uint16(i*4+j)))
   360  			nodes = append(nodes, node)
   361  		}
   362  	}
   363  
   364  	var nm NetMap
   365  	nm.SetNodes(nodes)
   366  
   367  	v, err := nm.ContainerNodes(p, nil)
   368  	require.NoError(t, err)
   369  	for i, vi := range v {
   370  		for _, ni := range vi {
   371  			for j := range i {
   372  				for _, nj := range v[j] {
   373  					require.NotEqual(t, ni.hash, nj.hash)
   374  				}
   375  			}
   376  		}
   377  	}
   378  }
   379  
   380  func TestPlacementPolicy_SingleOmitNames(t *testing.T) {
   381  	nodes := []NodeInfo{
   382  		nodeInfoFromAttributes("ID", "1", "Country", "Russia", "City", "SPB"),
   383  		nodeInfoFromAttributes("ID", "2", "Country", "Germany", "City", "Berlin"),
   384  		nodeInfoFromAttributes("ID", "3", "Country", "Russia", "City", "Moscow"),
   385  		nodeInfoFromAttributes("ID", "4", "Country", "France", "City", "Paris"),
   386  		nodeInfoFromAttributes("ID", "5", "Country", "France", "City", "Lyon"),
   387  		nodeInfoFromAttributes("ID", "6", "Country", "Russia", "City", "SPB"),
   388  		nodeInfoFromAttributes("ID", "7", "Country", "Russia", "City", "Moscow"),
   389  		nodeInfoFromAttributes("ID", "8", "Country", "Germany", "City", "Darmstadt"),
   390  		nodeInfoFromAttributes("ID", "9", "Country", "Germany", "City", "Frankfurt"),
   391  		nodeInfoFromAttributes("ID", "10", "Country", "Russia", "City", "SPB"),
   392  		nodeInfoFromAttributes("ID", "11", "Country", "Russia", "City", "Moscow"),
   393  		nodeInfoFromAttributes("ID", "12", "Country", "Germany", "City", "London"),
   394  	}
   395  	for i := range nodes {
   396  		pub := make([]byte, 33)
   397  		rand.Read(pub)
   398  		nodes[i].SetPublicKey(pub)
   399  	}
   400  
   401  	var nm NetMap
   402  	nm.SetNodes(nodes)
   403  
   404  	for _, unique := range []bool{false, true} {
   405  		t.Run(fmt.Sprintf("unique=%t", unique), func(t *testing.T) {
   406  			ssNamed := []Selector{newSelector("X", "City", 2, "FromRU", (*Selector).SelectDistinct)}
   407  			fsNamed := []Filter{newFilter("FromRU", "Country", "Russia", netmap.EQ)}
   408  			rsNamed := []ReplicaDescriptor{newReplica(1, "X")}
   409  			pNamed := newPlacementPolicy(3, rsNamed, ssNamed, fsNamed)
   410  			pNamed.unique = unique
   411  
   412  			vNamed, err := nm.ContainerNodes(pNamed, []byte{1})
   413  			require.NoError(t, err)
   414  
   415  			ssUnnamed := []Selector{newSelector("", "City", 2, "FromRU", (*Selector).SelectDistinct)}
   416  			fsUnnamed := []Filter{newFilter("FromRU", "Country", "Russia", netmap.EQ)}
   417  			rsUnnamed := []ReplicaDescriptor{newReplica(1, "")}
   418  			pUnnamed := newPlacementPolicy(3, rsUnnamed, ssUnnamed, fsUnnamed)
   419  			pUnnamed.unique = unique
   420  
   421  			vUnnamed, err := nm.ContainerNodes(pUnnamed, []byte{1})
   422  			require.NoError(t, err)
   423  
   424  			require.Equal(t, vNamed, vUnnamed)
   425  		})
   426  	}
   427  }
   428  
   429  func TestPlacementPolicy_MultiREP(t *testing.T) {
   430  	nodes := []NodeInfo{
   431  		nodeInfoFromAttributes("ID", "1", "Country", "Russia", "City", "SPB"),
   432  		nodeInfoFromAttributes("ID", "2", "Country", "Germany", "City", "Berlin"),
   433  		nodeInfoFromAttributes("ID", "3", "Country", "Russia", "City", "Moscow"),
   434  		nodeInfoFromAttributes("ID", "4", "Country", "France", "City", "Paris"),
   435  		nodeInfoFromAttributes("ID", "5", "Country", "France", "City", "Lyon"),
   436  		nodeInfoFromAttributes("ID", "6", "Country", "Russia", "City", "SPB"),
   437  		nodeInfoFromAttributes("ID", "7", "Country", "Russia", "City", "Moscow"),
   438  		nodeInfoFromAttributes("ID", "8", "Country", "Germany", "City", "Darmstadt"),
   439  		nodeInfoFromAttributes("ID", "9", "Country", "Germany", "City", "Frankfurt"),
   440  		nodeInfoFromAttributes("ID", "10", "Country", "Russia", "City", "SPB"),
   441  		nodeInfoFromAttributes("ID", "11", "Country", "Russia", "City", "Moscow"),
   442  		nodeInfoFromAttributes("ID", "12", "Country", "Germany", "City", "London"),
   443  	}
   444  	for i := range nodes {
   445  		pub := make([]byte, 33)
   446  		rand.Read(pub)
   447  		nodes[i].SetPublicKey(pub)
   448  	}
   449  
   450  	var nm NetMap
   451  	nm.SetNodes(nodes)
   452  
   453  	ss := []Selector{newSelector("SameRU", "City", 2, "FromRU", (*Selector).SelectDistinct)}
   454  	fs := []Filter{newFilter("FromRU", "Country", "Russia", netmap.EQ)}
   455  
   456  	for _, unique := range []bool{false, true} {
   457  		for _, additional := range []int{0, 1, 2} {
   458  			t.Run(fmt.Sprintf("unique=%t, additional=%d", unique, additional), func(t *testing.T) {
   459  				rs := []ReplicaDescriptor{newReplica(1, "SameRU")}
   460  				for range additional {
   461  					rs = append(rs, newReplica(1, ""))
   462  				}
   463  
   464  				p := newPlacementPolicy(3, rs, ss, fs)
   465  				p.unique = unique
   466  
   467  				v, err := nm.ContainerNodes(p, []byte{1})
   468  				require.NoError(t, err)
   469  				require.Equal(t, 1+additional, len(v))
   470  				require.Equal(t, 6, len(v[0]))
   471  
   472  				for i := 1; i < additional; i++ {
   473  					require.Equal(t, 3, len(v[i]))
   474  					if !unique {
   475  						require.Equal(t, v[1], v[i])
   476  					}
   477  				}
   478  
   479  				if unique {
   480  					seen := make(map[string]bool)
   481  					for i := range v {
   482  						for j := range v[i] {
   483  							attr := v[i][j].Attribute("ID")
   484  							require.NotEmpty(t, attr)
   485  							require.False(t, seen[attr])
   486  
   487  							seen[attr] = true
   488  						}
   489  					}
   490  				}
   491  			})
   492  		}
   493  	}
   494  }
   495  
   496  func TestPlacementPolicy_ProcessSelectorsExceptForNodes(t *testing.T) {
   497  	p := newPlacementPolicy(1, nil,
   498  		[]Selector{
   499  			newSelector("ExceptRU", "City", 2, "ExceptRU", (*Selector).SelectSame),
   500  		},
   501  		[]Filter{
   502  			newFilter("ExceptRU", "", "", netmap.NOT,
   503  				newFilter("", "", "", netmap.AND,
   504  					newFilter("", "City", "Lyon", netmap.EQ),
   505  					newFilter("", "Rating", "10", netmap.LE),
   506  				),
   507  			),
   508  		})
   509  	nodes := []NodeInfo{
   510  		nodeInfoFromAttributes("Country", "Germany", "Rating", "1", "City", "Berlin"),
   511  		nodeInfoFromAttributes("Country", "Germany", "Rating", "5", "City", "Berlin"),
   512  		nodeInfoFromAttributes("Country", "Russia", "Rating", "6", "City", "Moscow"),
   513  		nodeInfoFromAttributes("Country", "France", "Rating", "4", "City", "Paris"),
   514  		nodeInfoFromAttributes("Country", "France", "Rating", "1", "City", "Lyon"),
   515  		nodeInfoFromAttributes("Country", "France", "Rating", "5", "City", "Lyon"),
   516  		nodeInfoFromAttributes("Country", "Russia", "Rating", "7", "City", "Moscow"),
   517  		nodeInfoFromAttributes("Country", "Germany", "Rating", "3", "City", "Darmstadt"),
   518  		nodeInfoFromAttributes("Country", "Germany", "Rating", "7", "City", "Frankfurt"),
   519  		nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
   520  		nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
   521  	}
   522  
   523  	var nm NetMap
   524  	nm.SetNodes(nodes)
   525  	c := newContext(nm)
   526  	c.setCBF(p.backupFactor)
   527  	require.NoError(t, c.processFilters(p))
   528  	require.NoError(t, c.processSelectors(p))
   529  
   530  	for _, s := range p.selectors {
   531  		sel := c.selections[s.GetName()]
   532  		s := c.processedSelectors[s.GetName()]
   533  		bucketCount, nodesInBucket := calcNodesCount(*s)
   534  		nodesInBucket *= int(c.cbf)
   535  		targ := fmt.Sprintf("selector '%s'", s.GetName())
   536  		require.Equal(t, bucketCount, len(sel), targ)
   537  		fName := s.GetFilter()
   538  		for _, res := range sel {
   539  			require.Equal(t, nodesInBucket, len(res), targ)
   540  			for j := range res {
   541  				require.True(t, fName == mainFilterName || c.match(c.processedFilters[fName], res[j]), targ)
   542  			}
   543  		}
   544  	}
   545  }
   546  
   547  func TestPlacementPolicy_NonAsciiAttributes(t *testing.T) {
   548  	p := newPlacementPolicy(
   549  		1,
   550  		[]ReplicaDescriptor{
   551  			newReplica(2, "Nodes"),
   552  			newReplica(2, "Nodes"),
   553  		},
   554  		[]Selector{
   555  			newSelector("Nodes", "Цвет", 2, "Colorful", (*Selector).SelectSame),
   556  		},
   557  		[]Filter{
   558  			newFilter("Colorful", "", "", netmap.OR,
   559  				newFilter("", "Цвет", "Красный", netmap.EQ),
   560  				newFilter("", "Цвет", "Синий", netmap.EQ),
   561  			),
   562  		},
   563  	)
   564  	p.SetUnique(true)
   565  
   566  	nodes := []NodeInfo{
   567  		nodeInfoFromAttributes("Цвет", "Красный", "Форма", "Треугольник"),
   568  		nodeInfoFromAttributes("Цвет", "Красный", "Форма", "Круг"),
   569  		nodeInfoFromAttributes("Цвет", "Синий", "Форма", "Треугольник"),
   570  		nodeInfoFromAttributes("Цвет", "Синий", "Форма", "Круг"),
   571  		nodeInfoFromAttributes("Свойство", "Мягкий", "Форма", "Треугольник"),
   572  		nodeInfoFromAttributes("Свойство", "Теплый", "Форма", "Круг"),
   573  	}
   574  	for i := range nodes {
   575  		nodes[i].SetPublicKey([]byte{byte(i)})
   576  	}
   577  
   578  	redNodes := nodes[:2]
   579  	blueNodes := nodes[2:4]
   580  
   581  	var nm NetMap
   582  	nm.SetNodes(nodes)
   583  
   584  	pivot := make([]byte, 42)
   585  	_, _ = rand.Read(pivot)
   586  
   587  	nodesPerReplica, err := nm.ContainerNodes(p, pivot)
   588  	require.NoError(t, err)
   589  	require.Len(t, nodesPerReplica, 2)
   590  
   591  	for i := range nodesPerReplica {
   592  		slices.SortFunc(nodesPerReplica[i], func(n1, n2 NodeInfo) int {
   593  			pk1, pk2 := string(n1.PublicKey()), string(n2.PublicKey())
   594  			return cmp.Compare(pk1, pk2)
   595  		})
   596  	}
   597  
   598  	redMatchFirst := reflect.DeepEqual(redNodes, nodesPerReplica[0])
   599  	blueMatchFirst := reflect.DeepEqual(blueNodes, nodesPerReplica[0])
   600  
   601  	redMatchSecond := reflect.DeepEqual(redNodes, nodesPerReplica[1])
   602  	blueMatchSecond := reflect.DeepEqual(blueNodes, nodesPerReplica[1])
   603  
   604  	assert.True(t, redMatchFirst && blueMatchSecond || blueMatchFirst && redMatchSecond)
   605  }
   606  
   607  func TestSelector_SetName(t *testing.T) {
   608  	const name = "some name"
   609  	var s Selector
   610  
   611  	require.Zero(t, s.m.GetName())
   612  
   613  	s.SetName(name)
   614  	require.Equal(t, name, s.m.GetName())
   615  }
   616  
   617  func TestSelector_SetNumberOfNodes(t *testing.T) {
   618  	const num = 3
   619  	var s Selector
   620  
   621  	require.Zero(t, s.m.GetCount())
   622  
   623  	s.SetNumberOfNodes(num)
   624  
   625  	require.EqualValues(t, num, s.m.GetCount())
   626  }
   627  
   628  func TestSelectorClauses(t *testing.T) {
   629  	var s Selector
   630  
   631  	require.Equal(t, netmap.UnspecifiedClause, s.m.GetClause())
   632  
   633  	s.SelectDistinct()
   634  	require.Equal(t, netmap.Distinct, s.m.GetClause())
   635  
   636  	s.SelectSame()
   637  	require.Equal(t, netmap.Same, s.m.GetClause())
   638  }
   639  
   640  func TestSelector_SelectByBucketAttribute(t *testing.T) {
   641  	const attr = "some attribute"
   642  	var s Selector
   643  
   644  	require.Zero(t, s.m.GetAttribute())
   645  
   646  	s.SelectByBucketAttribute(attr)
   647  	require.Equal(t, attr, s.m.GetAttribute())
   648  }
   649  
   650  func TestSelector_SetFilterName(t *testing.T) {
   651  	const fName = "some filter"
   652  	var s Selector
   653  
   654  	require.Zero(t, s.m.GetFilter())
   655  
   656  	s.SetFilterName(fName)
   657  	require.Equal(t, fName, s.m.GetFilter())
   658  }