{"id":18319895,"url":"https://github.com/bitwalker/ex_unit_clustered_case","last_synced_at":"2026-02-24T21:34:26.192Z","repository":{"id":57501121,"uuid":"141747234","full_name":"bitwalker/ex_unit_clustered_case","owner":"bitwalker","description":"An extension for ExUnit for simplifying tests against a clustered application","archived":false,"fork":false,"pushed_at":"2023-08-04T18:45:22.000Z","size":75,"stargazers_count":62,"open_issues_count":4,"forks_count":8,"subscribers_count":9,"default_branch":"main","last_synced_at":"2024-04-14T23:56:47.512Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bitwalker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-20T18:52:51.000Z","updated_at":"2023-12-17T10:10:11.000Z","dependencies_parsed_at":"2024-11-05T18:14:39.083Z","dependency_job_id":"19aed3ad-02e0-464c-8e4c-fbeb4a9f729c","html_url":"https://github.com/bitwalker/ex_unit_clustered_case","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Fex_unit_clustered_case","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Fex_unit_clustered_case/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Fex_unit_clustered_case/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bitwalker%2Fex_unit_clustered_case/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bitwalker","download_url":"https://codeload.github.com/bitwalker/ex_unit_clustered_case/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247411237,"owners_count":20934650,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T18:14:36.537Z","updated_at":"2026-02-24T21:34:21.171Z","avatar_url":"https://github.com/bitwalker.png","language":"Elixir","readme":"# ExUnit Clustered Cases\n\n[![Master](https://travis-ci.com/bitwalker/ex_unit_clustered_case.svg?branch=master)](https://travis-ci.com/bitwalker/ex_unit_clustered_case)\n[![Hex.pm Version](http://img.shields.io/hexpm/v/ex_unit_clustered_case.svg?style=flat)](https://hex.pm/packages/ex_unit_clustered_case)\n\nThis project provides an extension for ExUnit for running tests against a\nclustered application. It provides an easy way to spin up multiple nodes,\nmultiple clusters, and test a variety of scenarios in parallel without needing\nto manage the clustering aspect yourself.\n\n**NOTE:** This library requires Elixir 1.7+, due to a bug in earlier versions of\nExUnit which would generate different module md5s on every compile. This results\nin being unable to define functions in test modules for execution on other nodes,\nwhich is an impractical constraint for testing. This library has to compile test\nmodules on each node separately, as they are compiled in-memory by the test compiler,\nand so are unable to be remotely code loaded like modules which are compiled to `.beam`\nfiles on disk.\n\n## Installation\n\nYou can add this library to your project like so:\n\n```elixir\ndef deps do\n  [\n    {:ex_unit_clustered_case, \"~\u003e 0.4\"}\n  ]\nend\n```\n\nThen, add the following lines right above `ExUnit.start/1` in your `test_helper.exs` file to switch ExUnit's node to allow distribution:\n\n```elixir\n{_, 0} = System.cmd(\"epmd\", [\"-daemon\"])\nNode.start(:\"ex_unit@127.0.0.1\", :longnames)\nExUnit.start()\n```\n\n## Usage\n\nThe documentation for `ExUnit.ClusteredCase` provide more details, but below is brief idea\nof the capabilities currently offered in this library:\n\n```elixir\ndefmodule KVStore.ClusteredTests do\n  use ExUnit.ClusteredCase\n\n  # A scenario defines a group of tests which will be run against a single cluster,\n  # which is dynamically created. There are several options you can provide to configure\n  # the cluster, including size, partitions, configuration, system environment and more.\n  scenario \"given a healthy cluster\", [cluster_size: 2] do\n\n    # Node setups work similar to `setup` in ExUnit, but are run on each node of the cluster\n    node_setup [:start_apps, :seed_kvstore]\n\n    # Just plain old tests - note the :cluster key of the context, which is needed to talk\n    # to the nodes of the cluster via the Cluster API (an alias added for you)\n    test \"writes are replicated to all nodes\", %{cluster: c} do\n      writer = Cluster.random_member(c)\n      assert {:ok, 0} = Cluster.call(writer, KVStore, :get, [:counter])\n      assert :ok = Cluster.call(writer, KVStore, :increment, [:counter]\n      assert [1, 1] = Cluster.map(c, KVStore, :get, [:counter])\n    end\n  end\n\n  scenario \"given a partitioned cluster\", [cluster_size: 2] do\n    node_setup [:start_apps, :seed_kvstore]\n\n    test \"writes are not replicated during a partition, but are when healed\", %{cluster: c} do\n      [a, b] = Cluster.members(c)\n      assert [0, 0] = Cluster.map(c, KVStore, :get, [:counter])\n      # Partitions can be specified as a number of partitions, list of node\n      # counts, or list of node memberships\n      assert :ok = Cluster.partition(c, [[a], [b]])\n      assert :ok = Cluster.call(a, KVStore, :increment, [:counter])\n      assert {:ok, 1} = Cluster.call(a, KVStore, :get, [:counter])\n      assert [1, 0] = Cluster.map(c, KVStore, :get, [:counter])\n      assert :ok = Cluster.heal(c)\n      # You can use anonymous functions as well\n      assert [1, 1] = Cluster.map(c, fn -\u003e KVStore.get(:counter) end)\n    end\n  end\n\n  def start_apps(_context) do\n    Application.ensure_all_started(:kv_store)\n  end\n\n  def seed_kvstore(_context) do\n    KVStore.put(:counter, 0)\n  end\nend\n```\n\nThe goal of this project is to provide a way to easily express tests in a clustered environment, and\nto provide infrastructure for running such tests as efficiently as possible. Many different scenarios\nare desirable to test, but they boil down to the following:\n\n- The behavior when a cluster is healthy\n- The behavior when a cluster is partitioned or unhealthy (perhaps a master node is unavailable)\n- The behavior when a partition occurs and is subsequently healed\n- The behavior when one or more members of a cluster are \"flapping\", i.e. joining and leaving the cluster rapidly\n\nIf you are finding one of these scenarios difficult to test using this library, please let me know so\nthat it can be improved.\n\nYou can find more information in the docs on [hexdocs.pm](https://hexdocs.pm/ex_unit_clustered_case) or generate the docs\nwith `mix docs` from a local git checkout.\n\n## Capturing Output\n\nBy default, output written to stdio/stderr on nodes will be hidden. You can change this behavior for testing\nwith the following node options:\n\n- Capture the entire log from a node with `capture_log: true`\n- Redirect output to a device or process with `stdout: :standard_error | :standard_io | pid`\n- Both capture _and_ redirect by setting both options.\n\nDefault values are `capture_log: false` and `stdout: false`\n\nWhen you capture, you can get the captured logs for a specific node with `Cluster.log(node)`. If capturing\nis not enabled, this will simply return `{:ok, \"\"}`, otherwise it returns `{:ok, binary}`. When you call this\nfunction, the logs are returned, and the accumulated logs are flushed, resetting the capture state.\n\n**NOTE**: Setting these options occurs when a node is started, and cannot be changed later. Since output is\ngathered in a central location, async tests which are testing against a node's output may stomp on each other,\neither by writing content that conflicts with the other test, or by flushing the captured log when a test is not\nexpecting that to happen. If you need to test against log output, be sure to start separate nodes for each test,\nor run your tests with `async: false`.\n\n## Roadmap\n\n- [ ] Add support for disabling auto-clustering in favor of letting tools like\n      `libcluster` do the work.\n- [ ] Add fault injection support (random partitioning, flapping)\n\n## License\n\nApache 2, see the `LICENSE` file for more information.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitwalker%2Fex_unit_clustered_case","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbitwalker%2Fex_unit_clustered_case","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbitwalker%2Fex_unit_clustered_case/lists"}