Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,8 @@ defmodule Ash.MixProject do
{:mix_audit, ">= 0.0.0", only: [:dev, :test], runtime: false},
{:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false},
{:benchee, "~> 1.1", only: [:dev, :test]},
{:tz, "~> 0.28", only: [:test]}
{:tz, "~> 0.28", only: [:test]},
{:usage_rules, "~> 0.1"}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be added here.

]
end

Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"},
"tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"},
"usage_rules": {:hex, :usage_rules, "0.1.23", "908fb5ffe23f8689327d29a0ee0d4f3485408e9f60413213f47673777e870b5d", [:mix], [{:igniter, ">= 0.6.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "f77abaf9bc8479702addf48974ded96caabfb298dd091d5d26bb3564fa5d2159"},
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
"yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
"ymlr": {:hex, :ymlr, "5.1.3", "a8061add5a378e20272a31905be70209a5680fdbe0ad51f40cb1af4bdd0a010b", [:mix], [], "hexpm", "8663444fa85101a117887c170204d4c5a2182567e5f84767f0071cf15f2efb1e"},
Expand Down
59 changes: 59 additions & 0 deletions usage-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -1131,3 +1131,62 @@ When testing resources:
- Use `authorize?: false` in tests where authorization is not the focus
- Write generators using `Ash.Generator`
- Prefer to use raising versions of functions whenever possible, as opposed to pattern matching

### Preventing Deadlocks in Concurrent Tests with Ash.Generator

When running tests concurrently, using fixed values for identity attributes can cause postgres deadlock errors. Multiple tests attempting to create records with the same unique values will conflict. Ash provides `Ash.Generator` with built-in utilities for generating unique test data.

#### Using Ash.Generator.sequence/3

The `sequence/3` function generates unique sequential values within your test process:

```elixir
# BAD - Can cause deadlocks in concurrent tests
%{email: "test@example.com", username: "testuser"}

# GOOD - Use Ash.Generator.sequence for unique values
%{
email: Ash.Generator.sequence(:email, fn i -> "user#{i}@example.com" end),
username: Ash.Generator.sequence(:username, fn i -> "user_#{i}" end),
slug: Ash.Generator.sequence(:slug, fn i -> "post-#{i}" end)
}
```

#### Creating Reusable Test Generators

For better organization, create a generator module:

```elixir
defmodule MyApp.TestGenerators do
use Ash.Generator

def user(opts \\ []) do
changeset_generator(
User,
:create,
defaults: [
email: sequence(:user_email, &"user#{&1}@example.com"),
username: sequence(:username, &"user_#{&1}")
],
overrides: opts
)
end
end

# In your tests
test "concurrent user creation" do
users = MyApp.TestGenerators.generate_many(user(), 10)
# Each user has unique identity attributes
end
```

#### Process-Scoped vs Globally Unique

- `Ash.Generator.sequence/3` provides uniqueness within the test process
- For cross-process uniqueness, use `System.unique_integer/1`:

```elixir
email: "test-#{System.unique_integer([:positive])}@example.com"
```

This applies to ANY field used in identity constraints, not just primary keys. Using these patterns prevents frustrating intermittent test failures in CI environments.
Loading