diff --git a/tests/python_client/milvus_client/test_milvus_client_query.py b/tests/python_client/milvus_client/test_milvus_client_query.py index b9eae7a14b569..457495aaf6949 100644 --- a/tests/python_client/milvus_client/test_milvus_client_query.py +++ b/tests/python_client/milvus_client/test_milvus_client_query.py @@ -238,3 +238,216 @@ def test_milvus_client_query_limit(self): "with_vec": True, "primary_field": default_primary_key_field_name[:limit]})[0] client_w.drop_collection(client, collection_name) + + +class TestMilvusClientGetInvalid(TestcaseBase): + """ Test case of search interface """ + + """ + ****************************************************************** + # The following are invalid base cases + ****************************************************************** + """ + + @pytest.mark.tags(CaseLabel.L2) + @pytest.mark.parametrize("name", + ["12-s", "12 s", "(mn)", "中文", "%$#", "".join("a" for i in range(ct.max_name_length + 1))]) + def test_milvus_client_get_invalid_collection_name(self, name): + """ + target: test get interface invalid cases + method: invalid collection name + expected: search/query successfully without deleted data + """ + client = self._connect(enable_milvus_client_api=True) + collection_name = cf.gen_unique_str(prefix) + # 1. create collection + client_w.create_collection(client, collection_name, default_dim, consistency_level="Strong") + # 2. insert + default_nb = 1000 + rng = np.random.default_rng(seed=19530) + rows = [{default_primary_key_field_name: i, default_vector_field_name: list(rng.random((1, default_dim))[0]), + default_float_field_name: i * 1.0, default_string_field_name: str(i)} for i in range(default_nb)] + client_w.insert(client, collection_name, rows)[0] + pks = [i for i in range(default_nb)] + # 3. get first primary key + error = {ct.err_code: 1100, ct.err_msg: f"Invalid collection name"} + client_w.get(client, name, ids=pks[0:1], + check_task=CheckTasks.err_res, check_items=error) + client_w.drop_collection(client, collection_name) + + @pytest.mark.tags(CaseLabel.L2) + def test_milvus_client_get_not_exist_collection_name(self): + """ + target: test get interface invalid cases + method: invalid collection name + expected: search/query successfully without deleted data + """ + client = self._connect(enable_milvus_client_api=True) + collection_name = cf.gen_unique_str(prefix) + # 1. create collection + client_w.create_collection(client, collection_name, default_dim, consistency_level="Strong") + # 2. insert + default_nb = 1000 + rng = np.random.default_rng(seed=19530) + rows = [{default_primary_key_field_name: i, default_vector_field_name: list(rng.random((1, default_dim))[0]), + default_float_field_name: i * 1.0, default_string_field_name: str(i)} for i in range(default_nb)] + client_w.insert(client, collection_name, rows)[0] + pks = [i for i in range(default_nb)] + # 3. get first primary key + name = "invalid" + error = {ct.err_code: 100, ct.err_msg: f"can't find collection[database=default][collection={name}]"} + client_w.get(client, name, ids=pks[0:1], + check_task=CheckTasks.err_res, check_items=error) + client_w.drop_collection(client, collection_name) + + @pytest.mark.tags(CaseLabel.L2) + @pytest.mark.parametrize("invalid_ids",["中文", "%$#"]) + def test_milvus_client_get_invalid_ids(self, invalid_ids): + """ + target: test get interface invalid cases + method: invalid collection name + expected: search/query successfully without deleted data + """ + client = self._connect(enable_milvus_client_api=True) + collection_name = cf.gen_unique_str(prefix) + # 1. create collection + client_w.create_collection(client, collection_name, default_dim, consistency_level="Strong") + # 2. insert + default_nb = 1000 + rng = np.random.default_rng(seed=19530) + rows = [{default_primary_key_field_name: i, default_vector_field_name: list(rng.random((1, default_dim))[0]), + default_float_field_name: i * 1.0, default_string_field_name: str(i)} for i in range(default_nb)] + client_w.insert(client, collection_name, rows)[0] + # 3. get first primary key + error = {ct.err_code: 1100, ct.err_msg: f"cannot parse expression"} + client_w.get(client, collection_name, ids=invalid_ids, + check_task=CheckTasks.err_res, check_items=error) + client_w.drop_collection(client, collection_name) + + +class TestMilvusClientGetValid(TestcaseBase): + """ Test case of search interface """ + + @pytest.fixture(scope="function", params=[False, True]) + def auto_id(self, request): + yield request.param + + @pytest.fixture(scope="function", params=["COSINE", "L2"]) + def metric_type(self, request): + yield request.param + + """ + ****************************************************************** + # The following are valid base cases + ****************************************************************** + """ + @pytest.mark.tags(CaseLabel.L1) + def test_milvus_client_get_normal(self): + """ + target: test get interface + method: create connection, collection, insert delete, and search + expected: search/query successfully without deleted data + """ + client = self._connect(enable_milvus_client_api=True) + collection_name = cf.gen_unique_str(prefix) + # 1. create collection + client_w.create_collection(client, collection_name, default_dim, consistency_level="Strong") + # 2. insert + default_nb = 1000 + rng = np.random.default_rng(seed=19530) + rows = [{default_primary_key_field_name: i, default_vector_field_name: list(rng.random((1, default_dim))[0]), + default_float_field_name: i * 1.0, default_string_field_name: str(i)} for i in range(default_nb)] + client_w.insert(client, collection_name, rows)[0] + pks = [i for i in range(default_nb)] + # 3. get first primary key + first_pk_data = client_w.get(client, collection_name, ids=pks[0:1])[0] + assert len(first_pk_data) == len(pks[0:1]) + first_pk_data_1 = client_w.get(client, collection_name, ids=0)[0] + assert first_pk_data == first_pk_data_1 + client_w.drop_collection(client, collection_name) + + @pytest.mark.tags(CaseLabel.L2) + def test_milvus_client_get_output_fields(self): + """ + target: test get interface with output fields + method: create connection, collection, insert delete, and search + expected: search/query successfully without deleted data + """ + client = self._connect(enable_milvus_client_api=True) + collection_name = cf.gen_unique_str(prefix) + # 1. create collection + client_w.create_collection(client, collection_name, default_dim, consistency_level="Strong") + # 2. insert + default_nb = 1000 + rng = np.random.default_rng(seed=19530) + rows = [{default_primary_key_field_name: i, default_vector_field_name: list(rng.random((1, default_dim))[0]), + default_float_field_name: i * 1.0, default_string_field_name: str(i)} for i in range(default_nb)] + client_w.insert(client, collection_name, rows)[0] + pks = [i for i in range(default_nb)] + # 3. get first primary key + output_fields_array = [default_primary_key_field_name, default_vector_field_name, + default_float_field_name, default_string_field_name] + first_pk_data = client_w.get(client, collection_name, ids=pks[0:1], output_fields=output_fields_array)[0] + assert len(first_pk_data) == len(pks[0:1]) + assert len(first_pk_data[0]) == len(output_fields_array) + first_pk_data_1 = client_w.get(client, collection_name, ids=0, output_fields=output_fields_array)[0] + assert first_pk_data == first_pk_data_1 + assert len(first_pk_data_1[0]) == len(output_fields_array) + client_w.drop_collection(client, collection_name) + + @pytest.mark.tags(CaseLabel.L1) + @pytest.mark.skip(reason="pymilvus issue 2056") + def test_milvus_client_get_normal_string(self): + """ + target: test get interface for string field + method: create connection, collection, insert delete, and search + expected: search/query successfully without deleted data + """ + client = self._connect(enable_milvus_client_api=True) + collection_name = cf.gen_unique_str(prefix) + # 1. create collection + client_w.create_collection(client, collection_name, default_dim, id_type="string", max_length=ct.default_length) + # 2. insert + rng = np.random.default_rng(seed=19530) + rows = [ + {default_primary_key_field_name: str(i), default_vector_field_name: list(rng.random((1, default_dim))[0]), + default_float_field_name: i * 1.0, default_string_field_name: str(i)} for i in range(default_nb)] + client_w.insert(client, collection_name, rows)[0] + pks = [str(i) for i in range(default_nb)] + # 3. get first primary key + first_pk_data = client_w.get(client, collection_name, ids=pks[0:1])[0] + assert len(first_pk_data) == len(pks[0:1]) + first_pk_data_1 = client_w.get(client, collection_name, ids="0")[0] + assert first_pk_data == first_pk_data_1 + + client_w.drop_collection(client, collection_name) + + @pytest.mark.tags(CaseLabel.L2) + @pytest.mark.skip(reason="pymilvus issue 2056") + def test_milvus_client_get_normal_string_output_fields(self): + """ + target: test get interface for string field + method: create connection, collection, insert delete, and search + expected: search/query successfully without deleted data + """ + client = self._connect(enable_milvus_client_api=True) + collection_name = cf.gen_unique_str(prefix) + # 1. create collection + client_w.create_collection(client, collection_name, default_dim, id_type="string", max_length=ct.default_length) + # 2. insert + rng = np.random.default_rng(seed=19530) + rows = [ + {default_primary_key_field_name: str(i), default_vector_field_name: list(rng.random((1, default_dim))[0]), + default_float_field_name: i * 1.0, default_string_field_name: str(i)} for i in range(default_nb)] + client_w.insert(client, collection_name, rows)[0] + pks = [str(i) for i in range(default_nb)] + # 3. get first primary key + output_fields_array = [default_primary_key_field_name, default_vector_field_name, + default_float_field_name, default_string_field_name] + first_pk_data = client_w.get(client, collection_name, ids=pks[0:1], output_fields=output_fields_array)[0] + assert len(first_pk_data) == len(pks[0:1]) + assert len(first_pk_data[0]) == len(output_fields_array) + first_pk_data_1 = client_w.get(client, collection_name, ids="0", output_fields=output_fields_array)[0] + assert first_pk_data == first_pk_data_1 + assert len(first_pk_data_1[0]) == len(output_fields_array) + client_w.drop_collection(client, collection_name) \ No newline at end of file diff --git a/tests/python_client/testcases/test_collection.py b/tests/python_client/testcases/test_collection.py index cf4208445e8c3..a18c0c8a216ec 100644 --- a/tests/python_client/testcases/test_collection.py +++ b/tests/python_client/testcases/test_collection.py @@ -4489,7 +4489,7 @@ def test_create_collection_multiple_vectors_invalid_all_vector_field_name(self, self.collection_wrap.init_collection(c_name, schema=schema, check_task=CheckTasks.err_res, check_items=error) @pytest.mark.tags(CaseLabel.L1) - @pytest.mark.xfail(reason="issue #29796") + @pytest.mark.skip(reason="issue #29796") def test_create_collection_multiple_vectors_invalid_dim(self, get_invalid_dim): """ target: test create collection with multiple vector fields diff --git a/tests/python_client/testcases/test_partition.py b/tests/python_client/testcases/test_partition.py index d7d0852010423..9a4b42cb4d687 100644 --- a/tests/python_client/testcases/test_partition.py +++ b/tests/python_client/testcases/test_partition.py @@ -320,7 +320,7 @@ def get_non_number_replicas(self, request): yield request.param @pytest.mark.tags(CaseLabel.L2) - @pytest.mark.xfail(reason="issue #21618") + @pytest.mark.skip(reason="issue #21618") def test_load_partition_replica_non_number(self, get_non_number_replicas): """ target: test load partition with non-number replicas diff --git a/tests/python_client/testcases/test_query.py b/tests/python_client/testcases/test_query.py index 8101c253c470f..1c4f4830f4277 100644 --- a/tests/python_client/testcases/test_query.py +++ b/tests/python_client/testcases/test_query.py @@ -1329,7 +1329,7 @@ def test_query_output_one_field(self, enable_dynamic_field): assert set(res[0].keys()) == {ct.default_int64_field_name, ct.default_float_field_name} @pytest.mark.tags(CaseLabel.L1) - @pytest.mark.xfail(reason="issue 30437") + @pytest.mark.skip(reason="issue 30437") def test_query_output_all_fields(self, enable_dynamic_field, random_primary_key): """ target: test query with none output field @@ -1505,7 +1505,7 @@ def test_query_output_not_existed_field(self): check_task=CheckTasks.err_res, check_items=error) @pytest.mark.tags(CaseLabel.L2) - @pytest.mark.xfail(reason="exception not MilvusException") + @pytest.mark.skip(reason="exception not MilvusException") def test_query_invalid_output_fields(self): """ target: test query with invalid output fields @@ -1520,7 +1520,7 @@ def test_query_invalid_output_fields(self): check_items=error) @pytest.mark.tags(CaseLabel.L0) - @pytest.mark.xfail(reason="issue 24637") + @pytest.mark.skip(reason="issue 24637") def test_query_output_fields_simple_wildcard(self): """ target: test query output_fields with simple wildcard (* and %) @@ -1539,7 +1539,7 @@ def test_query_output_fields_simple_wildcard(self): check_items={exp_res: res3, "with_vec": True}) @pytest.mark.tags(CaseLabel.L1) - @pytest.mark.xfail(reason="issue 24637") + @pytest.mark.skip(reason="issue 24637") def test_query_output_fields_part_scale_wildcard(self): """ target: test query output_fields with part wildcard @@ -2679,7 +2679,7 @@ def test_query_compare_invalid_fields(self): f"error: comparisons between VarChar and Int64 are not supported: invalid parameter"}) @pytest.mark.tags(CaseLabel.L1) - @pytest.mark.xfail(reason="issue 24637") + @pytest.mark.skip(reason="issue 24637") def test_query_after_insert_multi_threading(self): """ target: test data consistency after multi threading insert diff --git a/tests/python_client/testcases/test_search.py b/tests/python_client/testcases/test_search.py index ad45962227db3..3dd14b994a387 100644 --- a/tests/python_client/testcases/test_search.py +++ b/tests/python_client/testcases/test_search.py @@ -154,6 +154,10 @@ def get_invalid_range_search_paras(self, request): def enable_dynamic_field(self, request): yield request.param + @pytest.fixture(scope="function", params=["FLOAT_VECTOR", "FLOAT16_VECTOR", "BFLOAT16_VECTOR"]) + def vector_data_type(self, request): + yield request.param + """ ****************************************************************** # The followings are invalid cases @@ -322,7 +326,7 @@ def test_search_param_invalid_metric_type(self, get_invalid_metric_type): "err_msg": "metric type not match"}) @pytest.mark.tags(CaseLabel.L1) - @pytest.mark.xfail(reason="issue 30356") + @pytest.mark.skip(reason="issue 30356") def test_search_param_metric_type_not_match(self): """ target: test search with invalid parameter values @@ -720,9 +724,8 @@ def test_search_release_partition(self): check_items={"err_code": 65535, "err_msg": "collection not loaded"}) - @pytest.mark.skip("enable this later using session/strong consistency") @pytest.mark.tags(CaseLabel.L1) - def test_search_with_empty_collection(self): + def test_search_with_empty_collection(self, vector_data_type): """ target: test search with empty connection method: 1. search the empty collection before load @@ -733,15 +736,16 @@ def test_search_with_empty_collection(self): 3. return topk successfully """ # 1. initialize without data - collection_w = self.init_collection_general(prefix)[0] + collection_w = self.init_collection_general(prefix, is_index=False, vector_data_type=vector_data_type)[0] # 2. search collection without data before load log.info("test_search_with_empty_collection: Searching empty collection %s" % collection_w.name) err_msg = "collection" + collection_w.name + "was not loaded into memory" + vectors = cf.gen_vectors_based_on_vector_type(default_nq, default_dim, vector_data_type) collection_w.search(vectors[:default_nq], default_search_field, default_search_params, default_limit, default_search_exp, timeout=1, check_task=CheckTasks.err_res, - check_items={"err_code": 1, + check_items={"err_code": 101, "err_msg": err_msg}) # 3. search collection without data after load collection_w.create_index( @@ -754,16 +758,15 @@ def test_search_with_empty_collection(self): "ids": [], "limit": 0}) # 4. search with data inserted but not load again - data = cf.gen_default_dataframe_data(nb=2000) - insert_res = collection_w.insert(data)[0] + insert_res = cf.insert_data(collection_w, vector_data_type=vector_data_type)[3] + assert collection_w.num_entities == default_nb # Using bounded staleness, maybe we cannot search the "inserted" requests, # since the search requests arrived query nodes earlier than query nodes consume the insert requests. collection_w.search(vectors[:default_nq], default_search_field, default_search_params, default_limit, default_search_exp, - guarantee_timestamp=insert_res.timestamp, check_task=CheckTasks.check_search_results, check_items={"nq": default_nq, - "ids": insert_res.primary_keys, + "ids": insert_res, "limit": default_limit}) @pytest.mark.tags(CaseLabel.L2) @@ -922,7 +925,7 @@ def test_search_with_invalid_nq(self, nq): "request) should be in range [1, 16384]"}) @pytest.mark.tags(CaseLabel.L2) - @pytest.mark.xfail(reason="issue 15407") + @pytest.mark.skip(reason="issue 15407") def test_search_param_invalid_binary(self): """ target: test search within binary data (invalid parameter) @@ -1200,7 +1203,7 @@ def test_range_search_invalid_radius_range_filter_L2(self): "err_msg": "range_filter must less than radius except IP"}) @pytest.mark.tags(CaseLabel.L1) - @pytest.mark.xfail(reason="issue 30365") + @pytest.mark.skip(reason="issue 30365") def test_range_search_invalid_radius_range_filter_IP(self): """ target: test range search with invalid radius and range_filter for IP @@ -9560,7 +9563,7 @@ def test_load_collection_release_all_partitions(self): ct.err_msg: "collection not loaded"}) @pytest.mark.tags(CaseLabel.L2) - @pytest.mark.xfail(reason="issue #24446") + @pytest.mark.skip(reason="issue #24446") def test_search_load_collection_create_partition(self): """ target: test load collection and create partition and search @@ -10857,6 +10860,103 @@ def test_hybrid_search_normal(self, nq, is_flush, offset, primary_field, vector_ for i in range(len(score_answer_nq[k][:default_limit])): assert score_answer_nq[k][i] - hybrid_res[k].distances[i] < hybrid_search_epsilon + @pytest.mark.tags(CaseLabel.L2) + @pytest.mark.parametrize("nq", [16384]) + def test_hybrid_search_normal_max_nq(self, nq): + """ + target: test hybrid search normal case + method: create connection, collection, insert and search + expected: hybrid search successfully with limit(topK) + """ + # 1. initialize collection with data + collection_w, _, _, insert_ids, time_stamp = self.init_collection_general(prefix, True)[0:5] + # 2. extract vector field name + vector_name_list = cf.extract_vector_field_name_list(collection_w) + vector_name_list.append(ct.default_float_vec_field_name) + # 3. prepare search params + req_list = [] + weights = [1] + vectors = cf.gen_vectors_based_on_vector_type(nq, default_dim, "FLOAT_VECTOR") + log.debug("binbin") + log.debug(vectors) + # 4. get hybrid search req list + for i in range(len(vector_name_list)): + search_param = { + "data": vectors, + "anns_field": vector_name_list[i], + "param": {"metric_type": "COSINE"}, + "limit": default_limit, + "expr": "int64 > 0"} + req = AnnSearchRequest(**search_param) + req_list.append(req) + # 5. hybrid search + hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, + check_task=CheckTasks.check_search_results, + check_items={"nq": nq, + "ids": insert_ids, + "limit": default_limit})[0] + + @pytest.mark.tags(CaseLabel.L1) + @pytest.mark.skip(reason="issue 32288") + @pytest.mark.parametrize("nq", [0, 16385]) + def test_hybrid_search_normal_over_max_nq(self, nq): + """ + target: test hybrid search normal case + method: create connection, collection, insert and search + expected: hybrid search successfully with limit(topK) + """ + # 1. initialize collection with data + collection_w = self.init_collection_general(prefix, True)[0] + # 2. extract vector field name + vector_name_list = cf.extract_vector_field_name_list(collection_w) + vector_name_list.append(ct.default_float_vec_field_name) + # 3. prepare search params + req_list = [] + weights = [1] + vectors = cf.gen_vectors_based_on_vector_type(nq, default_dim, "FLOAT_VECTOR") + # 4. get hybrid search req list + for i in range(len(vector_name_list)): + search_param = { + "data": vectors, + "anns_field": vector_name_list[i], + "param": {"metric_type": "COSINE"}, + "limit": default_limit, + "expr": "int64 > 0"} + req = AnnSearchRequest(**search_param) + req_list.append(req) + # 5. hybrid search + err_msg = "nq (number of search vector per search request) should be in range [1, 16384]" + collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, + check_task=CheckTasks.err_res, + check_items={"err_code": 65535, + "err_msg": err_msg}) + + @pytest.mark.tags(CaseLabel.L1) + def test_hybrid_search_no_limit(self): + """ + target: test hybrid search with no limit + method: create connection, collection, insert and search + expected: hybrid search successfully with limit(topK) + """ + # 1. initialize collection with data + multiple_dim_array = [default_dim, default_dim] + collection_w, _, _, insert_ids, time_stamp = \ + self.init_collection_general(prefix, True, multiple_dim_array=multiple_dim_array)[0:5] + # 2. extract vector field name + vector_name_list = cf.extract_vector_field_name_list(collection_w) + vector_name_list.append(ct.default_float_vec_field_name) + # 3. prepare search params + vectors = cf.gen_vectors_based_on_vector_type(nq, default_dim, "FLOAT_VECTOR") + + # get hybrid search req list + search_param = { + "data": vectors, + "anns_field": vector_name_list[0], + "param": {"metric_type": "COSINE"}, + "limit": default_limit, + "expr": "int64 > 0"} + req = AnnSearchRequest(**search_param) + @pytest.mark.tags(CaseLabel.L2) @pytest.mark.parametrize("primary_field", [ct.default_int64_field_name, ct.default_string_field_name]) def test_hybrid_search_WeightedRanker_empty_reqs(self, primary_field): @@ -11161,7 +11261,7 @@ def test_hybrid_search_overall_different_limit(self, primary_field, metric_type) req_list = [] for i in range(len(vector_name_list)): search_param = { - "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(1)], + "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(nq)], "anns_field": vector_name_list[i], "param": {"metric_type": metric_type, "offset": 0}, "limit": default_limit - i, @@ -11171,7 +11271,7 @@ def test_hybrid_search_overall_different_limit(self, primary_field, metric_type) # 4. hybrid search collection_w.hybrid_search(req_list, WeightedRanker(0.1, 0.9), default_limit, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, "limit": default_limit}) @@ -11248,7 +11348,7 @@ def test_hybrid_search_max_limit(self, primary_field, metric_type): req_list = [] for i in range(len(vector_name_list)): search_param = { - "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(1)], + "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(nq)], "anns_field": vector_name_list[i], "param": {"metric_type": metric_type}, "limit": max_limit, @@ -11258,7 +11358,7 @@ def test_hybrid_search_max_limit(self, primary_field, metric_type): # 4. hybrid search collection_w.hybrid_search(req_list, WeightedRanker(0.1, 0.9), default_limit, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, "limit": default_limit}) @@ -11290,7 +11390,7 @@ def test_hybrid_search_max_min_limit(self, primary_field, metric_type): if i == 1: limit = 1 search_param = { - "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(1)], + "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(nq)], "anns_field": vector_name_list[i], "param": {"metric_type": metric_type}, "limit": limit, @@ -11300,7 +11400,7 @@ def test_hybrid_search_max_min_limit(self, primary_field, metric_type): # 4. hybrid search collection_w.hybrid_search(req_list, WeightedRanker(0.1, 0.9), default_limit, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, "limit": default_limit}) @@ -11329,7 +11429,7 @@ def test_hybrid_search_same_anns_field(self, primary_field, metric_type): req_list = [] for i in range(len(vector_name_list)): search_param = { - "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(1)], + "data": [[random.random() for _ in range(multiple_dim_array[i])] for _ in range(nq)], "anns_field": vector_name_list[0], "param": {"metric_type": metric_type, "offset": 0}, "limit": default_limit, @@ -11339,7 +11439,7 @@ def test_hybrid_search_same_anns_field(self, primary_field, metric_type): # 4. hybrid search collection_w.hybrid_search(req_list, WeightedRanker(0.1, 0.9), default_limit, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, "limit": default_limit}) @@ -11368,7 +11468,7 @@ def test_hybrid_search_different_offset_single_field(self, primary_field, is_flu req_list = [] for i in range(len(vector_name_list)): search_param = { - "data": [[random.random() for _ in range(dim)] for _ in range(1)], + "data": [[random.random() for _ in range(dim)] for _ in range(nq)], "anns_field": vector_name_list[i], "param": {"metric_type": metric_type, "offset": i}, "limit": default_limit, @@ -11378,7 +11478,7 @@ def test_hybrid_search_different_offset_single_field(self, primary_field, is_flu # 4. hybrid search collection_w.hybrid_search(req_list, WeightedRanker(0.1, 0.9, 1), default_limit, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, "limit": default_limit}) @@ -11983,197 +12083,332 @@ def test_hybrid_search_limit_out_of_range_min(self): @pytest.mark.tags(CaseLabel.L2) @pytest.mark.parametrize("primary_field", [ct.default_int64_field_name, ct.default_string_field_name]) - def test_hybrid_search_with_output_fields_all_fields(self, primary_field): + def test_hybrid_search_with_output_fields(self, nq, dim, auto_id, is_flush, enable_dynamic_field, + primary_field, vector_data_type): """ target: test hybrid search normal case method: create connection, collection, insert and search expected: hybrid search successfully with limit(topK) """ # 1. initialize collection with data + nq = 10 + multiple_dim_array = [dim, dim] collection_w, _, _, insert_ids, time_stamp = \ - self.init_collection_general(prefix, True, primary_field=primary_field, - multiple_dim_array=[default_dim, default_dim])[0:5] + self.init_collection_general(prefix, True, auto_id=auto_id, dim=dim, is_flush=is_flush, + primary_field=primary_field, + enable_dynamic_field=enable_dynamic_field, + multiple_dim_array=multiple_dim_array, + vector_data_type=vector_data_type)[0:5] # 2. extract vector field name vector_name_list = cf.extract_vector_field_name_list(collection_w) vector_name_list.append(ct.default_float_vec_field_name) # 3. prepare search params req_list = [] weights = [0.2, 0.3, 0.5] + metrics = [] search_res_dict_array = [] + search_res_dict_array_nq = [] + vectors = cf.gen_vectors_based_on_vector_type(nq, dim, vector_data_type) + + # get hybrid search req list + for i in range(len(vector_name_list)): + search_param = { + "data": vectors, + "anns_field": vector_name_list[i], + "param": {"metric_type": "COSINE"}, + "limit": default_limit, + "expr": "int64 > 0"} + req = AnnSearchRequest(**search_param) + req_list.append(req) + metrics.append("COSINE") + + # get the result of search with the same params of the following hybrid search + single_search_param = {"metric_type": "COSINE", "params": {"nprobe": 10}} + for k in range(nq): + for i in range(len(vector_name_list)): + search_res_dict = {} + search_res_dict_array = [] + vectors_search = vectors[k] + # 5. search to get the base line of hybrid_search + search_res = collection_w.search([vectors_search], vector_name_list[i], + single_search_param, default_limit, + default_search_exp, + check_task=CheckTasks.check_search_results, + check_items={"nq": 1, + "ids": insert_ids, + "limit": default_limit})[0] + ids = search_res[0].ids + distance_array = search_res[0].distances + for j in range(len(ids)): + search_res_dict[ids[j]] = distance_array[j] + search_res_dict_array.append(search_res_dict) + search_res_dict_array_nq.append(search_res_dict_array) + + # 6. calculate hybrid search base line + score_answer_nq = [] + for k in range(nq): + ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array_nq[k], weights, metrics) + score_answer_nq.append(score_answer) + # 7. hybrid search + output_fields = [default_int64_field_name] + hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, + output_fields=output_fields, + check_task=CheckTasks.check_search_results, + check_items={"nq": nq, + "ids": insert_ids, + "limit": default_limit})[0] + # 8. compare results through the re-calculated distances + for k in range(len(score_answer_nq)): + for i in range(len(score_answer_nq[k][:default_limit])): + assert score_answer_nq[k][i] - hybrid_res[k].distances[i] < hybrid_search_epsilon + + @pytest.mark.tags(CaseLabel.L2) + @pytest.mark.parametrize("primary_field", [ct.default_int64_field_name, ct.default_string_field_name]) + def test_hybrid_search_with_output_fields_all_fields(self, nq, dim, auto_id, is_flush, enable_dynamic_field, + primary_field, vector_data_type): + """ + target: test hybrid search normal case + method: create connection, collection, insert and search + expected: hybrid search successfully with limit(topK) + """ + # 1. initialize collection with data + nq = 10 + multiple_dim_array = [dim, dim] + collection_w, _, _, insert_ids, time_stamp = \ + self.init_collection_general(prefix, True, auto_id=auto_id, dim=dim, is_flush=is_flush, + primary_field=primary_field, + enable_dynamic_field=enable_dynamic_field, + multiple_dim_array=multiple_dim_array, + vector_data_type=vector_data_type)[0:5] + # 2. extract vector field name + vector_name_list = cf.extract_vector_field_name_list(collection_w) + vector_name_list.append(ct.default_float_vec_field_name) + # 3. prepare search params + req_list = [] + weights = [0.2, 0.3, 0.5] metrics = [] + search_res_dict_array = [] + search_res_dict_array_nq = [] + vectors = cf.gen_vectors_based_on_vector_type(nq, dim, vector_data_type) + + # get hybrid search req list for i in range(len(vector_name_list)): - vectors = [[random.random() for _ in range(default_dim)] for _ in range(1)] - search_res_dict = {} search_param = { "data": vectors, "anns_field": vector_name_list[i], - "param": {"metric_type": "COSINE", "offset": 0}, + "param": {"metric_type": "COSINE"}, "limit": default_limit, "expr": "int64 > 0"} req = AnnSearchRequest(**search_param) req_list.append(req) metrics.append("COSINE") - # search to get the base line of hybrid_search - search_res = collection_w.search(vectors[:1], vector_name_list[i], - default_search_params, default_limit, + + # get the result of search with the same params of the following hybrid search + single_search_param = {"metric_type": "COSINE", "params": {"nprobe": 10}} + for k in range(nq): + for i in range(len(vector_name_list)): + search_res_dict = {} + search_res_dict_array = [] + vectors_search = vectors[k] + # 5. search to get the base line of hybrid_search + search_res = collection_w.search([vectors_search], vector_name_list[i], + single_search_param, default_limit, default_search_exp, check_task=CheckTasks.check_search_results, check_items={"nq": 1, "ids": insert_ids, "limit": default_limit})[0] - ids = search_res[0].ids - distance_array = search_res[0].distances - for j in range(len(ids)): - search_res_dict[ids[j]] = distance_array[j] - search_res_dict_array.append(search_res_dict) - # 4. calculate hybrid search base line - ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array, weights, metrics) - # 5. hybrid search + ids = search_res[0].ids + distance_array = search_res[0].distances + for j in range(len(ids)): + search_res_dict[ids[j]] = distance_array[j] + search_res_dict_array.append(search_res_dict) + search_res_dict_array_nq.append(search_res_dict_array) + + # 6. calculate hybrid search base line + score_answer_nq = [] + for k in range(nq): + ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array_nq[k], weights, metrics) + score_answer_nq.append(score_answer) + # 7. hybrid search output_fields = [default_int64_field_name, default_float_field_name, default_string_field_name, default_json_field_name] output_fields = output_fields + vector_name_list hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, output_fields=output_fields, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, - "limit": default_limit, - "output_fields": output_fields})[0] - # 6. compare results through the re-calculated distances - for i in range(len(score_answer[:default_limit])): - delta = math.fabs(score_answer[i] - hybrid_res[0].distances[i]) - assert delta < hybrid_search_epsilon + "limit": default_limit})[0] + # 8. compare results through the re-calculated distances + for k in range(len(score_answer_nq)): + for i in range(len(score_answer_nq[k][:default_limit])): + assert score_answer_nq[k][i] - hybrid_res[k].distances[i] < hybrid_search_epsilon @pytest.mark.tags(CaseLabel.L2) - def test_hybrid_search_with_output_fields_all_fields_wildcard(self): + @pytest.mark.parametrize("primary_field", [ct.default_int64_field_name, ct.default_string_field_name]) + def test_hybrid_search_with_output_fields_all_fields(self, nq, dim, auto_id, is_flush, enable_dynamic_field, + primary_field, vector_data_type): """ target: test hybrid search normal case method: create connection, collection, insert and search expected: hybrid search successfully with limit(topK) """ # 1. initialize collection with data + nq = 10 + multiple_dim_array = [dim, dim] collection_w, _, _, insert_ids, time_stamp = \ - self.init_collection_general(prefix, True, multiple_dim_array=[default_dim, default_dim])[0:5] + self.init_collection_general(prefix, True, auto_id=auto_id, dim=dim, is_flush=is_flush, + primary_field=primary_field, + enable_dynamic_field=enable_dynamic_field, + multiple_dim_array=multiple_dim_array, + vector_data_type=vector_data_type)[0:5] # 2. extract vector field name vector_name_list = cf.extract_vector_field_name_list(collection_w) vector_name_list.append(ct.default_float_vec_field_name) # 3. prepare search params req_list = [] weights = [0.2, 0.3, 0.5] - search_res_dict_array = [] metrics = [] + search_res_dict_array = [] + search_res_dict_array_nq = [] + vectors = cf.gen_vectors_based_on_vector_type(nq, dim, vector_data_type) + + # get hybrid search req list for i in range(len(vector_name_list)): - vectors = [[random.random() for _ in range(default_dim)] for _ in range(1)] - search_res_dict = {} search_param = { "data": vectors, "anns_field": vector_name_list[i], - "param": {"metric_type": "COSINE", "offset": 0}, + "param": {"metric_type": "COSINE"}, "limit": default_limit, "expr": "int64 > 0"} req = AnnSearchRequest(**search_param) req_list.append(req) metrics.append("COSINE") - # search to get the base line of hybrid_search - search_res = collection_w.search(vectors[:1], vector_name_list[i], - default_search_params, default_limit, + + # get the result of search with the same params of the following hybrid search + single_search_param = {"metric_type": "COSINE", "params": {"nprobe": 10}} + for k in range(nq): + for i in range(len(vector_name_list)): + search_res_dict = {} + search_res_dict_array = [] + vectors_search = vectors[k] + # 5. search to get the base line of hybrid_search + search_res = collection_w.search([vectors_search], vector_name_list[i], + single_search_param, default_limit, default_search_exp, check_task=CheckTasks.check_search_results, check_items={"nq": 1, "ids": insert_ids, "limit": default_limit})[0] - ids = search_res[0].ids - distance_array = search_res[0].distances - for j in range(len(ids)): - search_res_dict[ids[j]] = distance_array[j] - search_res_dict_array.append(search_res_dict) - # 4. calculate hybrid search base line - ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array, weights, metrics) - # 5. hybrid search - output_fields = [default_int64_field_name, default_float_field_name, default_string_field_name, - default_json_field_name] - output_fields = output_fields + vector_name_list + ids = search_res[0].ids + distance_array = search_res[0].distances + for j in range(len(ids)): + search_res_dict[ids[j]] = distance_array[j] + search_res_dict_array.append(search_res_dict) + search_res_dict_array_nq.append(search_res_dict_array) + + # 6. calculate hybrid search base line + score_answer_nq = [] + for k in range(nq): + ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array_nq[k], weights, metrics) + score_answer_nq.append(score_answer) + # 7. hybrid search hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, - output_fields = ["*"], + output_fields= ["*"], check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, - "limit": default_limit, - "output_fields": output_fields})[0] - # 6. compare results through the re-calculated distances - for i in range(len(score_answer[:default_limit])): - delta = math.fabs(score_answer[i] - hybrid_res[0].distances[i]) - assert delta < hybrid_search_epsilon + "limit": default_limit})[0] + # 8. compare results through the re-calculated distances + for k in range(len(score_answer_nq)): + for i in range(len(score_answer_nq[k][:default_limit])): + assert score_answer_nq[k][i] - hybrid_res[k].distances[i] < hybrid_search_epsilon @pytest.mark.tags(CaseLabel.L2) @pytest.mark.parametrize("output_fields", [[default_search_field], [default_search_field, default_int64_field_name]]) @pytest.mark.parametrize("primary_field", [ct.default_int64_field_name, ct.default_string_field_name]) - def test_hybrid_search_with_output_fields_sync_async(self, primary_field, output_fields, _async): + def test_hybrid_search_with_output_fields_sync_async(self, nq, primary_field, output_fields, _async): """ target: test hybrid search normal case method: create connection, collection, insert and search expected: hybrid search successfully with limit(topK) """ # 1. initialize collection with data + multiple_dim_array = [default_dim, default_dim] collection_w, _, _, insert_ids, time_stamp = \ - self.init_collection_general(prefix, True, primary_field=primary_field, - multiple_dim_array=[default_dim, default_dim])[0:5] + self.init_collection_general(prefix, True, dim=default_dim, + primary_field=primary_field, + multiple_dim_array=multiple_dim_array)[0:5] # 2. extract vector field name vector_name_list = cf.extract_vector_field_name_list(collection_w) vector_name_list.append(ct.default_float_vec_field_name) # 3. prepare search params req_list = [] weights = [0.2, 0.3, 0.5] - search_res_dict_array = [] metrics = [] + search_res_dict_array = [] + search_res_dict_array_nq = [] + vectors = cf.gen_vectors_based_on_vector_type(nq, default_dim, "FLOAT_VECTOR") + + # get hybrid search req list for i in range(len(vector_name_list)): - vectors = [[random.random() for _ in range(default_dim)] for _ in range(1)] - search_res_dict = {} search_param = { "data": vectors, "anns_field": vector_name_list[i], - "param": {"metric_type": "COSINE", "offset": 0}, + "param": {"metric_type": "COSINE"}, "limit": default_limit, "expr": "int64 > 0"} req = AnnSearchRequest(**search_param) req_list.append(req) metrics.append("COSINE") - # search to get the base line of hybrid_search - search_res = collection_w.search(vectors[:1], vector_name_list[i], - default_search_params, default_limit, + + # get the result of search with the same params of the following hybrid search + single_search_param = {"metric_type": "COSINE", "params": {"nprobe": 10}} + for k in range(nq): + for i in range(len(vector_name_list)): + search_res_dict = {} + search_res_dict_array = [] + vectors_search = vectors[k] + # 5. search to get the base line of hybrid_search + search_res = collection_w.search([vectors_search], vector_name_list[i], + single_search_param, default_limit, default_search_exp, - _async = _async, + _async=_async, check_task=CheckTasks.check_search_results, check_items={"nq": 1, "ids": insert_ids, - "limit": default_limit, - "_async": _async})[0] - if _async: - search_res.done() - search_res = search_res.result() - ids = search_res[0].ids - distance_array = search_res[0].distances - for j in range(len(ids)): - search_res_dict[ids[j]] = distance_array[j] - search_res_dict_array.append(search_res_dict) - # 4. calculate hybrid search base line - ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array, weights, metrics) - # 5. hybrid search - hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, _async = _async, - output_fields = output_fields, + "limit": default_limit})[0] + if _async: + search_res.done() + search_res = search_res.result() + ids = search_res[0].ids + distance_array = search_res[0].distances + for j in range(len(ids)): + search_res_dict[ids[j]] = distance_array[j] + search_res_dict_array.append(search_res_dict) + search_res_dict_array_nq.append(search_res_dict_array) + + # 6. calculate hybrid search base line + score_answer_nq = [] + for k in range(nq): + ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array_nq[k], weights, metrics) + score_answer_nq.append(score_answer) + # 7. hybrid search + hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, + output_fields= output_fields, + _async=_async, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, - "limit": default_limit, - "output_fields": output_fields, - "_async": _async})[0] + "limit": default_limit})[0] if _async: hybrid_res.done() hybrid_res = hybrid_res.result() - # 6. compare results through the re-calculated distances - for i in range(len(score_answer[:default_limit])): - delta = math.fabs(score_answer[i] - hybrid_res[0].distances[i]) - assert delta < hybrid_search_epsilon + # 8. compare results through the re-calculated distances + for k in range(len(score_answer_nq)): + for i in range(len(score_answer_nq[k][:default_limit])): + assert score_answer_nq[k][i] - hybrid_res[k].distances[i] < hybrid_search_epsilon @pytest.mark.tags(CaseLabel.L2) @pytest.mark.parametrize("rerank", [RRFRanker(), WeightedRanker(0.1, 0.9, 1)]) @@ -12216,7 +12451,7 @@ def test_hybrid_search_offset_both_inside_outside_params(self, rerank): @pytest.mark.tags(CaseLabel.L2) @pytest.mark.parametrize("limit", [1, 100, 16384]) @pytest.mark.parametrize("primary_field", [ct.default_int64_field_name, ct.default_string_field_name]) - def test_hybrid_search_is_partition_key(self, primary_field, limit): + def test_hybrid_search_is_partition_key(self, nq, primary_field, limit, vector_data_type): """ target: test hybrid search with different valid limit and round decimal method: create connection, collection, insert and search @@ -12226,6 +12461,7 @@ def test_hybrid_search_is_partition_key(self, primary_field, limit): collection_w, _, _, insert_ids, time_stamp = \ self.init_collection_general(prefix, True, primary_field=primary_field, multiple_dim_array=[default_dim, default_dim], + vector_data_type = vector_data_type, is_partition_key=ct.default_float_field_name)[0:5] # 2. extract vector field name vector_name_list = cf.extract_vector_field_name_list(collection_w) @@ -12233,51 +12469,63 @@ def test_hybrid_search_is_partition_key(self, primary_field, limit): # 3. prepare search params req_list = [] weights = [0.2, 0.3, 0.5] - search_res_dict_array = [] - if limit > default_nb: - limit = default_limit metrics = [] + search_res_dict_array = [] + search_res_dict_array_nq = [] + vectors = cf.gen_vectors_based_on_vector_type(nq, default_dim, vector_data_type) + + # get hybrid search req list for i in range(len(vector_name_list)): - vectors = [[random.random() for _ in range(default_dim)] for _ in range(1)] - search_res_dict = {} search_param = { "data": vectors, "anns_field": vector_name_list[i], - "param": {"metric_type": "COSINE", "offset": 0}, - "limit": limit, + "param": {"metric_type": "COSINE"}, + "limit": default_limit, "expr": "int64 > 0"} req = AnnSearchRequest(**search_param) req_list.append(req) metrics.append("COSINE") - # search to get the base line of hybrid_search - search_res = collection_w.search(vectors[:1], vector_name_list[i], - default_search_params, limit, - default_search_exp, round_decimal= 5, - check_task=CheckTasks.check_search_results, - check_items={"nq": 1, - "ids": insert_ids, - "limit": limit})[0] - ids = search_res[0].ids - distance_array = search_res[0].distances - for j in range(len(ids)): - search_res_dict[ids[j]] = distance_array[j] - search_res_dict_array.append(search_res_dict) - # 4. calculate hybrid search base line - ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array, weights, metrics, 5) - # 5. hybrid search - hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), limit, - round_decimal=5, + + # get the result of search with the same params of the following hybrid search + single_search_param = {"metric_type": "COSINE", "params": {"nprobe": 10}} + for k in range(nq): + for i in range(len(vector_name_list)): + search_res_dict = {} + search_res_dict_array = [] + vectors_search = vectors[k] + # 5. search to get the base line of hybrid_search + search_res = collection_w.search([vectors_search], vector_name_list[i], + single_search_param, default_limit, + default_search_exp, + check_task=CheckTasks.check_search_results, + check_items={"nq": 1, + "ids": insert_ids, + "limit": default_limit})[0] + ids = search_res[0].ids + distance_array = search_res[0].distances + for j in range(len(ids)): + search_res_dict[ids[j]] = distance_array[j] + search_res_dict_array.append(search_res_dict) + search_res_dict_array_nq.append(search_res_dict_array) + + # 6. calculate hybrid search base line + score_answer_nq = [] + for k in range(nq): + ids_answer, score_answer = cf.get_hybrid_search_base_results(search_res_dict_array_nq[k], weights, metrics) + score_answer_nq.append(score_answer) + # 7. hybrid search + hybrid_res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), default_limit, check_task=CheckTasks.check_search_results, - check_items={"nq": 1, + check_items={"nq": nq, "ids": insert_ids, - "limit": limit})[0] - # 6. compare results through the re-calculated distances - for i in range(len(score_answer[:limit])): - delta = math.fabs(score_answer[i] - hybrid_res[0].distances[i]) - assert delta < hybrid_search_epsilon + "limit": default_limit})[0] + # 8. compare results through the re-calculated distances + for k in range(len(score_answer_nq)): + for i in range(len(score_answer_nq[k][:default_limit])): + assert score_answer_nq[k][i] - hybrid_res[k].distances[i] < hybrid_search_epsilon @pytest.mark.tags(CaseLabel.L1) - def test_hybrid_search_result_L2_order(self): + def test_hybrid_search_result_L2_order(self, nq): """ target: test hybrid search result having correct order for L2 distance method: create connection, collection, insert and search @@ -12299,7 +12547,7 @@ def test_hybrid_search_result_L2_order(self): req_list = [] weights = [0.2, 0.3, 0.5] for i in range(len(vector_name_list)): - vectors = [[random.random() for _ in range(default_dim)] for _ in range(1)] + vectors = [[random.random() for _ in range(default_dim)] for _ in range(nq)] search_param = { "data": vectors, "anns_field": vector_name_list[i], @@ -12310,11 +12558,12 @@ def test_hybrid_search_result_L2_order(self): req_list.append(req) # 4. hybrid search res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), 10)[0] - is_sorted_descend = lambda lst: all(lst[i] >= lst[i+1] for i in range(len(lst)-1)) - assert is_sorted_descend(res[0].distances) + is_sorted_descend = lambda lst: all(lst[i] >= lst[i + 1] for i in range(len(lst) - 1)) + for i in range(nq): + assert is_sorted_descend(res[i].distances) @pytest.mark.tags(CaseLabel.L1) - def test_hybrid_search_result_order(self): + def test_hybrid_search_result_order(self, nq): """ target: test hybrid search result having correct order for cosine distance method: create connection, collection, insert and search @@ -12330,7 +12579,7 @@ def test_hybrid_search_result_order(self): req_list = [] weights = [0.2, 0.3, 0.5] for i in range(len(vector_name_list)): - vectors = [[random.random() for _ in range(default_dim)] for _ in range(1)] + vectors = [[random.random() for _ in range(default_dim)] for _ in range(nq)] search_param = { "data": vectors, "anns_field": vector_name_list[i], @@ -12342,7 +12591,8 @@ def test_hybrid_search_result_order(self): # 4. hybrid search res = collection_w.hybrid_search(req_list, WeightedRanker(*weights), 10)[0] is_sorted_descend = lambda lst: all(lst[i] >= lst[i+1] for i in range(len(lst)-1)) - assert is_sorted_descend(res[0].distances) + for i in range(nq): + assert is_sorted_descend(res[i].distances) class TestSparseSearch(TestcaseBase): @@ -12398,7 +12648,7 @@ def test_sparse_index_dim(self, index, index_params, dim): "limit": default_limit}) @pytest.mark.tags(CaseLabel.L2) - @pytest.mark.xfail(reason="issue #31485") + @pytest.mark.skip(reason="issue #31485") @pytest.mark.parametrize("index, index_params", zip(ct.all_index_types[9:11], ct.default_index_params[9:11])) def test_sparse_index_enable_mmap_search(self, index, index_params): """