chore: sort discovered deps for stable ordering across environments#51
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses non-deterministic output from mix usage_rules.sync by ensuring discovered dependencies are always sorted alphabetically. Previously, regex-matched dependencies could appear in different orders across different environments due to Erlang's Map iteration order being non-deterministic for larger maps.
Changes:
- Added
Enum.sort_by(&elem(&1, 0))todiscover_deps/1to ensure alphabetical ordering of dependencies - Added test to verify regex-matched dependencies appear in stable sorted order
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lib/mix/tasks/usage_rules.sync.ex | Added sorting to discover_deps/1 to ensure deterministic alphabetical ordering of dependencies |
| test/mix/tasks/usage_rules.sync_test.exs | Added test verifying regex produces sections in stable sorted order |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| alpha_pos = :binary.match(content, "## alpha usage") |> elem(0) | ||
| mango_pos = :binary.match(content, "## mango usage") |> elem(0) | ||
| zeta_pos = :binary.match(content, "## zeta usage") |> elem(0) | ||
|
|
There was a problem hiding this comment.
The test uses :binary.match/2 followed by elem(0) to extract positions. If any of these matches fail and return :nomatch, calling elem(0) on the atom :nomatch will crash with a BadArgumentError rather than providing a clear test failure message. Consider using pattern matching to provide better error messages, for example: {alpha_pos, _} = :binary.match(content, "## alpha usage") or adding explicit assertions that the content contains these strings before extracting positions.
| alpha_pos = :binary.match(content, "## alpha usage") |> elem(0) | |
| mango_pos = :binary.match(content, "## mango usage") |> elem(0) | |
| zeta_pos = :binary.match(content, "## zeta usage") |> elem(0) | |
| assert content =~ "## alpha usage" | |
| assert content =~ "## mango usage" | |
| assert content =~ "## zeta usage" | |
| {alpha_pos, _} = :binary.match(content, "## alpha usage") | |
| {mango_pos, _} = :binary.match(content, "## mango usage") | |
| {zeta_pos, _} = :binary.match(content, "## zeta usage") |
Contributor checklist
Leave anything that you believe does not apply unchecked.
Problem
Different environments (machines, Elixir versions) running
mix usage_rules.synccan produce different output for the same config. This causes unnecessary diffs and noisy commits when collaborators sync on their own machines.The root cause is
discover_deps/1returning deps fromMix.Project.deps_paths()(a Map) without sorting. Since Erlang Map iteration order is not guaranteed, regex-matched deps inusage_rulesconfig can appear in different orders.:ash)~r/^ash_/)Enum.sortalready applied)Solution
Add
Enum.sort_by(&elem(&1, 0))to the end ofdiscover_deps/1so thatall_depsis always in alphabetical order. This makes regex matching produce deterministic results without affecting explicit atom ordering (which is handled inresolve_usage_rules).Notes
The test verifies the intended sorted order but cannot reproduce the non-deterministic behavior in test mode, since Erlang small maps (<=32 keys) store keys in sorted order internally.