Closed boringcactus closed 1 year ago
I can confirm the issue with OTP v26
and v0.6.0
still exists:
1) test can autogenerate primary keys (Uniq.Ecto.Test)
test/ecto_test.exs:47
match (=) failed
code: assert {:ok, %UUID{version: 4}} = UUID.parse(uuid)
left: {:ok, %Uniq.UUID{version: 4}}
right: {:error, {:invalid_format, <<3>>, 2, 6, 14}}
stacktrace:
test/ecto_test.exs:52: (test)
.
2) test parsing can parse version 1 (Uniq.Test)
test/uniq_test.exs:47
match (=) failed
The following variables were pinned:
version = 1
code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
left: {:ok, %Uniq.UUID{format: :raw, version: ^version}}
right: {:error, {:invalid_format, <<226>>, 2, 6, 14}}
stacktrace:
test/uniq_test.exs:207: Uniq.Test.parse/2
test/uniq_test.exs:48: (test)
...
3) doctest Uniq.UUID.parse/1 (3) (Uniq.Test)
test/uniq_test.exs:6
match (=) failed
code: {:ok, uuid} = Uniq.UUID.parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6")
left: {:ok, uuid}
right: {:error, {:invalid_format, <<157>>, 2, 6, 14}}
stacktrace:
(for doctest at) lib/uuid.ex:443: (test)
...
4) doctest Uniq.UUID.info/2 (2) (Uniq.Test)
test/uniq_test.exs:6
Doctest failed
doctest:
iex> Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf")
{:ok, %Uniq.UUID{
format: :default,
version: 4,
variant: <<2::2>>,
time: 326283406408022248,
seq: 790,
node: <<129, 224, 137, 184, 194, 207>>,
bytes: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
}}
code: Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf") === {:ok, %Uniq.UUID{
format: :default,
version: 4,
variant: <<2::2>>,
time: 326283406408022248,
seq: 790,
node: <<129, 224, 137, 184, 194, 207>>,
bytes: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
}}
left: {:error, {:invalid_format, "\f", 2, 6, 14}}
right: {:ok, #UUIDv4<870df8e8-3107-4487-8316-81e089b8c2cf>}
stacktrace:
lib/uuid.ex:371: Uniq.UUID (module)
.
5) test parsing can parse version 5 (Uniq.Test)
test/uniq_test.exs:59
match (=) failed
The following variables were pinned:
version = 5
code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
left: {:ok, %Uniq.UUID{format: :raw, version: ^version}}
right: {:error, {:invalid_format, "_", 2, 6, 14}}
stacktrace:
test/uniq_test.exs:207: Uniq.Test.parse/2
test/uniq_test.exs:60: (test)
6) doctest Uniq.UUID.info/2 (1) (Uniq.Test)
test/uniq_test.exs:6
Doctest failed
doctest:
iex> Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf", :keyword)
{:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf",
binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
type: :default,
version: 4,
variant: :rfc4122]}
code: Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf", :keyword) === {:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf",
binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
type: :default,
version: 4,
variant: :rfc4122]}
left: {:error, {:invalid_format, "\f", 2, 6, 14}}
right: {:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf", binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>, type: :default, version: 4, variant: :rfc4122]}
stacktrace:
lib/uuid.ex:364: Uniq.UUID (module)
.
7) test generating can generate version 1 (Uniq.Test)
test/uniq_test.exs:139
match (=) failed
code: assert {:ok, %UUID{format: :default, version: 1}} = UUID.parse(default)
left: {:ok, %Uniq.UUID{format: :default, version: 1}}
right: {:error, {:invalid_format, <<2>>, 2, 6, 14}}
stacktrace:
test/uniq_test.exs:143: (test)
....
8) test generating can generate version 3 (Uniq.Test)
test/uniq_test.exs:147
match (=) failed
code: assert {:ok, %UUID{format: :default, version: 3}} = UUID.parse(default)
left: {:ok, %Uniq.UUID{format: :default, version: 3}}
right: {:error, {:invalid_format, "W", 2, 6, 14}}
stacktrace:
test/uniq_test.exs:154: (test)
9) property parsing can parse any 32-byte hex string which represents a valid uuid (Uniq.Test)
test/uniq_test.exs:83
Failed with generated values (after 0 successful runs):
* Clause: {version, variant, uuid} <- valid_uuid(:hex)
Generated: {4, <<7::size(3)>>, "0000000000004000E000000000000000"}
match (=) failed
The following variables were pinned:
version = 4
variant = <<7::size(3)>>
code: assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid)
left: {:ok, %Uniq.UUID{version: ^version, variant: ^variant}}
right: {:error, {:invalid_format, <<0>>, 3, 5, 13}}
stacktrace:
test/uniq_test.exs:85: anonymous fn/4 in Uniq.Test."property parsing can parse any 32-byte hex string which represents a valid uuid"/1
(stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6
(stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7
test/uniq_test.exs:84: (test)
10) property parsing can parse any 22-byte base64-encoded string which represents a valid uuid (Uniq.Test)
test/uniq_test.exs:95
Failed with generated values (after 0 successful runs):
* Clause: {version, variant, uuid} <- valid_uuid(:slug)
Generated: {4, <<7::size(3)>>, "AAAAAAAAQADgAAAAAAAAAA"}
match (=) failed
The following variables were pinned:
version = 4
variant = <<7::size(3)>>
code: assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid)
left: {:ok, %Uniq.UUID{version: ^version, variant: ^variant}}
right: {:error, {:invalid_format, <<0>>, 3, 5, 13}}
stacktrace:
test/uniq_test.exs:97: anonymous fn/4 in Uniq.Test."property parsing can parse any 22-byte base64-encoded string which represents a valid uuid"/1
(stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6
(stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7
test/uniq_test.exs:96: (test)
11) doctest Uniq.UUID.parse/1 (4) (Uniq.Test)
test/uniq_test.exs:6
Doctest failed
doctest:
iex> match?({:ok, %Uniq.UUID{format: :default, version: 1}}, Uniq.UUID.uuid1() |> Uniq.UUID.parse())
true
code: match?({:ok, %Uniq.UUID{format: :default, version: 1}}, Uniq.UUID.uuid1() |> Uniq.UUID.parse()) === true
left: false
right: true
stacktrace:
lib/uuid.ex:456: Uniq.UUID (module)
12) test parsing can parse version 4 (Uniq.Test)
test/uniq_test.exs:55
match (=) failed
The following variables were pinned:
version = 4
code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
left: {:ok, %Uniq.UUID{format: :raw, version: ^version}}
right: {:error, {:invalid_format, "`", 2, 6, 14}}
stacktrace:
test/uniq_test.exs:207: Uniq.Test.parse/2
test/uniq_test.exs:56: (test)
13) test generating can generate version 4 (Uniq.Test)
test/uniq_test.exs:164
match (=) failed
code: assert {:ok, %UUID{format: :default, version: 4}} = UUID.parse(default)
left: {:ok, %Uniq.UUID{format: :default, version: 4}}
right: {:error, {:invalid_format, <<220>>, 2, 6, 14}}
stacktrace:
test/uniq_test.exs:168: (test)
14) property parsing can parse any 128-bit binary with valid version/variant values (Uniq.Test)
test/uniq_test.exs:71
Failed with generated values (after 0 successful runs):
* Clause: {version, variant, uuid} <- valid_uuid()
Generated: {4, <<7::size(3)>>, <<0, 0, 0, 0, 0, 0, 64, 0, 224, 0, 0, 0, 0, 0, 0, 0>>}
match (=) failed
The following variables were pinned:
version = 4
variant = <<7::size(3)>>
code: assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid)
left: {:ok, %Uniq.UUID{version: ^version, variant: ^variant}}
right: {:error, {:invalid_format, <<0>>, 3, 5, 13}}
stacktrace:
test/uniq_test.exs:73: anonymous fn/4 in Uniq.Test."property parsing can parse any 128-bit binary with valid version/variant values"/1
(stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6
(stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7
test/uniq_test.exs:72: (test)
.
15) test parsing can parse version 3 (Uniq.Test)
test/uniq_test.exs:51
match (=) failed
The following variables were pinned:
version = 3
code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
left: {:ok, %Uniq.UUID{format: :raw, version: ^version}}
right: {:error, {:invalid_format, "B", 2, 6, 14}}
stacktrace:
test/uniq_test.exs:207: Uniq.Test.parse/2
test/uniq_test.exs:52: (test)
16) test generating can generate version 5 (Uniq.Test)
test/uniq_test.exs:172
match (=) failed
code: assert {:ok, %UUID{format: :default, version: 5}} = UUID.parse(default)
left: {:ok, %Uniq.UUID{format: :default, version: 5}}
right: {:error, {:invalid_format, <<128>>, 2, 6, 14}}
stacktrace:
test/uniq_test.exs:179: (test)
..
Finished in 15.2 seconds (0.1s async, 15.0s sync)
4 doctests, 6 properties, 22 tests, 16 failures
Randomized with seed 870698
Interesting - I wonder what happened in OTP 26 to break things, I'll take a look at this ASAP
Something really weird is going on here, to the point that I suspect a miscompilation.
Parsing is failing here, because the binary constructed by <<clock::unsigned-integer-size(clock_size>> = <<clock_hi::bitstring-size(clock_hi_size), clock_lo::bitstring-size(8)>>
is inexplicably being truncated to 8 bits from 14 bits. For reference, I'm looking at the intermediate results when running mix test test/uniq_test:47
, which tests parsing a Version 1 uuid. For that test, here's the values of the bindings in that expression:
clock_size = 14
clock_hi = <<56::size(6)>>
clock_hi_size = 6
clock_lo = <<188>>
If I open up IEx and type in the exact same expression with all of the same values, I get <<226, 60::size(6)>>
as output, which is correct; but the compiled code produces <<226>>
! However, if I change clock_hi_size
to 6
in the code, it correctly produces <<226, 60::size(6)>>
and the test passes.
I can only assume this is a JIT issue, because binary constructing/matching in the shell uses the eval_bits
module in the Erlang standard library, whereas compiled code uses highly specialized bytecode instructions for the same tasks, some/all of which get JITed I would imagine.
@josevalim Has anyone reported similar issues in Elixir with OTP 26? I'm not actually sure whether this is an issue in Erlang or in Elixir. It is a bit tough to reproduce minimally because the bug here only seems to appear when none of the values are known at compile-time, so presumably optimizations are interfering in less dynamic, but equivalent programs. I would assume it's an issue in Erlang due to the difference in behavior between compiled code and interpreted code, but that might just be a coincidence. Sorry to ping you like this, again 😅, but this definitely appears to be a compiler bug of some sort or another. I'm happy to put in some effort on a minimal reproducer, but wanted to make sure this wasn't already a known issue that I'm not aware of.
From the description it looks like an Erlang bug. So I would try 26.0.2 and, if it persists, we need a way to reproduce it so I can report it upstream.
Yeah I'm testing with 26.0.2, so I'll put a reproducer together and follow up. Thanks!
@josevalim Here's the reproducer:
# lib/app.ex
defmodule BinMatchBugOtp26 do
defmacrop bits(n), do: quote(do: bitstring - size(unquote(n)))
defmacrop uint(n), do: quote(do: unsigned - integer - size(unquote(n)))
@rfc_variant <<2::2>>
def parse(<<_::64, @rfc_variant, rest::bits(62)>>) do
parse_raw(@rfc_variant, rest)
end
defp parse_raw(variant, rest) do
variant_size = bit_size(variant)
clock_hi_size = 8 - variant_size
clock_size = 8 + clock_hi_size
with <<clock_hi::bits(clock_hi_size), clock_lo::bits(8), _::bits(48)>> <-
rest,
<<clock::uint(clock_size)>> <-
IO.inspect(<<clock_hi::bits(clock_hi_size), clock_lo::bits(8)>>) do
{:ok, clock}
else
other ->
{:error, {:invalid_format, other}}
end
end
end
With the following test harness:
# test/app_test.exs
defmodule BinMatchBugOtp26Test do
use ExUnit.Case
test "reproduces the bug" do
bin = <<146, 254, 245, 214, 198, 57, 17, 235, 184, 188, 2, 66, 172, 19, 0, 3>>
assert {:ok, 14524} = BinMatchBugOtp26.parse(bin)
end
end
If you modify it to change variant_size = bit_size(variant)
to variant_size = 2
, the test passes, presumably due to optimizations, but I haven't looked at the bytecode to see for sure. I think this should be minimal enough for debugging/reporting upstream.
Just to reiterate, we're expecting that the binary which is being inspected has the value <<226, 60::size(6)>>
, but the bug causes it to be <<226>>
.
Thanks, reported upstream here: https://github.com/erlang/otp/issues/7469
Btw, it may be worth writing the alternative variant and shipping a new release to unblock others. :)
There isn't a great alternative unfortunately, the code I used for the repro was just for a single UUID variant, and there are quite a few. I can probably use macros to generate more specialized parsing functions that bypass the issue for the time being though.
For anyone reading, I wouldn't consider running OTP 26 in production until this bug gets patched, we're lucky that it is so easy to trigger with uniq
, but that is only because I was careful to write assertions into core parts of the parsing code. I have to imagine there is a lot of parsing code out there affected by this bug, just with more subtle failure modes.
I've pushed a workaround in main
, and I'll push a new release (0.6.1) shortly
I'm trying to update my application that uses Uniq to Erlang/OTP 26, but
Uniq.UUID.valid?/1
is incorrectly returningfalse
for valid UUIDs. I ran the Uniq tests (after updating the locked Ecto to v3.9.5), and I'm seeing 16 failures, all of which are related to parsing but none of which specifically parse versions 6 or 7.Exact output
``` ❯ mix test 1) test can autogenerate primary keys (Uniq.Ecto.Test) test/ecto_test.exs:47 match (=) failed code: assert {:ok, %UUID{version: 4}} = UUID.parse(uuid) left: {:ok, %Uniq.UUID{version: 4}} right: {:error, {:invalid_format, <<240>>, 2, 6, 14}} stacktrace: test/ecto_test.exs:52: (test) 2) test generating can generate version 3 (Uniq.Test) test/uniq_test.exs:147 match (=) failed code: assert {:ok, %UUID{format: :default, version: 3}} = UUID.parse(default) left: {:ok, %Uniq.UUID{format: :default, version: 3}} right: {:error, {:invalid_format, "W", 2, 6, 14}} stacktrace: test/uniq_test.exs:154: (test) 3) property parsing can parse any 22-byte base64-encoded string which represents a valid uuid (Uniq.Test) test/uniq_test.exs:95 Failed with generated values (after 0 successful runs): * Clause: {version, variant, uuid} <- valid_uuid(:slug) Generated: {3, <<2::size(2)>>, "AAAAAAAAMACAAAAAAAAAAA"} match (=) failed The following variables were pinned: version = 3 variant = <<2::size(2)>> code: assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid) left: {:ok, %Uniq.UUID{version: ^version, variant: ^variant}} right: {:error, {:invalid_format, <<0>>, 2, 6, 14}} stacktrace: test/uniq_test.exs:97: anonymous fn/4 in Uniq.Test."property parsing can parse any 22-byte base64-encoded string which represents a valid uuid"/1 (stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6 (stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7 test/uniq_test.exs:96: (test) . 4) test generating can generate version 1 (Uniq.Test) test/uniq_test.exs:139 match (=) failed code: assert {:ok, %UUID{format: :default, version: 1}} = UUID.parse(default) left: {:ok, %Uniq.UUID{format: :default, version: 1}} right: {:error, {:invalid_format, <<218>>, 2, 6, 14}} stacktrace: test/uniq_test.exs:143: (test) . 5) test generating can generate version 4 (Uniq.Test) test/uniq_test.exs:164 match (=) failed code: assert {:ok, %UUID{format: :default, version: 4}} = UUID.parse(default) left: {:ok, %Uniq.UUID{format: :default, version: 4}} right: {:error, {:invalid_format, "3", 2, 6, 14}} stacktrace: test/uniq_test.exs:168: (test) 6) doctest Uniq.UUID.info/2 (1) (Uniq.Test) test/uniq_test.exs:6 Doctest failed doctest: iex> Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf", :keyword) {:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf", binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>, type: :default, version: 4, variant: :rfc4122]} code: Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf", :keyword) === {:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf", binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>, type: :default, version: 4, variant: :rfc4122]} left: {:error, {:invalid_format, "\f", 2, 6, 14}} right: { :ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf", binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>, type: :default, version: 4, variant: :rfc4122] } stacktrace: lib/uuid.ex:364: Uniq.UUID (module) 7) doctest Uniq.UUID.info/2 (2) (Uniq.Test) test/uniq_test.exs:6 Doctest failed doctest: iex> Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf") {:ok, %Uniq.UUID{ format: :default, version: 4, variant: <<2::2>>, time: 326283406408022248, seq: 790, node: <<129, 224, 137, 184, 194, 207>>, bytes: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>, }} code: Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf") === {:ok, %Uniq.UUID{ format: :default, version: 4, variant: <<2::2>>, time: 326283406408022248, seq: 790, node: <<129, 224, 137, 184, 194, 207>>, bytes: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>, }} left: {:error, {:invalid_format, "\f", 2, 6, 14}} right: {:ok, #UUIDv4<870df8e8-3107-4487-8316-81e089b8c2cf>} stacktrace: lib/uuid.ex:371: Uniq.UUID (module) .. 8) property parsing can parse any 128-bit binary with valid version/variant values (Uniq.Test) test/uniq_test.exs:71 Failed with generated values (after 0 successful runs): * Clause: {version, variant, uuid} <- valid_uuid() Generated: {3, <<2::size(2)>>, <<0, 0, 0, 0, 0, 0, 48, 0, 128, 0, 0, 0, 0, 0, 0, 0>>} match (=) failed The following variables were pinned: version = 3 variant = <<2::size(2)>> code: assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid) left: {:ok, %Uniq.UUID{version: ^version, variant: ^variant}} right: {:error, {:invalid_format, <<0>>, 2, 6, 14}} stacktrace: test/uniq_test.exs:73: anonymous fn/4 in Uniq.Test."property parsing can parse any 128-bit binary with valid version/variant values"/1 (stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6 (stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7 test/uniq_test.exs:72: (test) 9) test generating can generate version 5 (Uniq.Test) test/uniq_test.exs:172 match (=) failed code: assert {:ok, %UUID{format: :default, version: 5}} = UUID.parse(default) left: {:ok, %Uniq.UUID{format: :default, version: 5}} right: {:error, {:invalid_format, <<243>>, 2, 6, 14}} stacktrace: test/uniq_test.exs:179: (test) .. 10) test parsing can parse version 5 (Uniq.Test) test/uniq_test.exs:59 match (=) failed The following variables were pinned: version = 5 code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version]) left: {:ok, %Uniq.UUID{format: :raw, version: ^version}} right: {:error, {:invalid_format, "_", 2, 6, 14}} stacktrace: test/uniq_test.exs:207: Uniq.Test.parse/2 test/uniq_test.exs:60: (test) 11) property parsing can parse any 32-byte hex string which represents a valid uuid (Uniq.Test) test/uniq_test.exs:83 Failed with generated values (after 0 successful runs): * Clause: {version, variant, uuid} <- valid_uuid(:hex) Generated: {3, <<2::size(2)>>, "00000000000030008000000000000000"} match (=) failed The following variables were pinned: version = 3 variant = <<2::size(2)>> code: assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid) left: {:ok, %Uniq.UUID{version: ^version, variant: ^variant}} right: {:error, {:invalid_format, <<0>>, 2, 6, 14}} stacktrace: test/uniq_test.exs:85: anonymous fn/4 in Uniq.Test."property parsing can parse any 32-byte hex string which represents a valid uuid"/1 (stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6 (stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7 test/uniq_test.exs:84: (test) ...... 12) doctest Uniq.UUID.parse/1 (3) (Uniq.Test) test/uniq_test.exs:6 match (=) failed code: {:ok, uuid} = Uniq.UUID.parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6") left: {:ok, uuid} right: {:error, {:invalid_format, <<157>>, 2, 6, 14}} stacktrace: (for doctest at) lib/uuid.ex:443: (test) . 13) test parsing can parse version 1 (Uniq.Test) test/uniq_test.exs:47 match (=) failed The following variables were pinned: version = 1 code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version]) left: {:ok, %Uniq.UUID{format: :raw, version: ^version}} right: {:error, {:invalid_format, <<226>>, 2, 6, 14}} stacktrace: test/uniq_test.exs:207: Uniq.Test.parse/2 test/uniq_test.exs:48: (test) . 14) test parsing can parse version 4 (Uniq.Test) test/uniq_test.exs:55 match (=) failed The following variables were pinned: version = 4 code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version]) left: {:ok, %Uniq.UUID{format: :raw, version: ^version}} right: {:error, {:invalid_format, "`", 2, 6, 14}} stacktrace: test/uniq_test.exs:207: Uniq.Test.parse/2 test/uniq_test.exs:56: (test) 15) test parsing can parse version 3 (Uniq.Test) test/uniq_test.exs:51 match (=) failed The following variables were pinned: version = 3 code: assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version]) left: {:ok, %Uniq.UUID{format: :raw, version: ^version}} right: {:error, {:invalid_format, "B", 2, 6, 14}} stacktrace: test/uniq_test.exs:207: Uniq.Test.parse/2 test/uniq_test.exs:52: (test) . 16) doctest Uniq.UUID.parse/1 (4) (Uniq.Test) test/uniq_test.exs:6 Doctest failed doctest: iex> match?({:ok, %Uniq.UUID{format: :default, version: 1}}, Uniq.UUID.uuid1() |> Uniq.UUID.parse()) true code: match?({:ok, %Uniq.UUID{format: :default, version: 1}}, Uniq.UUID.uuid1() |> Uniq.UUID.parse()) === true left: false right: true stacktrace: lib/uuid.ex:456: Uniq.UUID (module) . Finished in 15.1 seconds (0.1s async, 15.0s sync) 4 doctests, 6 properties, 22 tests, 16 failures Randomized with seed 100798 ```I looked at the code and the Erlang/OTP 26 release notes to see if I could spot the problem, but I can't.