Skip to content

Commit

Permalink
Add Quickcheck playground
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamintanweihao committed Jul 18, 2016
1 parent a29945b commit 19a30e5
Show file tree
Hide file tree
Showing 13 changed files with 457 additions and 0 deletions.
7 changes: 7 additions & 0 deletions chapter_11/quickcheck_playground/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/_build
/cover
/deps
erl_crash.dump
*.ez
*.eqc
.eqc-info
6 changes: 6 additions & 0 deletions chapter_11/quickcheck_playground/.vimrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
" make sure the following 2 lines are included in ~/.vimrc
" set exrc
" set secure

map <leader>t :!mix test %<CR>
map <leader>c :!mix compile && mix dialyzer<CR>
10 changes: 10 additions & 0 deletions chapter_11/quickcheck_playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Quickcheck Playground

This project uses QuickCheck Mini from Quviq.

1. Head over to [QuviQ](http://www.quviq.com/downloads/) and download the _FREE_ version of QuickCheck (Mini).
2. Unzip the folder, and `cd` into it.
3. Run `iex`.
4. Run `:eqc_install.install()` to install QuickCheck Mini.
5. Clone this repo, and run `mix deps.get`
6. Run `mix tests` to run the tests.
30 changes: 30 additions & 0 deletions chapter_11/quickcheck_playground/config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure for your application as:
#
# config :quickcheck_playground, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:quickcheck_playground, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
2 changes: 2 additions & 0 deletions chapter_11/quickcheck_playground/lib/quickcheck_playground.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
defmodule QuickcheckPlayground do
end
23 changes: 23 additions & 0 deletions chapter_11/quickcheck_playground/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule QuickcheckPlayground.Mixfile do
use Mix.Project

def project do
[app: :quickcheck_playground,
version: "0.0.1",
elixir: "~> 1.2-rc",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
test_pattern: "*_{test,eqc}.exs",
deps: deps]
end

def application do
[applications: [:logger]]
end

defp deps do
[
{:eqc_ex, "~> 1.2.4"}
]
end
end
3 changes: 3 additions & 0 deletions chapter_11/quickcheck_playground/mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
%{"eqc_ex": {:hex, :eqc_ex, "1.2.4"},
"excheck": {:hex, :excheck, "0.3.2"},
"triq": {:git, "https://github.com/krestenkrab/triq.git", "c7306b8eaea133d52140cb828817efb5e50a3d52", []}}
92 changes: 92 additions & 0 deletions chapter_11/quickcheck_playground/test/list_eqc.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
defmodule ListEQC do
use ExUnit.Case
use EQC.ExUnit

@tag numtests: 100
property "Deleting from a list" do
forall list <- ulist(int) do
implies list != [] do
forall item <- elements(list) do
ensure not item in List.delete(list, item) == true
end
end
end
end

property "Misconception of deleting from a list" do
:eqc.fails(
forall list <- list(int) do
implies list != [] do
forall item <- elements(list) do
ensure not item in List.delete(list, item) == true
end
end
end
)
end

property "Deleting an element not in a list leaves the list unchanged" do
forall list <- list(int) do
forall item <- int do
implies not item in list do
ensure list == List.delete(list, item)
end
end
end
end

property "sorting works" do
forall l <- list(int) do
ensure l |> Enum.sort |> is_sorted == true
end
end


# NOTE: Testing properties of reverse

property "reverse is recursive" do
forall l <- non_empty(list(char)) do
equal Enum.reverse(l), Enum.reverse(tl(l)) ++ [hd(l)]
end
end

property "reverse is distributive" do
forall {l1, l2} <- {list(char), list(char)} do
ensure Enum.reverse(l1 ++ l2) == Enum.reverse(l2) ++ Enum.reverse(l1)
end
end

property "reverse is idempotent" do
forall l <- list(char) do
ensure l |> Enum.reverse |> Enum.reverse == l
end
end

property "reverse is equivalent to the Erlang version" do
forall l <- list(oneof [real, int]) do
ensure Enum.reverse(l) == :lists.reverse(l)
end
end

def is_sorted([]), do: true

def is_sorted(list) do
list
|> Enum.zip(tl(list))
|> Enum.all?(fn {x, y} -> x <= y end)
end

def equal(x, y) do
when_fail(IO.puts("FAILED ☛ #{inspect(x)} != #{inspect(y)}")) do
x == y
end
end

# Custom generator to generate unique lists
def ulist(item) do
let l <- list(item) do
l |> Enum.sort |> Enum.uniq
end
end

end
121 changes: 121 additions & 0 deletions chapter_11/quickcheck_playground/test/map_eqc.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
defmodule MapEQC do
use ExUnit.Case
use EQC.ExUnit

# NOTE: Strange that this should fail but doesn't.
property "keys are unique (take 1)" do
forall m <- map_1 do
no_duplicates(Map.keys(m))
end
end

property "keys are unique (take 2)" do
forall m <- map_2 do
no_duplicates(Map.keys(:eqc_symbolic.eval(m)))
end
end

property "storing keys and values" do
forall {k, v, m} <- {key, val, map_2} do
map = :eqc_symbolic.eval(m)
lists_equal(model(Map.put(map, k, v)), model_store(k, v, model(map)))
end
end

property "merging maps is *not* commutative" do
forall {m1, m2} <- {map_2, map_2} do
map_1 = :eqc_symbolic.eval(m1)
map_2 = :eqc_symbolic.eval(m2)

# NOTE: This will not work, since keys can be overriden!
# Cool that QC finds this out after ~ 79 tests
:eqc.fails(
ensure Map.merge(map_1, map_2) == Map.merge(map_2, map_1)
)
end
end

property "merging maps retains keys" do
forall {m1, m2} <- {map_2, map_2} do
map_1 = :eqc_symbolic.eval(m1)
map_2 = :eqc_symbolic.eval(m2)

left_keys = Map.merge(map_1, map_2) |> Map.keys
right_keys = Map.merge(map_2, map_1) |> Map.keys

equal(left_keys, right_keys)
end
end

# First version of map generator
# NOTE: there's a recursive call to map_1(). We need to
# use the `lazy` macro here.
def map_1 do
map_gen = lazy do
let {k, v, m} <- {key, val, map_1} do
Map.put(m, k, v)
end
end

oneof [Map.new, map_gen]
end

# NOTE: Make sure that the order is right!
# {:call, Map, :put, [key, val, map_2]}] will *not* work!
def map_2 do
lazy do
oneof [{:call, Map, :new, []},
{:call, Map, :put, [map_2, key, val]}]
end
end

def no_duplicates(elems) do
left = elems |> Enum.sort
right = elems |> Enum.uniq |> Enum.sort
# equal(:lists.sort(elems), :lists.usort(elems))
equal(left, right)
end

def key do
oneof [int, real, atom]
end

def val do
key
end

def atom do
elements [:a, :b, :c, true, false, :ok]
end

def model(map) do
Map.to_list(map)
end

def model_store(k, v, list) do
case find_index_with_key(k, list) do
{:match, index} ->
List.replace_at(list, index, {k, v})
_ ->
[{k, v} | list]
end
end

def find_index_with_key(k, list) do
case Enum.find_index(list, fn({x,_}) -> x == k end) do
nil -> :nomatch
index -> {:match, index}
end
end

def equal(x, y) do
when_fail(IO.puts("FAILED ☛ #{inspect(x)} != #{inspect(y)}")) do
x == y
end
end

def lists_equal(x, y) do
equal(Enum.sort(x), Enum.sort(y))
end

end
23 changes: 23 additions & 0 deletions chapter_11/quickcheck_playground/test/numbers_eqc.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule NumbersEQC do
use ExUnit.Case
use EQC.ExUnit

property "the square of a real number is always positive" do
forall x <- real do
ensure x*x >= 0
end
end

property "the square of the square root of a real number is the original number" do
forall x <- pos_real do
sq_root = :math.sqrt(x)
ensure sq_root * sq_root - x < 0.000000000001
end
end

def pos_real do
let r <- real do
:erlang.abs(r)
end
end
end
17 changes: 17 additions & 0 deletions chapter_11/quickcheck_playground/test/others_eqc.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule OthersEQC do
use ExUnit.Case
use EQC.ExUnit

property "encoding then decoding a binary gives back the same binary" do
forall s <- binary do
equal s |> Base.encode16 |> Base.decode16!, s
end
end

def equal(x, y) do
when_fail(IO.puts("FAILED ☛ #{inspect(x)} != #{inspect(y)}")) do
x == y
end
end

end
Loading

0 comments on commit 19a30e5

Please sign in to comment.