diff options
author | Ivan Mironov <ivan.mironov@gmail.com> | 2018-12-07 16:47:21 -0500 |
---|---|---|
committer | Joan Touzet <wohali@users.noreply.github.com> | 2018-12-07 16:47:21 -0500 |
commit | 77eb982bd9cd078e45e4a424e7ffa46f7073bfa5 (patch) | |
tree | 8d4e398d3cc5b246fe3dd9c37f56de5d7463f4a1 | |
parent | f9354ea21d2f5161e0403fbded404506405a5d70 (diff) | |
download | couchdb-77eb982bd9cd078e45e4a424e7ffa46f7073bfa5.tar.gz |
Refactor elixir test suite initialization (#1779)
* Move CouchTestCase to separate file
* Move setup context functions to separate module
-rw-r--r-- | test/elixir/lib/couch/db_test.ex | 240 | ||||
-rw-r--r-- | test/elixir/mix.exs | 5 | ||||
-rw-r--r-- | test/elixir/test/support/couch_test_case.ex | 27 | ||||
-rw-r--r-- | test/elixir/test/test_helper.exs | 262 |
4 files changed, 272 insertions, 262 deletions
diff --git a/test/elixir/lib/couch/db_test.ex b/test/elixir/lib/couch/db_test.ex new file mode 100644 index 000000000..538a99877 --- /dev/null +++ b/test/elixir/lib/couch/db_test.ex @@ -0,0 +1,240 @@ +defmodule Couch.DBTest do + import ExUnit.Callbacks, only: [on_exit: 1] + import ExUnit.Assertions, only: [assert: 1, assert: 2] + + def set_db_context(context) do + context = + case context do + %{:with_db_name => true} -> + Map.put(context, :db_name, random_db_name()) + + %{:with_db_name => db_name} when is_binary(db_name) -> + Map.put(context, :db_name, db_name) + + %{:with_random_db => db_name} when is_binary(db_name) -> + context + |> Map.put(:db_name, random_db_name(db_name)) + |> Map.put(:with_db, true) + + %{:with_db => true} -> + Map.put(context, :db_name, random_db_name()) + + %{:with_db => db_name} when is_binary(db_name) -> + Map.put(context, :db_name, db_name) + + _ -> + context + end + + if Map.has_key?(context, :with_db) do + {:ok, _} = create_db(context[:db_name]) + on_exit(fn -> delete_db(context[:db_name]) end) + end + + context + end + + def set_config_context(context) do + if is_list(context[:config]) do + Enum.each(context[:config], fn cfg -> + set_config(cfg) + end) + end + + context + end + + def set_user_context(context) do + case Map.get(context, :user) do + nil -> + context + + user when is_list(user) -> + user = create_user(user) + + on_exit(fn -> + query = %{:rev => user["_rev"]} + resp = Couch.delete("/_users/#{user["_id"]}", query: query) + assert HTTPotion.Response.success?(resp) + end) + + context = Map.put(context, :user, user) + userinfo = user["name"] <> ":" <> user["password"] + Map.put(context, :userinfo, userinfo) + end + end + + def random_db_name do + random_db_name("random-test-db") + end + + def random_db_name(prefix) do + time = :erlang.monotonic_time() + umi = :erlang.unique_integer([:monotonic]) + "#{prefix}-#{time}-#{umi}" + end + + def set_config({section, key, value}) do + existing = set_config_raw(section, key, value) + + on_exit(fn -> + Enum.each(existing, fn {node, prev_value} -> + if prev_value != "" do + url = "/_node/#{node}/_config/#{section}/#{key}" + headers = ["X-Couch-Persist": "false"] + body = :jiffy.encode(prev_value) + resp = Couch.put(url, headers: headers, body: body) + assert resp.status_code == 200 + else + url = "/_node/#{node}/_config/#{section}/#{key}" + headers = ["X-Couch-Persist": "false"] + resp = Couch.delete(url, headers: headers) + assert resp.status_code == 200 + end + end) + end) + end + + def set_config_raw(section, key, value) do + resp = Couch.get("/_membership") + + Enum.map(resp.body["all_nodes"], fn node -> + url = "/_node/#{node}/_config/#{section}/#{key}" + headers = ["X-Couch-Persist": "false"] + body = :jiffy.encode(value) + resp = Couch.put(url, headers: headers, body: body) + assert resp.status_code == 200 + {node, resp.body} + end) + end + + def create_user(user) do + required = [:name, :password, :roles] + + Enum.each(required, fn key -> + assert Keyword.has_key?(user, key), "User missing key: #{key}" + end) + + name = Keyword.get(user, :name) + password = Keyword.get(user, :password) + roles = Keyword.get(user, :roles) + + assert is_binary(name), "User name must be a string" + assert is_binary(password), "User password must be a string" + assert is_list(roles), "Roles must be a list of strings" + + Enum.each(roles, fn role -> + assert is_binary(role), "Roles must be a list of strings" + end) + + user_doc = %{ + "_id" => "org.couchdb.user:" <> name, + "type" => "user", + "name" => name, + "roles" => roles, + "password" => password + } + + resp = Couch.get("/_users/#{user_doc["_id"]}") + + user_doc = + case resp.status_code do + 404 -> + user_doc + + sc when sc >= 200 and sc < 300 -> + Map.put(user_doc, "_rev", resp.body["_rev"]) + end + + resp = Couch.post("/_users", body: user_doc) + assert HTTPotion.Response.success?(resp) + assert resp.body["ok"] + Map.put(user_doc, "_rev", resp.body["rev"]) + end + + def create_db(db_name) do + resp = Couch.put("/#{db_name}") + assert resp.status_code == 201 + assert resp.body == %{"ok" => true} + {:ok, resp} + end + + def delete_db(db_name) do + resp = Couch.delete("/#{db_name}") + assert resp.status_code == 200 + assert resp.body == %{"ok" => true} + {:ok, resp} + end + + def create_doc(db_name, body) do + resp = Couch.post("/#{db_name}", body: body) + assert resp.status_code == 201 + assert resp.body["ok"] + {:ok, resp} + end + + def sample_doc_foo do + %{ + _id: "foo", + bar: "baz" + } + end + + # Generate range of docs with strings as keys + def make_docs(id_range) do + for id <- id_range, str_id = Integer.to_string(id) do + %{"_id" => str_id, "integer" => id, "string" => str_id} + end + end + + # Generate range of docs with atoms as keys, which are more + # idiomatic, and are encoded by jiffy to binaries + def create_docs(id_range) do + for id <- id_range, str_id = Integer.to_string(id) do + %{_id: str_id, integer: id, string: str_id} + end + end + + def retry_until(condition, sleep \\ 100, timeout \\ 5000) do + retry_until(condition, now(:ms), sleep, timeout) + end + + defp retry_until(condition, start, sleep, timeout) do + now = now(:ms) + + if now > start + timeout do + raise "timed out after #{now - start} ms" + else + try do + if condition.() do + :ok + else + raise ExUnit.AssertionError + end + rescue + ExUnit.AssertionError -> + :timer.sleep(sleep) + retry_until(condition, start, sleep, timeout) + end + end + end + + defp now(:ms) do + div(:erlang.system_time(), 1_000_000) + end + + @spec rev(map(), map()) :: map() + def rev(doc = %{_id: id}, %{"id" => id, "rev" => rev}) do + Map.put(doc, :_rev, rev) + end + + @spec rev([map()], [map()]) :: [map()] + def rev(docs, rows) when length(docs) == length(rows) do + for {doc, row} <- Enum.zip(docs, rows), do: rev(doc, row) + end + + def pretty_inspect(resp) do + opts = [pretty: true, width: 20, limit: :infinity, printable_limit: :infinity] + inspect(resp, opts) + end +end diff --git a/test/elixir/mix.exs b/test/elixir/mix.exs index 86f4c7aa0..68de5ce7a 100644 --- a/test/elixir/mix.exs +++ b/test/elixir/mix.exs @@ -6,6 +6,7 @@ defmodule Foo.Mixfile do app: :foo, version: "0.1.0", elixir: "~> 1.5", + elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps() ] @@ -18,6 +19,10 @@ defmodule Foo.Mixfile do ] end + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + # Run "mix help deps" to learn about dependencies. defp deps do [ diff --git a/test/elixir/test/support/couch_test_case.ex b/test/elixir/test/support/couch_test_case.ex new file mode 100644 index 000000000..02bee46af --- /dev/null +++ b/test/elixir/test/support/couch_test_case.ex @@ -0,0 +1,27 @@ +defmodule CouchTestCase do + use ExUnit.CaseTemplate + + using do + quote do + require Logger + use ExUnit.Case + + import Couch.DBTest + end + end + + setup context do + setup_funs = [ + &Couch.DBTest.set_db_context/1, + &Couch.DBTest.set_config_context/1, + &Couch.DBTest.set_user_context/1 + ] + + context = + Enum.reduce(setup_funs, context, fn setup_fun, acc -> + setup_fun.(acc) + end) + + {:ok, context} + end +end diff --git a/test/elixir/test/test_helper.exs b/test/elixir/test/test_helper.exs index e08229f82..33041fd02 100644 --- a/test/elixir/test/test_helper.exs +++ b/test/elixir/test/test_helper.exs @@ -1,264 +1,2 @@ ExUnit.configure(exclude: [pending: true]) ExUnit.start() - -defmodule CouchTestCase do - use ExUnit.Case - - defmacro __using__(_opts) do - quote do - require Logger - use ExUnit.Case - - setup context do - setup_funs = [ - &set_db_context/1, - &set_config_context/1, - &set_user_context/1 - ] - - context = - Enum.reduce(setup_funs, context, fn setup_fun, acc -> - setup_fun.(acc) - end) - - {:ok, context} - end - - def set_db_context(context) do - context = - case context do - %{:with_db_name => true} -> - Map.put(context, :db_name, random_db_name()) - - %{:with_db_name => db_name} when is_binary(db_name) -> - Map.put(context, :db_name, db_name) - - %{:with_random_db => db_name} when is_binary(db_name) -> - context - |> Map.put(:db_name, random_db_name(db_name)) - |> Map.put(:with_db, true) - - %{:with_db => true} -> - Map.put(context, :db_name, random_db_name()) - - %{:with_db => db_name} when is_binary(db_name) -> - Map.put(context, :db_name, db_name) - - _ -> - context - end - - if Map.has_key?(context, :with_db) do - {:ok, _} = create_db(context[:db_name]) - on_exit(fn -> delete_db(context[:db_name]) end) - end - - context - end - - def set_config_context(context) do - if is_list(context[:config]) do - Enum.each(context[:config], fn cfg -> - set_config(cfg) - end) - end - - context - end - - def set_user_context(context) do - case Map.get(context, :user) do - nil -> - context - - user when is_list(user) -> - user = create_user(user) - - on_exit(fn -> - query = %{:rev => user["_rev"]} - resp = Couch.delete("/_users/#{user["_id"]}", query: query) - assert HTTPotion.Response.success?(resp) - end) - - context = Map.put(context, :user, user) - userinfo = user["name"] <> ":" <> user["password"] - Map.put(context, :userinfo, userinfo) - end - end - - def random_db_name do - random_db_name("random-test-db") - end - - def random_db_name(prefix) do - time = :erlang.monotonic_time() - umi = :erlang.unique_integer([:monotonic]) - "#{prefix}-#{time}-#{umi}" - end - - def set_config({section, key, value}) do - existing = set_config_raw(section, key, value) - - on_exit(fn -> - Enum.each(existing, fn {node, prev_value} -> - if prev_value != "" do - url = "/_node/#{node}/_config/#{section}/#{key}" - headers = ["X-Couch-Persist": "false"] - body = :jiffy.encode(prev_value) - resp = Couch.put(url, headers: headers, body: body) - assert resp.status_code == 200 - else - url = "/_node/#{node}/_config/#{section}/#{key}" - headers = ["X-Couch-Persist": "false"] - resp = Couch.delete(url, headers: headers) - assert resp.status_code == 200 - end - end) - end) - end - - def set_config_raw(section, key, value) do - resp = Couch.get("/_membership") - - Enum.map(resp.body["all_nodes"], fn node -> - url = "/_node/#{node}/_config/#{section}/#{key}" - headers = ["X-Couch-Persist": "false"] - body = :jiffy.encode(value) - resp = Couch.put(url, headers: headers, body: body) - assert resp.status_code == 200 - {node, resp.body} - end) - end - - def create_user(user) do - required = [:name, :password, :roles] - - Enum.each(required, fn key -> - assert Keyword.has_key?(user, key), "User missing key: #{key}" - end) - - name = Keyword.get(user, :name) - password = Keyword.get(user, :password) - roles = Keyword.get(user, :roles) - - assert is_binary(name), "User name must be a string" - assert is_binary(password), "User password must be a string" - assert is_list(roles), "Roles must be a list of strings" - - Enum.each(roles, fn role -> - assert is_binary(role), "Roles must be a list of strings" - end) - - user_doc = %{ - "_id" => "org.couchdb.user:" <> name, - "type" => "user", - "name" => name, - "roles" => roles, - "password" => password - } - - resp = Couch.get("/_users/#{user_doc["_id"]}") - - user_doc = - case resp.status_code do - 404 -> - user_doc - - sc when sc >= 200 and sc < 300 -> - Map.put(user_doc, "_rev", resp.body["_rev"]) - end - - resp = Couch.post("/_users", body: user_doc) - assert HTTPotion.Response.success?(resp) - assert resp.body["ok"] - Map.put(user_doc, "_rev", resp.body["rev"]) - end - - def create_db(db_name) do - resp = Couch.put("/#{db_name}") - assert resp.status_code == 201 - assert resp.body == %{"ok" => true} - {:ok, resp} - end - - def delete_db(db_name) do - resp = Couch.delete("/#{db_name}") - assert resp.status_code == 200 - assert resp.body == %{"ok" => true} - {:ok, resp} - end - - def create_doc(db_name, body) do - resp = Couch.post("/#{db_name}", body: body) - assert resp.status_code == 201 - assert resp.body["ok"] - {:ok, resp} - end - - def sample_doc_foo do - %{ - _id: "foo", - bar: "baz" - } - end - - # Generate range of docs with strings as keys - def make_docs(id_range) do - for id <- id_range, str_id = Integer.to_string(id) do - %{"_id" => str_id, "integer" => id, "string" => str_id} - end - end - - # Generate range of docs with atoms as keys, which are more - # idiomatic, and are encoded by jiffy to binaries - def create_docs(id_range) do - for id <- id_range, str_id = Integer.to_string(id) do - %{_id: str_id, integer: id, string: str_id} - end - end - - def retry_until(condition, sleep \\ 100, timeout \\ 5000) do - retry_until(condition, now(:ms), sleep, timeout) - end - - defp retry_until(condition, start, sleep, timeout) do - now = now(:ms) - - if now > start + timeout do - raise "timed out after #{now - start} ms" - else - try do - if condition.() do - :ok - else - raise ExUnit.AssertionError - end - rescue - ExUnit.AssertionError -> - :timer.sleep(sleep) - retry_until(condition, start, sleep, timeout) - end - end - end - - defp now(:ms) do - div(:erlang.system_time(), 1_000_000) - end - - @spec rev(map(), map()) :: map() - def rev(doc = %{_id: id}, %{"id" => id, "rev" => rev}) do - Map.put(doc, :_rev, rev) - end - - @spec rev([map()], [map()]) :: [map()] - def rev(docs, rows) when length(docs) == length(rows) do - for {doc, row} <- Enum.zip(docs, rows), do: rev(doc, row) - end - - def pretty_inspect(resp) do - opts = [pretty: true, width: 20, limit: :infinity, printable_limit: :infinity] - inspect(resp, opts) - end - end - end -end |