Skip to content

Support dynamic enforcer names per test#63

Closed
Copilot wants to merge 5 commits intomasterfrom
copilot/fix-shared-enforcer-state-again
Closed

Support dynamic enforcer names per test#63
Copilot wants to merge 5 commits intomasterfrom
copilot/fix-shared-enforcer-state-again

Conversation

Copy link
Contributor

Copilot AI commented Feb 12, 2026

Tests using a shared enforcer name experience race conditions in async mode. One test's cleanup deletes policies while another test is executing, causing intermittent failures.

This PR provides utility functions to support dynamic enforcer names per test, allowing tests to run safely with async: true.

Changes

Core utilities (lib/casbin/test_helper.ex)

  • unique_enforcer_name/1 - Generates unique enforcer names via :erlang.unique_integer/1
  • cleanup_enforcer/1 - Removes enforcer from ETS and stops process

Usage

Before:

defmodule MyApp.AclTest do
  use ExUnit.Case  # async: false required
  @enforcer_name "my_enforcer"  # shared state
end

After:

defmodule MyApp.AclTest do
  use ExUnit.Case, async: true
  import Casbin.TestHelper

  setup do
    ename = unique_enforcer_name("acl_test")
    {:ok, _pid} = Casbin.EnforcerSupervisor.start_enforcer(ename, config_path)
    on_exit(fn -> cleanup_enforcer(ename) end)
    {:ok, enforcer_name: ename}
  end

  test "admin permissions", %{enforcer_name: ename} do
    EnforcerServer.add_policy(ename, {:p, ["admin", "data", "read"]})
    assert EnforcerServer.allow?(ename, ["admin", "data", "read"])
  end
end

Benefits

✅ Each test gets a unique enforcer instance
✅ Tests can run in parallel with async: true
✅ No race conditions or flaky tests
✅ Backward compatible

Original prompt

This section details on the original issue you should resolve

<issue_title>Shared Global Enforcer state breaks Async tests</issue_title>
<issue_description>## Problem
When using EnforcerServer with a named enforcer (e.g., "reach_enforcer"), all tests share the same global state, making async: true tests fail with race conditions.

What Happened

# This fails with async: true
defmodule Reach.Casbin.AclTest do
  use Reach.CasbinCase, async: true  # ❌ Tests interfere with each other
  
  test "admin has permissions" do
    create_test_policies(test_org_id)  # Adds policies
    # Another test's cleanup deletes these policies mid-test!
    assert Acl.allow?(...) # Returns false unexpectedly
  end
end

Symptoms

  • Policies.list() returns [] even after adding policies
  • add_policy returns {:error, :already_existed} but policies aren't in memory
  • Tests pass individually but fail when run together

Root Cause

# All tests use the same enforcer instance
@enforcer_name "reach_enforcer"
EnforcerServer.add_policy(@enforcer_name, ...)

One test's on_exit cleanup deletes policies while another test is running.

Workaround

# Must disable async
defmodule Reach.Casbin.AclTest do
  use Reach.CasbinCase  # Removed: async: true
end

Proposed Solutions

  1. Support dynamic enforcer names per test
  2. Provide enforcer isolation/sandboxing for tests
  3. Document the async limitation</issue_description>

Comments on the Issue (you are @copilot in this section)

@hsluoyz @sushilbansal can you make a PR?

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@hsluoyz hsluoyz marked this pull request as ready for review February 12, 2026 13:54
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

…nup from async tests

Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix shared global enforcer state causing async test failures Add async test isolation for EnforcerServer Feb 12, 2026
Copilot AI requested a review from hsluoyz February 12, 2026 14:07
@hsluoyz
Copy link
Member

hsluoyz commented Feb 12, 2026

@copilot fix:

1s
Run mix format --check-formatted
  mix format --check-formatted
  shell: /usr/bin/bash -e {0}
  env:
    INSTALL_DIR_FOR_OTP: /home/runner/work/_temp/.setup-beam/otp
    INSTALL_DIR_FOR_ELIXIR: /home/runner/work/_temp/.setup-beam/elixir
** (Mix) mix format failed due to --check-formatted.
The following files are not formatted:

  * test/casbin/test_helper_test.exs
  * test/casbin/async_example.exs
  * lib/casbin/async_case.ex
  * test/casbin/async_case_test.exs

Error: Process completed with exit code 1.

moreover, don't add test code, don't add markdown files, don't modify README.md

Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 12, 2026

@copilot fix:

...

Fixed in d02c47f. Removed all test files, markdown documentation, and README changes. Only kept the core library files (lib/casbin/test_helper.ex and lib/casbin/async_case.ex) and ensured they are properly formatted.

@hsluoyz
Copy link
Member

hsluoyz commented Feb 12, 2026

@copilot you should not add test templates. you should do as: #31

Proposed Solutions
Support dynamic enforcer names per test

Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 12, 2026

@copilot you should not add test templates. you should do as: #31

...

Fixed in 0e12cac. Removed test templates (AsyncCase) and convenience wrappers. Now only provides the core utilities for dynamic enforcer names per test: unique_enforcer_name/1 and cleanup_enforcer/1.

Copilot AI changed the title Add async test isolation for EnforcerServer Support dynamic enforcer names per test Feb 12, 2026
@hsluoyz hsluoyz closed this Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shared Global Enforcer state breaks Async tests

3 participants