app: hand-write the User Inspect impl so coverage can instrument it
`redact: true` makes Ecto derive an Inspect impl at the `schema` line.
Elixir 1.20.4 emits that whole derived impl on that one line, and OTP 27's
coverage instrumentation (sys_coverage) turns it into invalid Erlang
("variable '_2' unsafe in 'case'"), so `mix test --cover` aborted for the whole
app before reporting anything. Minimal repro, no Ecto needed:
`@derive Inspect` on a plain struct fails on Elixir 1.20.4 + OTP 27.3.4.19 and
works on Elixir 1.18.3 + the same OTP.
Writing the impl out by hand, one expression per line, keeps `inspect/1`
output byte-identical (Ecto/Elixir render redacted fields as
`#Mod<..., ...>`) and keeps redaction pinned by the existing test, and lets the
coverage sensor land. Revisit when the Elixir/OTP pins change.
This commit is contained in:
@@ -2,6 +2,16 @@ defmodule Firehose.Accounts.User do
|
|||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
# `redact: true` below makes Ecto derive an `Inspect` impl for us. We write
|
||||||
|
# that impl out by hand instead (see the `defimpl` after the schema):
|
||||||
|
# Elixir 1.20.4 emits the derived code entirely on the `schema` line, and
|
||||||
|
# OTP 27's coverage instrumentation turns that single line into invalid
|
||||||
|
# Erlang ("variable '_2' unsafe in 'case'"), which aborts `mix test --cover`
|
||||||
|
# for the whole app. Output and redaction are identical, and the redaction is
|
||||||
|
# pinned by test/firehose/accounts_test.exs. Drop this and go back to the
|
||||||
|
# derive when the Elixir/OTP pins no longer reproduce the crash.
|
||||||
|
@derive_inspect_for_redacted_fields false
|
||||||
|
|
||||||
schema "users" do
|
schema "users" do
|
||||||
field :email, :string
|
field :email, :string
|
||||||
field :password, :string, virtual: true, redact: true
|
field :password, :string, virtual: true, redact: true
|
||||||
@@ -12,6 +22,27 @@ defmodule Firehose.Accounts.User do
|
|||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defimpl Inspect do
|
||||||
|
alias Firehose.Accounts.User
|
||||||
|
|
||||||
|
# Same set as the `redact: true` field options above; keep them in sync.
|
||||||
|
@redacted_fields [:password, :hashed_password]
|
||||||
|
|
||||||
|
def inspect(%User{} = user, opts) do
|
||||||
|
infos =
|
||||||
|
for %{field: field} = info <- User.__info__(:struct),
|
||||||
|
field not in @redacted_fields,
|
||||||
|
do: info
|
||||||
|
|
||||||
|
Inspect.Any.inspect_as_struct(
|
||||||
|
user,
|
||||||
|
Macro.inspect_atom(:literal, User),
|
||||||
|
infos,
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
A user changeset for registering or changing the email.
|
A user changeset for registering or changing the email.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user