Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add test: check if decoding is one to one by generating collisions #24

Merged
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -29,7 +29,8 @@ defmodule ExRLP.Mixfile do
{:credo, "~> 0.10.2", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.19", only: :dev, runtime: false},
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
{:poison, "~> 4.0.1", only: [:dev, :test], runtime: false}
{:poison, "~> 4.0.1", only: [:dev, :test], runtime: false},
{:propcheck, "~> 1.1", only: [:test, :dev]}
]
end

Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm"},
"propcheck": {:hex, :propcheck, "1.2.0", "e2b84f2f1a4c46b6b2aa22a0f6ddf97696f99d4a5c8f71d45f6519741e727eca", [:mix], [{:proper, "~> 1.3", [hex: :proper, repo: "hexpm", optional: false]}], "hexpm"},
"proper": {:hex, :proper, "1.3.0", "c1acd51c51da17a2fe91d7a6fc6a0c25a6a9849d8dc77093533109d1218d8457", [:make, :mix, :rebar3], [], "hexpm"},
}
59 changes: 59 additions & 0 deletions test/ex_rlp/property_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule ExRLP.PropTest do
@moduledoc false

use PropCheck
use ExUnit.Case

@moduletag timeout: 120_000

def safe_decode(binary) do
try do
ExRLP.decode(binary)
rescue
_ -> :decoder_crashed
end
end

# Looks for cases where decoding is not one-to-one. Note: no claims about the encoder are checked here!
# If we will find two distinct binaries that decode to the same elixir value, we've found our counterexample.
# Lists and sorting are used as a performance optimization.
# This allows us to do n^2 effective checks in search space while making just n runs of generator,
# 2n runs of the decoder and n*log(n) value comparisons.
property "decoding is one to one", [1000, :verbose, max_size: 100, constraint_tries: 100_000] do
# This generator will generate random binaries until one will decode or constraint_tries.
decodable_binary_generator =
such_that(bin <- binary(), when: :decoder_crashed != safe_decode(bin))

forall list_of_decodable_binaries <- list(decodable_binary_generator) do
# Make sure there are no duplicates in encoded values.
list_of_decodable_binaries = :lists.usort(list_of_decodable_binaries)
decoded_items = for item <- list_of_decodable_binaries, do: ExRLP.decode(item)
# Assert that list of decoded structures contain no duplicates.
:lists.sort(decoded_items) == :lists.usort(decoded_items)
end
end

property "for any string encoding is one to one", [1000, :verbose, max_size: 100] do
forall l <- binary() do
encoded = ExRLP.encode(l)
decoded = ExRLP.decode(encoded)
l == decoded
end
end

# This test searches in at most 3 layers of nesting for lists because of lack of
# support for recursive types in propcheck.
# For details see: https://github.com/alfert/propcheck/issues/5 and related issues / PRs.
property "for lists encoding is one to one", [1000, :verbose, max_size: 100] do
forall l <-
union([
list(binary()),
list(union([binary(), list(binary())])),
list(union([binary(), list(binary()), list(union([binary(), list(binary())]))]))
]) do
encoded = ExRLP.encode(l)
decoded = ExRLP.decode(encoded)
l == decoded
end
end
end