This is an automated email from the ASF dual-hosted git repository. jiahuili430 pushed a commit to branch add-retry-until-for-search_test.exs in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 142670ea0a27aabe940e588ace2ec8f4700e4136 Author: Jiahui Li <[email protected]> AuthorDate: Sat Apr 11 15:12:27 2026 -0500 Add `retry_until()` to fix flaky elixir search tests When running Elixir search tests in Clouseau GitHub Actions CI, some tests got `(KeyError) key :status_code not found in: %HTTPotion.ErrorResponse{message: "req_timedout"}`. Adding `retry_until()` to mitigate this. --- test/elixir/test/partition_search_test.exs | 96 ++++++++++++++------- test/elixir/test/search_test.exs | 134 ++++++++++++++++++----------- 2 files changed, 149 insertions(+), 81 deletions(-) diff --git a/test/elixir/test/partition_search_test.exs b/test/elixir/test/partition_search_test.exs index 9310e701d..973aadeea 100644 --- a/test/elixir/test/partition_search_test.exs +++ b/test/elixir/test/partition_search_test.exs @@ -56,14 +56,18 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_partition/foo/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 200, "Fail to do partitioned search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 200, "Fail to do partitioned search.") + end) ids = get_ids(resp) assert ids == ["foo:10", "foo:2", "foo:4", "foo:6", "foo:8"] url = "/#{db_name}/_partition/bar/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 200, "Fail to do partitioned search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 200, "Fail to do partitioned search.") + end) ids = get_ids(resp) assert ids == ["bar:1", "bar:3", "bar:5", "bar:7", "bar:9"] end @@ -75,8 +79,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_partition/foo/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 200, "Fail to do partitioned search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 200, "Fail to do partitioned search.") + end) ids = get_ids(resp) assert ids == ["foo:10", "foo:2", "foo:4", "foo:6", "foo:8"] end @@ -95,18 +101,24 @@ defmodule PartitionSearchTest do %{:body => %{"bookmark" => bookmark}} = resp - resp = Couch.get(url, query: %{q: "some:field", limit: 3, bookmark: bookmark}) - assert_on_status(resp, 200, "Fail to do partitioned search with a bookmark.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field", limit: 3, bookmark: bookmark}) + assert_on_status(resp, 200, "Fail to do partitioned search with a bookmark.") + end) ids = get_ids(resp) assert ids == ["foo:6", "foo:8"] - resp = Couch.get(url, query: %{q: "some:field", limit: 2000, bookmark: bookmark}) - assert_on_status(resp, 200, "Fail to do partition search with an upper bound on the limit.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field", limit: 2000, bookmark: bookmark}) + assert_on_status(resp, 200, "Fail to do partition search with an upper bound on the limit.") + end) ids = get_ids(resp) assert ids == ["foo:6", "foo:8"] - resp = Couch.get(url, query: %{q: "some:field", limit: 2001, bookmark: bookmark}) - assert_on_status(resp, 400, "Should fail to do partition search with over limit.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field", limit: 2001, bookmark: bookmark}) + assert_on_status(resp, 400, "Should fail to do partition search with over limit.") + end) end @tag :with_db @@ -116,8 +128,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/library/_search/books" - resp = Couch.post(url, body: %{:q => "some:field", :limit => 1}) - assert_on_status(resp, 200, "Fail to do POST for non-partitioned db with limit.") + retry_until(fn -> + resp = Couch.post(url, body: %{:q => "some:field", :limit => 1}) + assert_on_status(resp, 200, "Fail to do POST for non-partitioned db with limit.") + end) end @tag :with_partitioned_db @@ -127,8 +141,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_partition/foo/_design/library/_search/books" - resp = Couch.post(url, body: %{:q => "some:field", :limit => 1}) - assert_on_status(resp, 200, "Fail to do POST for partitioned db with limit.") + retry_until(fn -> + resp = Couch.post(url, body: %{:q => "some:field", :limit => 1}) + assert_on_status(resp, 200, "Fail to do POST for partitioned db with limit.") + end) end @tag :with_partitioned_db @@ -138,8 +154,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 400, "Expected a failure to do a global query on partitioned view.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 400, "Expected a failure to do a global query on partitioned view.") + end) %{:body => %{"reason" => reason}} = resp assert Regex.match?(~r/mandatory for queries to this index./, reason) end @@ -151,8 +169,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name, options: %{partitioned: false}) url = "/#{db_name}/_partition/foo/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 400, "Expected a failure to do a query with a global search ddoc.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 400, "Expected a failure to do a query with a global search ddoc.") + end) %{:body => %{"reason" => reason}} = resp assert reason == "`partition` not supported on this index" end @@ -164,8 +184,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 200, "Failed to search on non-partitioned dbs.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 200, "Failed to search on non-partitioned dbs.") + end) ids = get_ids(resp) assert Enum.sort(ids) == Enum.sort(["bar:1", "bar:5", "bar:9", "foo:2", "bar:3", "foo:4", "foo:6", "bar:7", "foo:8", "foo:10"]) end @@ -177,8 +199,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 200, "Failed to search on non-partitioned dbs without the limit.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 200, "Failed to search on non-partitioned dbs without the limit.") + end) ids = get_ids(resp) assert Enum.sort(ids) == Enum.sort(["bar:1", "bar:5", "bar:9", "foo:2", "bar:3", "foo:4", "foo:6", "bar:7", "foo:8", "foo:10"]) end @@ -192,13 +216,17 @@ defmodule PartitionSearchTest do url = "/#{db_name}/_design/library/_search/books" # score order varies by Lucene version, so captured this order first. - resp = Couch.get(url, query: %{q: "some:field"}) - assert_on_status(resp, 200, "Failed to search on non-partitioned dbs without the limit.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field"}) + assert_on_status(resp, 200, "Failed to search on non-partitioned dbs without the limit.") + end) expected_ids = get_ids(resp) # Assert that the limit:3 results are the first 3 results from the unlimited search - resp = Couch.get(url, query: %{q: "some:field", limit: 3}) - assert_on_status(resp, 200, "Failed to search on non-partitioned dbs with the limit.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field", limit: 3}) + assert_on_status(resp, 200, "Failed to search on non-partitioned dbs with the limit.") + end) assert List.starts_with?(expected_ids, get_ids(resp)) end @@ -209,8 +237,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/library/_search/books" - resp = Couch.get(url, query: %{q: "some:field", limit: 201}) - assert_on_status(resp, 400, "Expected a failure on non-partitioned dbs with over limit.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "some:field", limit: 201}) + assert_on_status(resp, 400, "Expected a failure on non-partitioned dbs with over limit.") + end) end @tag :with_partitioned_db @@ -220,8 +250,10 @@ defmodule PartitionSearchTest do create_ddoc(db_name) url = "/#{db_name}/_partition/foo/_design/library/_search/books" - resp = Couch.post(url, body: %{q: "some:field", partition: "bar"}) - assert_on_status(resp, 400, "Expected a failure on conflicting partition values.") + retry_until(fn -> + resp = Couch.post(url, body: %{q: "some:field", partition: "bar"}) + assert_on_status(resp, 400, "Expected a failure on conflicting partition values.") + end) end @tag :with_partitioned_db diff --git a/test/elixir/test/search_test.exs b/test/elixir/test/search_test.exs index edf6858cf..061ee8da1 100644 --- a/test/elixir/test/search_test.exs +++ b/test/elixir/test/search_test.exs @@ -9,19 +9,18 @@ defmodule SearchTest do """ def create_search_docs(db_name) do - resp = Couch.post("/#{db_name}/_bulk_docs", - headers: ["Content-Type": "application/json"], - body: %{:docs => [ - %{"item" => "apple", "place" => "kitchen", "state" => "new", "price" => 0.99}, - %{"item" => "banana", "place" => "kitchen", "state" => "new", "price" => 1.49}, - %{"item" => "carrot", "place" => "kitchen", "state" => "old", "price" => 0.75}, - %{"item" => "date", "place" => "lobby", "state" => "unknown", "price" => 1.25}, - ]} - ) - assert resp.status_code in [201, 202], - "Cannot create search docs. " <> - "Expected one of [201, 202], got: #{resp.status_code}, body: #{inspect resp.body}" - + resp = Couch.post("/#{db_name}/_bulk_docs", + headers: ["Content-Type": "application/json"], + body: %{:docs => [ + %{"item" => "apple", "place" => "kitchen", "state" => "new", "price" => 0.99}, + %{"item" => "banana", "place" => "kitchen", "state" => "new", "price" => 1.49}, + %{"item" => "carrot", "place" => "kitchen", "state" => "old", "price" => 0.75}, + %{"item" => "date", "place" => "lobby", "state" => "unknown", "price" => 1.25}, + ]} + ) + assert resp.status_code in [201, 202], + "Cannot create search docs. " <> + "Expected one of [201, 202], got: #{resp.status_code}, body: #{inspect resp.body}" end def create_ddoc(db_name, opts \\ %{}) do @@ -74,8 +73,11 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.get(url, query: %{q: "*:*", include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) + ids = get_items(resp) assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot", "date"]) end @@ -87,8 +89,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode(["place", "kitchen"]), include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode(["place", "kitchen"]), include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot"]) @@ -101,8 +105,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode(["state", "new", "unknown"]), include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode(["state", "new", "unknown"]), include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == Enum.sort(["apple", "banana", "date"]) @@ -115,8 +121,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode([["state", "old"], ["item", "apple"]]), include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", drilldown: :jiffy.encode([["state", "old"], ["item", "apple"]]), include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == [] @@ -129,8 +137,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits?q=*:*&drilldown=[\"state\",\"old\"]&drilldown=[\"item\",\"apple\"]&include_docs=true" - resp = Couch.get(url) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == [] @@ -144,8 +154,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.post(url, body: %{q: "*:*", include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.post(url, body: %{q: "*:*", include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot", "date"]) @@ -158,8 +170,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.post(url, body: %{query: "*:*", drilldown: ["place", "kitchen"], include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.post(url, body: %{query: "*:*", drilldown: ["place", "kitchen"], include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == Enum.sort(["apple", "banana", "carrot"]) @@ -172,8 +186,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.post(url, body: %{query: "*:*", drilldown: ["state", "new", "unknown"], include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.post(url, body: %{query: "*:*", drilldown: ["state", "new", "unknown"], include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == Enum.sort(["apple", "banana", "date"]) @@ -186,8 +202,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.post(url, body: %{q: "*:*", drilldown: [["state", "old"], ["item", "apple"]], include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.post(url, body: %{q: "*:*", drilldown: [["state", "old"], ["item", "apple"]], include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == [] @@ -200,8 +218,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.post(url, body: %{q: "*:*", drilldown: [["place", "kitchen"], ["state", "new"], ["item", "apple"]], include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.post(url, body: %{q: "*:*", drilldown: [["place", "kitchen"], ["state", "new"], ["item", "apple"]], include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == ["apple"] @@ -214,8 +234,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.post(url, body: %{q: "*:*", drilldown: [["state", "old", "new"], ["item", "apple"]], include_docs: true}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.post(url, body: %{q: "*:*", drilldown: [["state", "old", "new"], ["item", "apple"]], include_docs: true}) + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == ["apple"] @@ -228,8 +250,10 @@ defmodule SearchTest do create_ddoc(db_name) url = "/#{db_name}/_design/inventory/_search/fruits" - resp = Couch.post(url, body: "{\"include_docs\": true, \"q\": \"*:*\", \"drilldown\": [\"state\", \"old\"], \"drilldown\": [\"item\", \"apple\"]}") - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.post(url, body: "{\"include_docs\": true, \"q\": \"*:*\", \"drilldown\": [\"state\", \"old\"], \"drilldown\": [\"item\", \"apple\"]}") + assert_on_status(resp, 200, "Fail to do search.") + end) ids = get_items(resp) assert Enum.sort(ids) == ["apple"] @@ -242,8 +266,10 @@ defmodule SearchTest do create_ddoc(db_name) create_invalid_ddoc(db_name) - resp = Couch.post("/#{db_name}/_search_cleanup") - assert_on_status(resp, [201, 202], "Fail to do a _search_cleanup.") + retry_until(fn -> + resp = Couch.post("/#{db_name}/_search_cleanup") + assert_on_status(resp, [201, 202], "Fail to do a _search_cleanup.") + end) end @tag :with_db @@ -254,8 +280,10 @@ defmodule SearchTest do url = "/#{db_name}/_design/inventory/_search/fruits" counts = ["place"] - resp = Couch.get(url, query: %{q: "*:*", limit: 0, counts: :jiffy.encode(counts)}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", limit: 0, counts: :jiffy.encode(counts)}) + assert_on_status(resp, 200, "Fail to do search.") + end) %{:body => %{"counts" => counts}} = resp assert counts == %{"place" => %{"kitchen" => 3, "lobby" => 1}} @@ -269,8 +297,10 @@ defmodule SearchTest do url = "/#{db_name}/_design/inventory/_search/fruits" counts = ["place"] - resp = Couch.get(url, query: %{q: "item:tomato", limit: 0, counts: :jiffy.encode(counts)}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "item:tomato", limit: 0, counts: :jiffy.encode(counts)}) + assert_on_status(resp, 200, "Fail to do search.") + end) %{:body => %{"counts" => counts}} = resp assert counts == %{"place" => %{}} @@ -284,8 +314,10 @@ defmodule SearchTest do url = "/#{db_name}/_design/inventory/_search/fruits" ranges = %{"price" => %{"cheap" => "[0 TO 0.99]", "expensive" => "[1.00 TO Infinity]"}} - resp = Couch.get(url, query: %{q: "*:*", limit: 0, ranges: :jiffy.encode(ranges)}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", limit: 0, ranges: :jiffy.encode(ranges)}) + assert_on_status(resp, 200, "Fail to do search.") + end) %{:body => %{"ranges" => ranges}} = resp assert ranges == %{"price" => %{"cheap" => 2, "expensive" => 2}} @@ -299,8 +331,10 @@ defmodule SearchTest do url = "/#{db_name}/_design/inventory/_search/fruits" ranges = %{"price" => %{}} - resp = Couch.get(url, query: %{q: "*:*", limit: 0, ranges: :jiffy.encode(ranges)}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", limit: 0, ranges: :jiffy.encode(ranges)}) + assert_on_status(resp, 200, "Fail to do search.") + end) %{:body => %{"ranges" => ranges}} = resp assert ranges == %{"price" => %{}} @@ -314,8 +348,10 @@ defmodule SearchTest do url = "/#{db_name}/_design/inventory/_search/fruits" ranges = %{"price" => %{}} - resp = Couch.get(url, query: %{q: "*:*", group_field: "state"}) - assert_on_status(resp, 200, "Fail to do search.") + retry_until(fn -> + resp = Couch.get(url, query: %{q: "*:*", group_field: "state"}) + assert_on_status(resp, 200, "Fail to do search.") + end) %{:body => %{"groups" => groups}} = resp assert length(groups) == 3
