Use actual range in the `EnumTest.Range`

Code.require_file "test_helper.exs", __DIR__

defmodule EnumTest.List do
  use ExUnit.Case, async: true

  test :empty? do
    assert Enum.empty?([])
    refute Enum.empty?([1, 2, 3])
    refute Enum.empty?(1..3)
  end

  test :member? do
    assert Enum.member?([1, 2, 3], 2)
    refute Enum.member?([], 0)
    refute Enum.member?([1, 2, 3], 0)
    assert Enum.member?(1..3, 2)
    refute Enum.member?(1..3, 0)
  end

  test :count do
    assert Enum.count([1, 2, 3]) == 3
    assert Enum.count([]) == 0
  end

  test :count_fun do
    assert Enum.count([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == 1
    assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
  end

  test :all? do
    assert Enum.all?([2, 4, 6], fn(x) -> rem(x, 2) == 0 end)
    refute Enum.all?([2, 3, 4], fn(x) -> rem(x, 2) == 0 end)

    assert Enum.all?([2, 4, 6])
    refute Enum.all?([2, nil, 4])

    assert Enum.all?([])
  end

  test :any? do
    refute Enum.any?([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
    assert Enum.any?([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)

    refute Enum.any?([false, false, false])
    assert Enum.any?([false, true, false])

    refute Enum.any?([])
  end

  test :at do
    assert Enum.at([2, 4, 6], 0) == 2
    assert Enum.at([2, 4, 6], 2) == 6
    assert Enum.at([2, 4, 6], 4) == nil
    assert Enum.at([2, 4, 6], 4, :none) == :none
    assert Enum.at([2, 4, 6], -2) == 4
    assert Enum.at([2, 4, 6], -4) == nil
  end

  test :concat_1 do
    assert Enum.concat([[1, [2], 3], [4], [5, 6]]) == [1, [2], 3, 4, 5, 6]
    assert Enum.concat(1..3, []) == [1,2,3]

    assert Enum.concat([[], []]) == []
    assert Enum.concat([[]])     == []
    assert Enum.concat([])       == []

    assert Enum.concat([1..5, fn acc, _ -> acc end, [1]]) == [1,2,3,4,5,1]
  end

  test :concat_2 do
    assert Enum.concat([], [1]) == [1]
    assert Enum.concat([1, [2], 3], [4, 5]) == [1, [2], 3, 4, 5]
    assert Enum.concat(1..3, []) == [1,2,3]

    assert Enum.concat([], []) == []

    assert Enum.concat(fn acc, _ -> acc end, [1]) == [1]
  end

  test :fetch! do
    assert Enum.fetch!([2, 4, 6], 0) == 2
    assert Enum.fetch!([2, 4, 6], 2) == 6
    assert Enum.fetch!([2, 4, 6], -2) == 4

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!([2, 4, 6], 4)
    end

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!([2, 4, 6], -4)
    end
  end

  test :drop do
    assert Enum.drop([1, 2, 3], 0) == [1, 2, 3]
    assert Enum.drop([1, 2, 3], 1) == [2, 3]
    assert Enum.drop([1, 2, 3], 2) == [3]
    assert Enum.drop([1, 2, 3], 3) == []
    assert Enum.drop([1, 2, 3], 4) == []
    assert Enum.drop([1, 2, 3], -1) == [1, 2]
    assert Enum.drop([1, 2, 3], -2) == [1]
    assert Enum.drop([1, 2, 3], -4) == []
    assert Enum.drop([], 3) == []
  end

  test :drop_while do
    assert Enum.drop_while([1, 2, 3, 4, 3, 2, 1], fn(x) -> x <= 3 end) == [4, 3, 2, 1]
    assert Enum.drop_while([1, 2, 3], fn(_) -> false end) == [1, 2, 3]
    assert Enum.drop_while([1, 2, 3], fn(x) -> x <= 3 end) == []
    assert Enum.drop_while([], fn(_) -> false end) == []
  end

  test :find do
    assert Enum.find([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
    assert Enum.find([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
    assert Enum.find([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 3
  end

  test :find_value do
    assert Enum.find_value([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
    assert Enum.find_value([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
    assert Enum.find_value([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
  end

  test :find_index do
    assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
    assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
  end

  test :each do
    assert Enum.each([], fn(x) -> x end) == :ok
    assert Enum.each([1, 2, 3], fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
    assert Process.get(:enum_test_each) == 6
  after
    Process.delete(:enum_test_each)
  end

  test :fetch do
    assert Enum.fetch([2, 4, 6], 0) == {:ok, 2}
    assert Enum.fetch([2, 4, 6], 2) == {:ok, 6}
    assert Enum.fetch([2, 4, 6], 4) == :error
    assert Enum.fetch([2, 4, 6], -2) == {:ok, 4}
    assert Enum.fetch([2, 4, 6], -4) == :error
  end

  test :filter do
    assert Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [2]
    assert Enum.filter([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
  end

  test :filter_with_match do
    assert Enum.filter([1, 2, 3], &match?(1, &1)) == [1]
    assert Enum.filter([1, 2, 3], &match?(x when x < 3, &1)) == [1, 2]
    assert Enum.filter([1, 2, 3], &match?(_, &1)) == [1, 2, 3]
  end

  test :filter_map do
    assert Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
    assert Enum.filter_map([2, 4, 6], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
  end

  test :flat_map do
    assert Enum.flat_map([], fn(x) -> [x, x] end) == []
    assert Enum.flat_map([1, 2, 3], fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
    assert Enum.flat_map([1, 2, 3], fn(x) -> x..x+1 end) == [1, 2, 2, 3, 3, 4]
  end

  test :flat_map_reduce do
    assert Enum.flat_map_reduce([1, 2, 3], 0, &{[&1, &2], &1 + &2}) ==
           {[1, 0, 2, 1, 3, 3], 6}

    assert Enum.flat_map_reduce(1..100, 0, fn i, acc ->
      if acc < 3, do: {[i], acc + 1}, else: {:halt, acc}
    end) == {[1,2,3], 3}
  end

  test :group_by do
    assert Enum.group_by([], fn -> nil end) == %{}
    assert Enum.group_by(1..6, &rem(&1, 3)) ==
           %{0 => [6, 3], 1 => [4, 1], 2 => [5, 2]}

    result = Enum.group_by(1..6, %{3 => :default}, &rem(&1, 3))
    assert result[0] == [6, 3]
    assert result[3] == :default
  end

  test :into do
    assert Enum.into([a: 1, b: 2], %{}) == %{a: 1, b: 2}
    assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
    assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
    assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
  end

  test :intersperse do
    assert Enum.intersperse([], true) == []
    assert Enum.intersperse([1], true) == [1]
    assert Enum.intersperse([1,2,3], true) == [1, true, 2, true, 3]
  end

  test :join do
    assert Enum.join([], " = ") == ""
    assert Enum.join([1, 2, 3], " = ") == "1 = 2 = 3"
    assert Enum.join([1, "2", 3], " = ") == "1 = 2 = 3"
    assert Enum.join([1, 2, 3]) == "123"
    assert Enum.join(["", "", 1, 2, "", 3, "", "\n"], ";") == ";;1;2;;3;;\n"
    assert Enum.join([""]) == ""
  end

  test :map_join do
    assert Enum.map_join([], " = ", &(&1 * 2)) == ""
    assert Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) == "2 = 4 = 6"
    assert Enum.map_join([1, 2, 3], &(&1 * 2)) == "246"
    assert Enum.map_join(["", "", 1, 2, "", 3, "", "\n"], ";", &(&1)) == ";;1;2;;3;;\n"
    assert Enum.map_join([""], "", &(&1)) == ""
  end

  test :join_empty do
    fun = fn (acc, _) -> acc end
    assert Enum.join(fun, ".") == ""
    assert Enum.map_join(fun, ".", &(&1 + 0)) == ""
  end

  test :map do
    assert Enum.map([], fn x -> x * 2 end) == []
    assert Enum.map([1, 2, 3], fn x -> x * 2 end) == [2, 4, 6]
  end

  test :map_reduce do
    assert Enum.map_reduce([], 1, fn(x, acc) -> {x * 2, x + acc} end) == {[], 1}
    assert Enum.map_reduce([1, 2, 3], 1, fn(x, acc) -> {x * 2, x + acc} end) == {[2, 4, 6], 7}
  end

  test :partition do
    assert Enum.partition([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == {[2], [1, 3]}
    assert Enum.partition([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == {[2, 4, 6], []}
  end

  test :reduce do
    assert Enum.reduce([], 1, fn(x, acc) -> x + acc end) == 1
    assert Enum.reduce([1, 2, 3], 1, fn(x, acc) -> x + acc end) == 7

    assert Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end) == 6
    assert_raise Enum.EmptyError, fn ->
      Enum.reduce([], fn(x, acc) -> x + acc end)
    end
  end

  test :reject do
    assert Enum.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [1, 3]
    assert Enum.reject([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == []
  end

  test :reverse do
    assert Enum.reverse([]) == []
    assert Enum.reverse([1, 2, 3]) == [3, 2, 1]
    assert Enum.reverse([1, 2, 3], [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
  end

  test :reverse_slice do
    assert Enum.reverse_slice([1, 2, 3], 0, 0) == [1, 2, 3]
    assert Enum.reverse_slice([1, 2, 3], 0, 1) == [1, 2, 3]
    assert Enum.reverse_slice([1, 2, 3], 0, 2) == [2, 1, 3]
    assert Enum.reverse_slice([1, 2, 3], 0, 20000000) == [3, 2, 1]
    assert Enum.reverse_slice([1, 2, 3], 100, 2) == [1, 2, 3]
  end

  test :sample_1 do
    # corner cases, independent of the seed
    assert_raise Enum.EmptyError, fn -> Enum.sample([]) end
    assert Enum.sample([1]) == 1

    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample([1, 2]) == 2
    assert Enum.sample([1, 2, 3]) == 2
    assert Enum.sample([1, 2, 3, 4]) == 4
    assert Enum.sample([1, 2, 3, 4, 5]) == 1
    :random.seed(seed2)
    assert Enum.sample([1, 2]) == 1
    assert Enum.sample([1, 2, 3]) == 3
    assert Enum.sample([1, 2, 3, 4]) == 1
    assert Enum.sample([1, 2, 3, 4, 5]) == 5
  end

  test :sample_2 do
    # corner cases, independent of the seed
    assert_raise FunctionClauseError, fn -> Enum.sample([1,2], -1) end
    assert Enum.sample([], 0) == []
    assert Enum.sample([], 3) == []
    assert Enum.sample([1], 0) == []
    assert Enum.sample([1], 2) == [1]
    assert Enum.sample([1, 2], 0) == []
    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample([1, 2, 3, 4, 5], 1) == [2]
    assert Enum.sample([1, 2, 3, 4, 5], 2) == [4, 1]
    assert Enum.sample([1, 2, 3, 4, 5], 3) == [1, 3, 5]
    assert Enum.sample([1, 2, 3, 4, 5], 4) == [3, 5, 1, 2]
    assert Enum.sample([1, 2, 3, 4, 5], 5) == [1, 2, 5, 4, 3]
    assert Enum.sample([1, 2, 3, 4, 5], 6) == [2, 4, 5, 3, 1]
    :random.seed(seed2)
    assert Enum.sample([1, 2, 3, 4, 5], 1) == [1]
    assert Enum.sample([1, 2, 3, 4, 5], 2) == [1, 5]
    assert Enum.sample([1, 2, 3, 4, 5], 3) == [5, 1, 3]
    assert Enum.sample([1, 2, 3, 4, 5], 4) == [4, 5, 2, 1]
    assert Enum.sample([1, 2, 3, 4, 5], 5) == [4, 3, 1, 5, 2]
    assert Enum.sample([1, 2, 3, 4, 5], 6) == [5, 2, 1, 4, 3]
    # assert that every item in the sample comes from the input list
    list = for _<-1..100, do: make_ref
    for x <- Enum.sample(list, 50) do
      assert Enum.find(list, &(&1 == x))
    end
 end

  test :scan do
    assert Enum.scan([1,2,3,4,5], &(&1 + &2)) == [1,3,6,10,15]
    assert Enum.scan([], &(&1 + &2)) == []

    assert Enum.scan([1,2,3,4,5], 0, &(&1 + &2)) == [1,3,6,10,15]
    assert Enum.scan([], 0, &(&1 + &2)) == []
  end

  test :shuffle do
    # set a fixed seed so the test can be deterministic
    :random.seed(1374, 347975, 449264)
    assert Enum.shuffle([1, 2, 3, 4, 5]) == [2, 4, 1, 5, 3]
  end

  test :sort do
    assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
    assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
  end

  test :sort_by do
    collection = [
      [other_data: 1, sorted_data: 5],
      [other_data: 3, sorted_data: 4],
      [other_data: 4, sorted_data: 3],
      [other_data: 2, sorted_data: 2],
      [other_data: 5, sorted_data: 1]
    ]

    assert Enum.sort_by(
        collection,
        &(&1[:sorted_data])
      ) == [
        [other_data: 5, sorted_data: 1],
        [other_data: 2, sorted_data: 2],
        [other_data: 4, sorted_data: 3],
        [other_data: 3, sorted_data: 4],
        [other_data: 1, sorted_data: 5]
      ]
    assert Enum.sort_by(collection, &(&1[:sorted_data]), &>=/2) == collection
  end

  test :split do
    assert Enum.split([1, 2, 3], 0) == {[], [1, 2, 3]}
    assert Enum.split([1, 2, 3], 1) == {[1], [2, 3]}
    assert Enum.split([1, 2, 3], 2) == {[1, 2], [3]}
    assert Enum.split([1, 2, 3], 3) == {[1, 2, 3], []}
    assert Enum.split([1, 2, 3], 4) == {[1, 2, 3], []}
    assert Enum.split([], 3) == {[], []}
    assert Enum.split([1, 2, 3], -1) == {[1, 2], [3]}
    assert Enum.split([1, 2, 3], -2) == {[1], [2, 3]}
    assert Enum.split([1, 2, 3], -3) == {[], [1, 2, 3]}
    assert Enum.split([1, 2, 3], -10) == {[], [1, 2, 3]}
  end

  test :split_while do
    assert Enum.split_while([1, 2, 3], fn(_) -> false end) == {[], [1, 2, 3]}
    assert Enum.split_while([1, 2, 3], fn(_) -> true end) == {[1, 2, 3], []}
    assert Enum.split_while([1, 2, 3], fn(x) -> x > 2 end) == {[], [1, 2, 3]}
    assert Enum.split_while([1, 2, 3], fn(x) -> x > 3 end) == {[], [1, 2, 3]}
    assert Enum.split_while([1, 2, 3], fn(x) -> x < 3 end) == {[1, 2], [3]}
    assert Enum.split_while([], fn(_) -> true end) == {[], []}
  end

  test :sum do
    assert Enum.sum([]) == 0
    assert Enum.sum([1]) == 1
    assert Enum.sum([1, 2, 3]) == 6
    assert Enum.sum([1.1, 2.2, 3.3]) == 6.6
    assert_raise ArithmeticError, fn ->
      Enum.sum([{}])
    end
    assert_raise ArithmeticError, fn ->
      Enum.sum([1,{}])
    end
  end

  test :take do
    assert Enum.take([1, 2, 3], 0) == []
    assert Enum.take([1, 2, 3], 1) == [1]
    assert Enum.take([1, 2, 3], 2) == [1, 2]
    assert Enum.take([1, 2, 3], 3) == [1, 2, 3]
    assert Enum.take([1, 2, 3], 4) == [1, 2, 3]
    assert Enum.take([1, 2, 3], -1) == [3]
    assert Enum.take([1, 2, 3], -2) == [2, 3]
    assert Enum.take([1, 2, 3], -4) == [1, 2, 3]
    assert Enum.take([], 3) == []
  end

  test :take_every do
    assert Enum.take_every([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2) == [1, 3, 5, 7, 9]
    assert Enum.take_every([], 2) == []
    assert Enum.take_every([1, 2], 2) == [1]
    assert Enum.take_every([1, 2, 3], 0) == []
    assert_raise FunctionClauseError, fn ->
      Enum.take_every([1, 2, 3], -1)
    end
  end

  test :take_while do
    assert Enum.take_while([1, 2, 3], fn(x) -> x > 3 end) == []
    assert Enum.take_while([1, 2, 3], fn(x) -> x <= 1 end) == [1]
    assert Enum.take_while([1, 2, 3], fn(x) -> x <= 3 end) == [1, 2, 3]
    assert Enum.take_while([], fn(_) -> true end) == []
  end

  test :to_list do
    assert Enum.to_list([]) == []
    assert Enum.to_list(1 .. 3) == [1, 2, 3]
  end

  test :uniq do
    assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
    assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
  end

  test :zip do
    assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([], [1]) == []
    assert Enum.zip([1], []) == []
    assert Enum.zip([], [])  == []
  end

  test :unzip do
    assert Enum.unzip([{:a, 1}, {:b, 2}, {:c, 3}]) == {[:a, :b, :c], [1, 2, 3]}
    assert Enum.unzip([]) == {[], []}
    assert Enum.unzip(%{a: 1, b: 2}) == {[:a, :b], [1, 2]}
    assert Enum.unzip([foo: "a", bar: "b"]) == {[:foo, :bar], ["a", "b"]}

    assert_raise FunctionClauseError, fn -> Enum.unzip([{:a, 1}, {:b, 2, "foo"}]) end
    assert_raise FunctionClauseError, fn -> Enum.unzip([{1, 2, {3, {4, 5}}}]) end
    assert_raise FunctionClauseError, fn -> Enum.unzip([1, 2, 3]) end
  end

  test :with_index do
    assert Enum.with_index([]) == []
    assert Enum.with_index([1,2,3]) == [{1,0},{2,1},{3,2}]
  end

  test :max do
    assert Enum.max([1]) == 1
    assert Enum.max([1, 2, 3]) == 3
    assert Enum.max([1, [], :a, {}]) == []
    assert_raise Enum.EmptyError, fn ->
      Enum.max([])
    end
  end

  test :max_by do
    assert Enum.max_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "aaa"
    assert_raise Enum.EmptyError, fn ->
      Enum.max_by([], fn(x) -> String.length(x) end)
    end
  end

  test :min do
    assert Enum.min([1]) == 1
    assert Enum.min([1, 2, 3]) == 1
    assert Enum.min([[], :a, {}]) == :a
    assert_raise Enum.EmptyError, fn ->
      Enum.min([])
    end
  end

  test :min_by do
    assert Enum.min_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "a"
    assert_raise Enum.EmptyError, fn ->
      Enum.min_by([], fn(x) -> String.length(x) end)
    end
  end

  test :minmax do
    assert Enum.minmax([1]) == {1, 1}
    assert Enum.minmax([2, 3, 1]) == {1, 3}
    assert Enum.minmax([[], :a, {}]) == {:a, []}
    assert_raise Enum.EmptyError, fn ->
      Enum.minmax([])
    end
  end

  test :minmax_by do
    assert Enum.minmax_by(["aaa", "a", "aa"], fn(x) -> String.length(x) end) == {"a", "aaa"}
    assert_raise Enum.EmptyError, fn ->
      Enum.minmax_by([], fn(x) -> String.length(x) end)
    end
  end

  test :chunk do
    assert Enum.chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4]]
    assert Enum.chunk([1, 2, 3, 4, 5], 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2) == [[1, 2, 3], [3, 4, 5]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 2, 3) == [[1, 2], [4, 5]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) == [[1, 2, 3], [4, 5, 6]]
    assert Enum.chunk([1, 2, 3, 4, 5], 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
  end

  test :chunk_by do
    assert Enum.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1)) == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]
    assert Enum.chunk_by([1, 2, 3, 4], fn _ -> true end) == [[1, 2, 3, 4]]
    assert Enum.chunk_by([], fn _ -> true end) == []
    assert Enum.chunk_by([1], fn _ -> true end) == [[1]]
  end

  test :slice do
    assert Enum.slice([1,2,3,4,5], 0, 0) == []
    assert Enum.slice([1,2,3,4,5], 0, 1) == [1]
    assert Enum.slice([1,2,3,4,5], 0, 2) == [1, 2]
    assert Enum.slice([1,2,3,4,5], 1, 2) == [2, 3]
    assert Enum.slice([1,2,3,4,5], 1, 0) == []
    assert Enum.slice([1,2,3,4,5], 2, 5) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 2, 6) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 5, 5) == []
    assert Enum.slice([1,2,3,4,5], 6, 5) == []
    assert Enum.slice([1,2,3,4,5], 6, 0) == []
    assert Enum.slice([1,2,3,4,5], -6, 0) == []
    assert Enum.slice([1,2,3,4,5], -6, 5) == []
    assert Enum.slice([1,2,3,4,5], -2, 5) == [4, 5]
    assert Enum.slice([1,2,3,4,5], -3, 1) == [3]
  end

  test :slice_range do
    assert Enum.slice([1,2,3,4,5], 0..0) == [1]
    assert Enum.slice([1,2,3,4,5], 0..1) == [1, 2]
    assert Enum.slice([1,2,3,4,5], 0..2) == [1, 2, 3]
    assert Enum.slice([1,2,3,4,5], 1..2) == [2, 3]
    assert Enum.slice([1,2,3,4,5], 1..0) == []
    assert Enum.slice([1,2,3,4,5], 2..5) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 2..6) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 4..4) == [5]
    assert Enum.slice([1,2,3,4,5], 5..5) == []
    assert Enum.slice([1,2,3,4,5], 6..5) == []
    assert Enum.slice([1,2,3,4,5], 6..0) == []
    assert Enum.slice([1,2,3,4,5], -6..0) == []
    assert Enum.slice([1,2,3,4,5], -6..5) == []
    assert Enum.slice([1,2,3,4,5], -5..-1) == [1, 2, 3, 4, 5]
    assert Enum.slice([1,2,3,4,5], -5..-3) == [1, 2, 3]
    assert Enum.slice([1,2,3,4,5], -6..-1) == []
    assert Enum.slice([1,2,3,4,5], -6..-3) == []
  end
end

defmodule EnumTest.Range do
  use ExUnit.Case, async: true

  test :all? do
    range = 0..5
    refute Enum.all?(range, fn(x) -> rem(x, 2) == 0 end)

    range = 0..1
    assert Enum.all?(range, fn(x) -> x < 2 end)
    assert Enum.all?(range)

    range = 1..0
    assert Enum.all?(range)
  end

  test :any? do
    range = 0..5
    refute Enum.any?(range, &(&1 > 10))

    range = 0..5
    assert Enum.any?(range, &(&1 > 3))

    range = 1..0
    assert Enum.any?(range)
  end

  test :fetch! do
    assert Enum.fetch!(2..6, 0) == 2
    assert Enum.fetch!(2..6, 4) == 6
    assert Enum.fetch!(2..6, -1) == 6
    assert Enum.fetch!(2..6, -2) == 5
    assert Enum.fetch!(-2..-6, 0) == -2
    assert Enum.fetch!(-2..-6, 4) == -6

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!(2..6, 8)
    end

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!(-2..-6, 8)
    end

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!(2..6, -8)
    end
  end

  test :count do
    range = 1..5
    assert Enum.count(range) == 5
    range = 1..1
    assert Enum.count(range) == 1
  end

  test :count_fun do
    range = 1..5
    assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 2
    range = 1..1
    assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 0
  end

  test :chunk do
    assert Enum.chunk(1..5, 2) == [[1, 2], [3, 4]]
    assert Enum.chunk(1..5, 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
    assert Enum.chunk(1..6, 3, 2) == [[1, 2, 3], [3, 4, 5]]
    assert Enum.chunk(1..6, 2, 3) == [[1, 2], [4, 5]]
    assert Enum.chunk(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
    assert Enum.chunk(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
  end

  test :chunk_by do
    assert Enum.chunk_by(1..4, fn _ -> true end) == [[1, 2, 3, 4]]
    assert Enum.chunk_by(1..4, &(rem(&1, 2) == 1)) == [[1], [2], [3], [4]]
  end

  test :drop do
    range = 1..3
    assert Enum.drop(range, 0) == [1, 2, 3]
    assert Enum.drop(range, 1) == [2, 3]
    assert Enum.drop(range, 2) == [3]
    assert Enum.drop(range, 3) == []
    assert Enum.drop(range, 4) == []
    assert Enum.drop(range, -1) == [1, 2]
    assert Enum.drop(range, -2) == [1]
    assert Enum.drop(range, -4) == []

    range = 1..0
    assert Enum.drop(range, 3) == []
  end

  test :drop_while do
    range = 0..6
    assert Enum.drop_while(range, fn(x) -> x <= 3 end) == [4, 5, 6]
    assert Enum.drop_while(range, fn(_) -> false end) == [0, 1, 2, 3, 4, 5, 6]

    range = 0..3
    assert Enum.drop_while(range, fn(x) -> x <= 3 end) == []

    range = 1..0
    assert Enum.drop_while(range, fn(_) -> false end) == [1, 0]
  end

  test :find do
    range = 2..6
    assert Enum.find(range, fn(x) -> rem(x, 2) == 0 end) == 2
    assert Enum.find(range, fn(x) -> rem(x, 2) == 1 end) == 3
    assert Enum.find(range, fn _ -> false end) == nil
    assert Enum.find(range, 0, fn _ -> false end) == 0
  end

  test :find_value do
    range = 2..6
    assert Enum.find_value(range, fn(x) -> rem(x, 2) == 1 end)
  end

  test :find_index do
    range = 2..6
    assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 1
  end

  test :empty? do
    range = 1..0
    refute Enum.empty?(range)

    range = 1..2
    refute Enum.empty?(range)
  end

  test :each do
    try do
      range = 1..0
      assert Enum.each(range, fn(x) -> x end) == :ok

      range = 1..3
      assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
      assert Process.get(:enum_test_each) == 6
    after
      Process.delete(:enum_test_each)
    end

    try do
      range = -1..-3
      assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
      assert Process.get(:enum_test_each) == -6
    after
      Process.delete(:enum_test_each)
    end
  end

  test :filter do
    range = 1..3
    assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2]

    range = 1..6
    assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
  end

  test :filter_with_match do
    range = 1..3
    assert Enum.filter(range, &match?(1, &1)) == [1]
    assert Enum.filter(range, &match?(x when x < 3, &1)) == [1, 2]
    assert Enum.filter(range, &match?(_, &1)) == [1, 2, 3]
  end

  test :filter_map do
    range = 1..3
    assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]

    range = 2..6
    assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
  end

  test :flat_map do
    range = 1..3
    assert Enum.flat_map(range, fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
  end

  test :intersperse do
    range = 1..0
    assert Enum.intersperse(range, true) == [1, true, 0]

    range = 1..3
    assert Enum.intersperse(range, false) == [1, false, 2, false, 3]
  end

  test :into do
    assert Enum.into([a: 1, b: 2], %{}) == %{a: 1, b: 2}
    assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
    assert Enum.into(3..5, [1, 2]) == [1, 2, 3, 4, 5]
    assert Enum.into(1..5, []) == [1, 2, 3, 4, 5]
    assert Enum.into(1..5, [], fn x -> x * 2 end) == [2, 4, 6, 8, 10]
    assert Enum.into(1..3, "numbers: ", &to_string/1) == "numbers: 123"
  end

  test :join do
    range = 1..0
    assert Enum.join(range, " = ") == "1 = 0"

    range = 1..3
    assert Enum.join(range, " = ") == "1 = 2 = 3"
    assert Enum.join(range) == "123"
  end

  test :map_join do
    range = 1..0
    assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 0"

    range = 1..3
    assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 4 = 6"
    assert Enum.map_join(range, &(&1 * 2)) == "246"
  end

  test :map do
    range = 1..3
    assert Enum.map(range, fn x -> x * 2 end) == [2, 4, 6]

    range = -1..-3
    assert Enum.map(range, fn x -> x * 2 end) == [-2, -4, -6]
  end

  test :map_reduce do
    range = 1..0
    assert Enum.map_reduce(range, 1, fn(x, acc) -> {x * 2, x + acc} end) == {[2, 0], 2}

    range = 1..3
    assert Enum.map_reduce(range, 1, fn(x, acc) -> {x * 2, x + acc} end) == {[2, 4, 6], 7}
  end

  test :max do
    assert Enum.max(1..1) == 1
    assert Enum.max(1..3) == 3
    assert Enum.max(3..1) == 3
  end

  test :max_by do
    assert Enum.max_by(1..1, fn(x) -> :math.pow(-2, x) end) == 1
    assert Enum.max_by(1..3, fn(x) -> :math.pow(-2, x) end) == 2
  end

  test :min do
    assert Enum.min(1..1) == 1
    assert Enum.min(1..3) == 1
  end

  test :min_by do
    assert Enum.min_by(1..1, fn(x) -> :math.pow(-2, x) end) == 1
    assert Enum.min_by(1..3, fn(x) -> :math.pow(-2, x) end) == 3
  end

  test :partition do
    range = 1..3
    assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == {[2], [1, 3]}
  end

  test :reduce do
    range = 1..0
    assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 2

    range = 1..3
    assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 7

    range = 1..3
    assert Enum.reduce(range, fn(x, acc) -> x + acc end) == 6
  end

  test :reject do
    range = 1..3
    assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3]

    range = 1..6
    assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3, 5]
  end

  test :reverse do
    assert Enum.reverse(0..0) == [0]
    assert Enum.reverse(1..3) == [3, 2, 1]
    assert Enum.reverse(1..3, 4..6) == [3, 2, 1, 4, 5, 6]
    assert Enum.reverse([1, 2, 3], 4..6) == [3, 2, 1, 4, 5, 6]
    assert Enum.reverse(1..3, [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
  end

  test :reverse_slice do
    assert Enum.reverse_slice(1..6, 2, 0) == [1, 2, 3, 4, 5, 6]
    assert Enum.reverse_slice(1..6, 2, 2) == [1, 2, 4, 3, 5, 6]
    assert Enum.reverse_slice(1..6, 2, 4) == [1, 2, 6, 5, 4, 3]
    assert Enum.reverse_slice(1..6, 2, 10000000) == [1, 2, 6, 5, 4, 3]
    assert Enum.reverse_slice(1..6, 10000000, 4) == [1, 2, 3, 4, 5, 6]
  end

  test :sample_1 do
    # corner cases, independent of the seed
    assert Enum.sample(1..1) == 1

    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample(1..2) == 2
    assert Enum.sample(1..3) == 2
    assert Enum.sample(1..4) == 4
    assert Enum.sample(1..5) == 1
    :random.seed(seed1)
    assert Enum.sample(?a..?z) == ?i
    :random.seed(seed1)
    assert Enum.sample(?0..?9) == ?8
    :random.seed(seed2)
    assert Enum.sample(1..2) == 1
    assert Enum.sample(1..3) == 3
    assert Enum.sample(1..4) == 1
    assert Enum.sample(1..5) == 5
    :random.seed(seed2)
    assert Enum.sample(?a..?z) == ?a
    :random.seed(seed2)
    assert Enum.sample(?0..?9) == ?0
  end

  test :sample_2 do
    # corner cases, independent of the seed
    assert_raise FunctionClauseError, fn -> Enum.sample(1..2, -1) end
    assert Enum.sample(1..1, 0) == []
    assert Enum.sample(1..1, 2) == [1]
    assert Enum.sample(1..2, 0) == []

    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample(1..5, 1) == [2]
    assert Enum.sample(1..5, 2) == [4, 1]
    assert Enum.sample(1..5, 3) == [1, 3, 5]
    assert Enum.sample(1..5, 4) == [3, 5, 1, 2]
    assert Enum.sample(1..5, 5) == [1, 2, 5, 4, 3]
    assert Enum.sample(1..5, 6) == [2, 4, 5, 3, 1]
    :random.seed(seed2)
    assert Enum.sample(1..5, 1) == [1]
    assert Enum.sample(1..5, 2) == [1, 5]
    assert Enum.sample(1..5, 3) == [5, 1, 3]
    assert Enum.sample(1..5, 4) == [4, 5, 2, 1]
    assert Enum.sample(1..5, 5) == [4, 3, 1, 5, 2]
    assert Enum.sample(1..5, 6) == [5, 2, 1, 4, 3]
    :random.seed(seed1)
    assert Enum.sample(?a..?z, 1) == 'i'
    assert Enum.sample(?a..?z, 2) == 'cm'
    assert Enum.sample(?a..?z, 3) == 'alp'
    assert Enum.sample(?a..?z, 4) == 'tzmd'
    assert Enum.sample(?a..?z, 5) == 'cuxvb'
    :random.seed(seed2)
    assert Enum.sample(?a..?z, 1) == 'a'
    assert Enum.sample(?a..?z, 2) == 'wk'
    assert Enum.sample(?a..?z, 3) == 'ayj'
    assert Enum.sample(?a..?z, 4) == 'rbcm'
    assert Enum.sample(?a..?z, 5) == 'rhzju'
    :random.seed(seed1)
    assert Enum.sample(?0..?9, 1) == '8'
    assert Enum.sample(?0..?9, 2) == '07'
    assert Enum.sample(?0..?9, 3) == '018'
    assert Enum.sample(?0..?9, 4) == '0856'
    assert Enum.sample(?0..?9, 5) == '03698'
    :random.seed(seed2)
    assert Enum.sample(?0..?9, 1) == '0'
    assert Enum.sample(?0..?9, 2) == '48'
    assert Enum.sample(?0..?9, 3) == '590'
    assert Enum.sample(?0..?9, 4) == '4805'
    assert Enum.sample(?0..?9, 5) == '81945'
  end

  test :scan do
    assert Enum.scan(1..5, &(&1 + &2)) == [1,3,6,10,15]
    assert Enum.scan(1..5, 0, &(&1 + &2)) == [1,3,6,10,15]
  end

  test :shuffle do
    # set a fixed seed so the test can be deterministic
    :random.seed(1374, 347975, 449264)
    assert Enum.shuffle(1..5) == [2, 4, 1, 5, 3]
  end

  test :slice do
    assert Enum.slice(1..5, 0, 0) == []
    assert Enum.slice(1..5, 0, 1) == [1]
    assert Enum.slice(1..5, 0, 2) == [1, 2]
    assert Enum.slice(1..5, 1, 2) == [2, 3]
    assert Enum.slice(1..5, 1, 0) == []
    assert Enum.slice(1..5, 2, 5) == [3, 4, 5]
    assert Enum.slice(1..5, 2, 6) == [3, 4, 5]
    assert Enum.slice(1..5, 5, 5) == []
    assert Enum.slice(1..5, 6, 5) == []
    assert Enum.slice(1..5, 6, 0) == []
    assert Enum.slice(1..5, -6, 0) == []
    assert Enum.slice(1..5, -6, 5) == []
    assert Enum.slice(1..5, -2, 5) == [4, 5]
    assert Enum.slice(1..5, -3, 1) == [3]
  end

  test :slice_range do
    assert Enum.slice(1..5, 0..0) == [1]
    assert Enum.slice(1..5, 0..1) == [1, 2]
    assert Enum.slice(1..5, 0..2) == [1, 2, 3]
    assert Enum.slice(1..5, 1..2) == [2, 3]
    assert Enum.slice(1..5, 1..0) == []
    assert Enum.slice(1..5, 2..5) == [3, 4, 5]
    assert Enum.slice(1..5, 2..6) == [3, 4, 5]
    assert Enum.slice(1..5, 4..4) == [5]
    assert Enum.slice(1..5, 5..5) == []
    assert Enum.slice(1..5, 6..5) == []
    assert Enum.slice(1..5, 6..0) == []
    assert Enum.slice(1..5, -6..0) == []
    assert Enum.slice(1..5, -6..5) == []
    assert Enum.slice(1..5, -5..-1) == [1, 2, 3, 4, 5]
    assert Enum.slice(1..5, -5..-3) == [1, 2, 3]
    assert Enum.slice(1..5, -6..-1) == []
    assert Enum.slice(1..5, -6..-3) == []
  end

  test :sort do
    assert Enum.sort(3..1) == [1, 2, 3]
    assert Enum.sort(2..1) == [1, 2]
    assert Enum.sort(1..1) == [1]

    assert Enum.sort(3..1, &(&1 > &2)) == [3, 2, 1]
    assert Enum.sort(2..1, &(&1 > &2)) == [2, 1]
    assert Enum.sort(1..1, &(&1 > &2)) == [1]
  end

  test :split do
    range = 1..3
    assert Enum.split(range, 0) == {[], [1, 2, 3]}
    assert Enum.split(range, 1) == {[1], [2, 3]}
    assert Enum.split(range, 2) == {[1, 2], [3]}
    assert Enum.split(range, 3) == {[1, 2, 3], []}
    assert Enum.split(range, 4) == {[1, 2, 3], []}
    assert Enum.split(range, -1) == {[1, 2], [3]}
    assert Enum.split(range, -2) == {[1], [2, 3]}
    assert Enum.split(range, -3) == {[], [1, 2, 3]}
    assert Enum.split(range, -10) == {[], [1, 2, 3]}

    range = 1..0
    assert Enum.split(range, 3) == {[1, 0], []}
  end

  test :split_while do
    range = 1..3
    assert Enum.split_while(range, fn(_) -> false end) == {[], [1, 2, 3]}
    assert Enum.split_while(range, fn(_) -> true end) == {[1, 2, 3], []}
    assert Enum.split_while(range, fn(x) -> x > 2 end) == {[], [1, 2, 3]}
    assert Enum.split_while(range, fn(x) -> x > 3 end) == {[], [1, 2, 3]}
    assert Enum.split_while(range, fn(x) -> x < 3 end) == {[1, 2], [3]}

    range = 1..0
    assert Enum.split_while(range, fn(_) -> true end) == {[1, 0], []}
  end

  test :sum do
    assert Enum.sum(1..1) == 1
    assert Enum.sum(1..3) == 6
  end

  test :take do
    range = 1..3
    assert Enum.take(range, 0) == []
    assert Enum.take(range, 1) == [1]
    assert Enum.take(range, 2) == [1, 2]
    assert Enum.take(range, 3) == [1, 2, 3]
    assert Enum.take(range, 4) == [1, 2, 3]
    assert Enum.take(range, -1) == [3]
    assert Enum.take(range, -2) == [2, 3]
    assert Enum.take(range, -4) == [1, 2, 3]

    range = 1..0
    assert Enum.take(range, 3) == [1, 0]
  end

  test :take_every do
    assert Enum.take_every(1..10, 2) == [1, 3, 5, 7, 9]
    assert Enum.take_every(1..2, 2) == [1]
    assert Enum.take_every(1..3, 0) == []
    assert_raise FunctionClauseError, fn ->
      Enum.take_every(1..3, -1)
    end
  end

  test :take_while do
    range = 1..3
    assert Enum.take_while(range, fn(x) -> x > 3 end) == []
    assert Enum.take_while(range, fn(x) -> x <= 1 end) == [1]
    assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1, 2, 3]
    assert Enum.take_while([], fn(_) -> true end) == []
  end

  test :uniq do
    assert Enum.uniq(1..3) == [1, 2, 3]
    assert Enum.uniq(1..3, fn x -> x end) == [1, 2, 3]
  end

  test :zip do
    assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}]

    assert Enum.zip(1..2, [:a, :b]) == [{1, :a}, {2, :b}]
    assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}]
    assert Enum.zip(1..2, [:a, :b, :c, :d]) == [{1, :a}, {2, :b}]

    assert Enum.zip(1..2, 1..2) == [{1, 1}, {2, 2}]
    assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}]
    assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
  end

  test :with_index do
    assert Enum.with_index(1..3) == [{1,0},{2,1},{3,2}]
  end
end

defmodule EnumTest.SideEffects do
  use ExUnit.Case, async: true

  import ExUnit.CaptureIO
  import PathHelpers

  test "take with side effects" do
    stream = Stream.unfold(1, fn x -> IO.puts x; {x, x + 1} end)
    assert capture_io(fn ->
      Enum.take(stream, 1)
    end) == "1\n"
  end

  test "take does not consume next without a need" do
    path = tmp_path("oneliner.txt")
    File.mkdir(Path.dirname(path))

    try do
      File.write!(path, "ONE")

      File.open!(path, [], fn file ->
        iterator = IO.stream(file, :line)
        assert Enum.take(iterator, 1) == ["ONE"]
        assert Enum.take(iterator, 5) == []
      end)
    after
      File.rm(path)
    end
  end

  test "take with no item works as no-op" do
    iterator = File.stream!(fixture_path("unknown.txt"))

    assert Enum.take(iterator, 0) == []
    assert Enum.take(iterator, 0) == []
    assert Enum.take(iterator, 0) == []
    assert Enum.take(iterator, 0) == []
  end
end

Code.require_file "test_helper.exs", __DIR__

defmodule EnumTest.List do
  use ExUnit.Case, async: true

  test :empty? do
    assert Enum.empty?([])
    refute Enum.empty?([1, 2, 3])
    refute Enum.empty?(1..3)
  end

  test :member? do
    assert Enum.member?([1, 2, 3], 2)
    refute Enum.member?([], 0)
    refute Enum.member?([1, 2, 3], 0)
    assert Enum.member?(1..3, 2)
    refute Enum.member?(1..3, 0)
  end

  test :count do
    assert Enum.count([1, 2, 3]) == 3
    assert Enum.count([]) == 0
  end

  test :count_fun do
    assert Enum.count([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == 1
    assert Enum.count([], fn(x) -> rem(x, 2) == 0 end) == 0
  end

  test :all? do
    assert Enum.all?([2, 4, 6], fn(x) -> rem(x, 2) == 0 end)
    refute Enum.all?([2, 3, 4], fn(x) -> rem(x, 2) == 0 end)

    assert Enum.all?([2, 4, 6])
    refute Enum.all?([2, nil, 4])

    assert Enum.all?([])
  end

  test :any? do
    refute Enum.any?([2, 4, 6], fn(x) -> rem(x, 2) == 1 end)
    assert Enum.any?([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)

    refute Enum.any?([false, false, false])
    assert Enum.any?([false, true, false])

    refute Enum.any?([])
  end

  test :at do
    assert Enum.at([2, 4, 6], 0) == 2
    assert Enum.at([2, 4, 6], 2) == 6
    assert Enum.at([2, 4, 6], 4) == nil
    assert Enum.at([2, 4, 6], 4, :none) == :none
    assert Enum.at([2, 4, 6], -2) == 4
    assert Enum.at([2, 4, 6], -4) == nil
  end

  test :concat_1 do
    assert Enum.concat([[1, [2], 3], [4], [5, 6]]) == [1, [2], 3, 4, 5, 6]
    assert Enum.concat(1..3, []) == [1,2,3]

    assert Enum.concat([[], []]) == []
    assert Enum.concat([[]])     == []
    assert Enum.concat([])       == []

    assert Enum.concat([1..5, fn acc, _ -> acc end, [1]]) == [1,2,3,4,5,1]
  end

  test :concat_2 do
    assert Enum.concat([], [1]) == [1]
    assert Enum.concat([1, [2], 3], [4, 5]) == [1, [2], 3, 4, 5]
    assert Enum.concat(1..3, []) == [1,2,3]

    assert Enum.concat([], []) == []

    assert Enum.concat(fn acc, _ -> acc end, [1]) == [1]
  end

  test :fetch! do
    assert Enum.fetch!([2, 4, 6], 0) == 2
    assert Enum.fetch!([2, 4, 6], 2) == 6
    assert Enum.fetch!([2, 4, 6], -2) == 4

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!([2, 4, 6], 4)
    end

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!([2, 4, 6], -4)
    end
  end

  test :drop do
    assert Enum.drop([1, 2, 3], 0) == [1, 2, 3]
    assert Enum.drop([1, 2, 3], 1) == [2, 3]
    assert Enum.drop([1, 2, 3], 2) == [3]
    assert Enum.drop([1, 2, 3], 3) == []
    assert Enum.drop([1, 2, 3], 4) == []
    assert Enum.drop([1, 2, 3], -1) == [1, 2]
    assert Enum.drop([1, 2, 3], -2) == [1]
    assert Enum.drop([1, 2, 3], -4) == []
    assert Enum.drop([], 3) == []
  end

  test :drop_while do
    assert Enum.drop_while([1, 2, 3, 4, 3, 2, 1], fn(x) -> x <= 3 end) == [4, 3, 2, 1]
    assert Enum.drop_while([1, 2, 3], fn(_) -> false end) == [1, 2, 3]
    assert Enum.drop_while([1, 2, 3], fn(x) -> x <= 3 end) == []
    assert Enum.drop_while([], fn(_) -> false end) == []
  end

  test :find do
    assert Enum.find([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
    assert Enum.find([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
    assert Enum.find([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 3
  end

  test :find_value do
    assert Enum.find_value([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
    assert Enum.find_value([2, 4, 6], 0, fn(x) -> rem(x, 2) == 1 end) == 0
    assert Enum.find_value([2, 3, 4], fn(x) -> rem(x, 2) == 1 end)
  end

  test :find_index do
    assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
    assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
  end

  test :each do
    assert Enum.each([], fn(x) -> x end) == :ok
    assert Enum.each([1, 2, 3], fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
    assert Process.get(:enum_test_each) == 6
  after
    Process.delete(:enum_test_each)
  end

  test :fetch do
    assert Enum.fetch([2, 4, 6], 0) == {:ok, 2}
    assert Enum.fetch([2, 4, 6], 2) == {:ok, 6}
    assert Enum.fetch([2, 4, 6], 4) == :error
    assert Enum.fetch([2, 4, 6], -2) == {:ok, 4}
    assert Enum.fetch([2, 4, 6], -4) == :error
  end

  test :filter do
    assert Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [2]
    assert Enum.filter([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
  end

  test :filter_with_match do
    assert Enum.filter([1, 2, 3], &match?(1, &1)) == [1]
    assert Enum.filter([1, 2, 3], &match?(x when x < 3, &1)) == [1, 2]
    assert Enum.filter([1, 2, 3], &match?(_, &1)) == [1, 2, 3]
  end

  test :filter_map do
    assert Enum.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]
    assert Enum.filter_map([2, 4, 6], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
  end

  test :flat_map do
    assert Enum.flat_map([], fn(x) -> [x, x] end) == []
    assert Enum.flat_map([1, 2, 3], fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
    assert Enum.flat_map([1, 2, 3], fn(x) -> x..x+1 end) == [1, 2, 2, 3, 3, 4]
  end

  test :flat_map_reduce do
    assert Enum.flat_map_reduce([1, 2, 3], 0, &{[&1, &2], &1 + &2}) ==
           {[1, 0, 2, 1, 3, 3], 6}

    assert Enum.flat_map_reduce(1..100, 0, fn i, acc ->
      if acc < 3, do: {[i], acc + 1}, else: {:halt, acc}
    end) == {[1,2,3], 3}
  end

  test :group_by do
    assert Enum.group_by([], fn -> nil end) == %{}
    assert Enum.group_by(1..6, &rem(&1, 3)) ==
           %{0 => [6, 3], 1 => [4, 1], 2 => [5, 2]}

    result = Enum.group_by(1..6, %{3 => :default}, &rem(&1, 3))
    assert result[0] == [6, 3]
    assert result[3] == :default
  end

  test :into do
    assert Enum.into([a: 1, b: 2], %{}) == %{a: 1, b: 2}
    assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
    assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
    assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
  end

  test :intersperse do
    assert Enum.intersperse([], true) == []
    assert Enum.intersperse([1], true) == [1]
    assert Enum.intersperse([1,2,3], true) == [1, true, 2, true, 3]
  end

  test :join do
    assert Enum.join([], " = ") == ""
    assert Enum.join([1, 2, 3], " = ") == "1 = 2 = 3"
    assert Enum.join([1, "2", 3], " = ") == "1 = 2 = 3"
    assert Enum.join([1, 2, 3]) == "123"
    assert Enum.join(["", "", 1, 2, "", 3, "", "\n"], ";") == ";;1;2;;3;;\n"
    assert Enum.join([""]) == ""
  end

  test :map_join do
    assert Enum.map_join([], " = ", &(&1 * 2)) == ""
    assert Enum.map_join([1, 2, 3], " = ", &(&1 * 2)) == "2 = 4 = 6"
    assert Enum.map_join([1, 2, 3], &(&1 * 2)) == "246"
    assert Enum.map_join(["", "", 1, 2, "", 3, "", "\n"], ";", &(&1)) == ";;1;2;;3;;\n"
    assert Enum.map_join([""], "", &(&1)) == ""
  end

  test :join_empty do
    fun = fn (acc, _) -> acc end
    assert Enum.join(fun, ".") == ""
    assert Enum.map_join(fun, ".", &(&1 + 0)) == ""
  end

  test :map do
    assert Enum.map([], fn x -> x * 2 end) == []
    assert Enum.map([1, 2, 3], fn x -> x * 2 end) == [2, 4, 6]
  end

  test :map_reduce do
    assert Enum.map_reduce([], 1, fn(x, acc) -> {x * 2, x + acc} end) == {[], 1}
    assert Enum.map_reduce([1, 2, 3], 1, fn(x, acc) -> {x * 2, x + acc} end) == {[2, 4, 6], 7}
  end

  test :partition do
    assert Enum.partition([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == {[2], [1, 3]}
    assert Enum.partition([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == {[2, 4, 6], []}
  end

  test :reduce do
    assert Enum.reduce([], 1, fn(x, acc) -> x + acc end) == 1
    assert Enum.reduce([1, 2, 3], 1, fn(x, acc) -> x + acc end) == 7

    assert Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end) == 6
    assert_raise Enum.EmptyError, fn ->
      Enum.reduce([], fn(x, acc) -> x + acc end)
    end
  end

  test :reject do
    assert Enum.reject([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) == [1, 3]
    assert Enum.reject([2, 4, 6], fn(x) -> rem(x, 2) == 0 end) == []
  end

  test :reverse do
    assert Enum.reverse([]) == []
    assert Enum.reverse([1, 2, 3]) == [3, 2, 1]
    assert Enum.reverse([1, 2, 3], [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
  end

  test :reverse_slice do
    assert Enum.reverse_slice([1, 2, 3], 0, 0) == [1, 2, 3]
    assert Enum.reverse_slice([1, 2, 3], 0, 1) == [1, 2, 3]
    assert Enum.reverse_slice([1, 2, 3], 0, 2) == [2, 1, 3]
    assert Enum.reverse_slice([1, 2, 3], 0, 20000000) == [3, 2, 1]
    assert Enum.reverse_slice([1, 2, 3], 100, 2) == [1, 2, 3]
  end

  test :sample_1 do
    # corner cases, independent of the seed
    assert_raise Enum.EmptyError, fn -> Enum.sample([]) end
    assert Enum.sample([1]) == 1

    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample([1, 2]) == 2
    assert Enum.sample([1, 2, 3]) == 2
    assert Enum.sample([1, 2, 3, 4]) == 4
    assert Enum.sample([1, 2, 3, 4, 5]) == 1
    :random.seed(seed2)
    assert Enum.sample([1, 2]) == 1
    assert Enum.sample([1, 2, 3]) == 3
    assert Enum.sample([1, 2, 3, 4]) == 1
    assert Enum.sample([1, 2, 3, 4, 5]) == 5
  end

  test :sample_2 do
    # corner cases, independent of the seed
    assert_raise FunctionClauseError, fn -> Enum.sample([1,2], -1) end
    assert Enum.sample([], 0) == []
    assert Enum.sample([], 3) == []
    assert Enum.sample([1], 0) == []
    assert Enum.sample([1], 2) == [1]
    assert Enum.sample([1, 2], 0) == []
    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample([1, 2, 3, 4, 5], 1) == [2]
    assert Enum.sample([1, 2, 3, 4, 5], 2) == [4, 1]
    assert Enum.sample([1, 2, 3, 4, 5], 3) == [1, 3, 5]
    assert Enum.sample([1, 2, 3, 4, 5], 4) == [3, 5, 1, 2]
    assert Enum.sample([1, 2, 3, 4, 5], 5) == [1, 2, 5, 4, 3]
    assert Enum.sample([1, 2, 3, 4, 5], 6) == [2, 4, 5, 3, 1]
    :random.seed(seed2)
    assert Enum.sample([1, 2, 3, 4, 5], 1) == [1]
    assert Enum.sample([1, 2, 3, 4, 5], 2) == [1, 5]
    assert Enum.sample([1, 2, 3, 4, 5], 3) == [5, 1, 3]
    assert Enum.sample([1, 2, 3, 4, 5], 4) == [4, 5, 2, 1]
    assert Enum.sample([1, 2, 3, 4, 5], 5) == [4, 3, 1, 5, 2]
    assert Enum.sample([1, 2, 3, 4, 5], 6) == [5, 2, 1, 4, 3]
    # assert that every item in the sample comes from the input list
    list = for _<-1..100, do: make_ref
    for x <- Enum.sample(list, 50) do
      assert Enum.find(list, &(&1 == x))
    end
 end

  test :scan do
    assert Enum.scan([1,2,3,4,5], &(&1 + &2)) == [1,3,6,10,15]
    assert Enum.scan([], &(&1 + &2)) == []

    assert Enum.scan([1,2,3,4,5], 0, &(&1 + &2)) == [1,3,6,10,15]
    assert Enum.scan([], 0, &(&1 + &2)) == []
  end

  test :shuffle do
    # set a fixed seed so the test can be deterministic
    :random.seed(1374, 347975, 449264)
    assert Enum.shuffle([1, 2, 3, 4, 5]) == [2, 4, 1, 5, 3]
  end

  test :sort do
    assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
    assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
  end

  test :sort_by do
    collection = [
      [other_data: 1, sorted_data: 5],
      [other_data: 3, sorted_data: 4],
      [other_data: 4, sorted_data: 3],
      [other_data: 2, sorted_data: 2],
      [other_data: 5, sorted_data: 1]
    ]

    assert Enum.sort_by(
        collection,
        &(&1[:sorted_data])
      ) == [
        [other_data: 5, sorted_data: 1],
        [other_data: 2, sorted_data: 2],
        [other_data: 4, sorted_data: 3],
        [other_data: 3, sorted_data: 4],
        [other_data: 1, sorted_data: 5]
      ]
    assert Enum.sort_by(collection, &(&1[:sorted_data]), &>=/2) == collection
  end

  test :split do
    assert Enum.split([1, 2, 3], 0) == {[], [1, 2, 3]}
    assert Enum.split([1, 2, 3], 1) == {[1], [2, 3]}
    assert Enum.split([1, 2, 3], 2) == {[1, 2], [3]}
    assert Enum.split([1, 2, 3], 3) == {[1, 2, 3], []}
    assert Enum.split([1, 2, 3], 4) == {[1, 2, 3], []}
    assert Enum.split([], 3) == {[], []}
    assert Enum.split([1, 2, 3], -1) == {[1, 2], [3]}
    assert Enum.split([1, 2, 3], -2) == {[1], [2, 3]}
    assert Enum.split([1, 2, 3], -3) == {[], [1, 2, 3]}
    assert Enum.split([1, 2, 3], -10) == {[], [1, 2, 3]}
  end

  test :split_while do
    assert Enum.split_while([1, 2, 3], fn(_) -> false end) == {[], [1, 2, 3]}
    assert Enum.split_while([1, 2, 3], fn(_) -> true end) == {[1, 2, 3], []}
    assert Enum.split_while([1, 2, 3], fn(x) -> x > 2 end) == {[], [1, 2, 3]}
    assert Enum.split_while([1, 2, 3], fn(x) -> x > 3 end) == {[], [1, 2, 3]}
    assert Enum.split_while([1, 2, 3], fn(x) -> x < 3 end) == {[1, 2], [3]}
    assert Enum.split_while([], fn(_) -> true end) == {[], []}
  end

  test :sum do
    assert Enum.sum([]) == 0
    assert Enum.sum([1]) == 1
    assert Enum.sum([1, 2, 3]) == 6
    assert Enum.sum([1.1, 2.2, 3.3]) == 6.6
    assert_raise ArithmeticError, fn ->
      Enum.sum([{}])
    end
    assert_raise ArithmeticError, fn ->
      Enum.sum([1,{}])
    end
  end

  test :take do
    assert Enum.take([1, 2, 3], 0) == []
    assert Enum.take([1, 2, 3], 1) == [1]
    assert Enum.take([1, 2, 3], 2) == [1, 2]
    assert Enum.take([1, 2, 3], 3) == [1, 2, 3]
    assert Enum.take([1, 2, 3], 4) == [1, 2, 3]
    assert Enum.take([1, 2, 3], -1) == [3]
    assert Enum.take([1, 2, 3], -2) == [2, 3]
    assert Enum.take([1, 2, 3], -4) == [1, 2, 3]
    assert Enum.take([], 3) == []
  end

  test :take_every do
    assert Enum.take_every([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2) == [1, 3, 5, 7, 9]
    assert Enum.take_every([], 2) == []
    assert Enum.take_every([1, 2], 2) == [1]
    assert Enum.take_every([1, 2, 3], 0) == []
    assert_raise FunctionClauseError, fn ->
      Enum.take_every([1, 2, 3], -1)
    end
  end

  test :take_while do
    assert Enum.take_while([1, 2, 3], fn(x) -> x > 3 end) == []
    assert Enum.take_while([1, 2, 3], fn(x) -> x <= 1 end) == [1]
    assert Enum.take_while([1, 2, 3], fn(x) -> x <= 3 end) == [1, 2, 3]
    assert Enum.take_while([], fn(_) -> true end) == []
  end

  test :to_list do
    assert Enum.to_list([]) == []
    assert Enum.to_list(1 .. 3) == [1, 2, 3]
  end

  test :uniq do
    assert Enum.uniq([1, 2, 3, 2, 1]) == [1, 2, 3]
    assert Enum.uniq([1, 2, 3, 2, 1], fn x -> x end) == [1, 2, 3]
  end

  test :zip do
    assert Enum.zip([:a, :b], [1, 2]) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b], [1, 2, 3, 4]) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b, :c, :d], [1, 2]) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([], [1]) == []
    assert Enum.zip([1], []) == []
    assert Enum.zip([], [])  == []
  end

  test :unzip do
    assert Enum.unzip([{:a, 1}, {:b, 2}, {:c, 3}]) == {[:a, :b, :c], [1, 2, 3]}
    assert Enum.unzip([]) == {[], []}
    assert Enum.unzip(%{a: 1, b: 2}) == {[:a, :b], [1, 2]}
    assert Enum.unzip([foo: "a", bar: "b"]) == {[:foo, :bar], ["a", "b"]}

    assert_raise FunctionClauseError, fn -> Enum.unzip([{:a, 1}, {:b, 2, "foo"}]) end
    assert_raise FunctionClauseError, fn -> Enum.unzip([{1, 2, {3, {4, 5}}}]) end
    assert_raise FunctionClauseError, fn -> Enum.unzip([1, 2, 3]) end
  end

  test :with_index do
    assert Enum.with_index([]) == []
    assert Enum.with_index([1,2,3]) == [{1,0},{2,1},{3,2}]
  end

  test :max do
    assert Enum.max([1]) == 1
    assert Enum.max([1, 2, 3]) == 3
    assert Enum.max([1, [], :a, {}]) == []
    assert_raise Enum.EmptyError, fn ->
      Enum.max([])
    end
  end

  test :max_by do
    assert Enum.max_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "aaa"
    assert_raise Enum.EmptyError, fn ->
      Enum.max_by([], fn(x) -> String.length(x) end)
    end
  end

  test :min do
    assert Enum.min([1]) == 1
    assert Enum.min([1, 2, 3]) == 1
    assert Enum.min([[], :a, {}]) == :a
    assert_raise Enum.EmptyError, fn ->
      Enum.min([])
    end
  end

  test :min_by do
    assert Enum.min_by(["a", "aa", "aaa"], fn(x) -> String.length(x) end) == "a"
    assert_raise Enum.EmptyError, fn ->
      Enum.min_by([], fn(x) -> String.length(x) end)
    end
  end

  test :minmax do
    assert Enum.minmax([1]) == {1, 1}
    assert Enum.minmax([2, 3, 1]) == {1, 3}
    assert Enum.minmax([[], :a, {}]) == {:a, []}
    assert_raise Enum.EmptyError, fn ->
      Enum.minmax([])
    end
  end

  test :minmax_by do
    assert Enum.minmax_by(["aaa", "a", "aa"], fn(x) -> String.length(x) end) == {"a", "aaa"}
    assert_raise Enum.EmptyError, fn ->
      Enum.minmax_by([], fn(x) -> String.length(x) end)
    end
  end

  test :chunk do
    assert Enum.chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4]]
    assert Enum.chunk([1, 2, 3, 4, 5], 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2) == [[1, 2, 3], [3, 4, 5]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 2, 3) == [[1, 2], [4, 5]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
    assert Enum.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) == [[1, 2, 3], [4, 5, 6]]
    assert Enum.chunk([1, 2, 3, 4, 5], 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
  end

  test :chunk_by do
    assert Enum.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1)) == [[1], [2, 2], [3], [4, 4, 6], [7, 7]]
    assert Enum.chunk_by([1, 2, 3, 4], fn _ -> true end) == [[1, 2, 3, 4]]
    assert Enum.chunk_by([], fn _ -> true end) == []
    assert Enum.chunk_by([1], fn _ -> true end) == [[1]]
  end

  test :slice do
    assert Enum.slice([1,2,3,4,5], 0, 0) == []
    assert Enum.slice([1,2,3,4,5], 0, 1) == [1]
    assert Enum.slice([1,2,3,4,5], 0, 2) == [1, 2]
    assert Enum.slice([1,2,3,4,5], 1, 2) == [2, 3]
    assert Enum.slice([1,2,3,4,5], 1, 0) == []
    assert Enum.slice([1,2,3,4,5], 2, 5) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 2, 6) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 5, 5) == []
    assert Enum.slice([1,2,3,4,5], 6, 5) == []
    assert Enum.slice([1,2,3,4,5], 6, 0) == []
    assert Enum.slice([1,2,3,4,5], -6, 0) == []
    assert Enum.slice([1,2,3,4,5], -6, 5) == []
    assert Enum.slice([1,2,3,4,5], -2, 5) == [4, 5]
    assert Enum.slice([1,2,3,4,5], -3, 1) == [3]
  end

  test :slice_range do
    assert Enum.slice([1,2,3,4,5], 0..0) == [1]
    assert Enum.slice([1,2,3,4,5], 0..1) == [1, 2]
    assert Enum.slice([1,2,3,4,5], 0..2) == [1, 2, 3]
    assert Enum.slice([1,2,3,4,5], 1..2) == [2, 3]
    assert Enum.slice([1,2,3,4,5], 1..0) == []
    assert Enum.slice([1,2,3,4,5], 2..5) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 2..6) == [3, 4, 5]
    assert Enum.slice([1,2,3,4,5], 4..4) == [5]
    assert Enum.slice([1,2,3,4,5], 5..5) == []
    assert Enum.slice([1,2,3,4,5], 6..5) == []
    assert Enum.slice([1,2,3,4,5], 6..0) == []
    assert Enum.slice([1,2,3,4,5], -6..0) == []
    assert Enum.slice([1,2,3,4,5], -6..5) == []
    assert Enum.slice([1,2,3,4,5], -5..-1) == [1, 2, 3, 4, 5]
    assert Enum.slice([1,2,3,4,5], -5..-3) == [1, 2, 3]
    assert Enum.slice([1,2,3,4,5], -6..-1) == []
    assert Enum.slice([1,2,3,4,5], -6..-3) == []
  end
end

defmodule EnumTest.Range do
  use ExUnit.Case, async: true

  test :all? do
    range = 0..5
    refute Enum.all?(range, fn(x) -> rem(x, 2) == 0 end)

    range = 0..1
    assert Enum.all?(range, fn(x) -> x < 2 end)
    assert Enum.all?(range)

    range = 1..0
    assert Enum.all?(range)
  end

  test :any? do
    range = 0..5
    refute Enum.any?(range, &(&1 > 10))

    range = 0..5
    assert Enum.any?(range, &(&1 > 3))

    range = 1..0
    assert Enum.any?(range)
  end

  test :fetch! do
    assert Enum.fetch!(2..6, 0) == 2
    assert Enum.fetch!(2..6, 4) == 6
    assert Enum.fetch!(2..6, -1) == 6
    assert Enum.fetch!(2..6, -2) == 5
    assert Enum.fetch!(-2..-6, 0) == -2
    assert Enum.fetch!(-2..-6, 4) == -6

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!(2..6, 8)
    end

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!(-2..-6, 8)
    end

    assert_raise Enum.OutOfBoundsError, fn ->
      Enum.fetch!(2..6, -8)
    end
  end

  test :count do
    range = 1..5
    assert Enum.count(range) == 5
    range = 1..1
    assert Enum.count(range) == 1
  end

  test :count_fun do
    range = 1..5
    assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 2
    range = 1..1
    assert Enum.count(range, fn(x) -> rem(x, 2) == 0 end) == 0
  end

  test :chunk do
    assert Enum.chunk(1..5, 2) == [[1, 2], [3, 4]]
    assert Enum.chunk(1..5, 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
    assert Enum.chunk(1..6, 3, 2) == [[1, 2, 3], [3, 4, 5]]
    assert Enum.chunk(1..6, 2, 3) == [[1, 2], [4, 5]]
    assert Enum.chunk(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
    assert Enum.chunk(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
  end

  test :chunk_by do
    assert Enum.chunk_by(1..4, fn _ -> true end) == [[1, 2, 3, 4]]
    assert Enum.chunk_by(1..4, &(rem(&1, 2) == 1)) == [[1], [2], [3], [4]]
  end

  test :drop do
    range = 1..3
    assert Enum.drop(range, 0) == [1, 2, 3]
    assert Enum.drop(range, 1) == [2, 3]
    assert Enum.drop(range, 2) == [3]
    assert Enum.drop(range, 3) == []
    assert Enum.drop(range, 4) == []
    assert Enum.drop(range, -1) == [1, 2]
    assert Enum.drop(range, -2) == [1]
    assert Enum.drop(range, -4) == []

    range = 1..0
    assert Enum.drop(range, 3) == []
  end

  test :drop_while do
    range = 0..6
    assert Enum.drop_while(range, fn(x) -> x <= 3 end) == [4, 5, 6]
    assert Enum.drop_while(range, fn(_) -> false end) == [0, 1, 2, 3, 4, 5, 6]

    range = 0..3
    assert Enum.drop_while(range, fn(x) -> x <= 3 end) == []

    range = 1..0
    assert Enum.drop_while(range, fn(_) -> false end) == [1, 0]
  end

  test :find do
    range = 2..6
    assert Enum.find(range, fn(x) -> rem(x, 2) == 0 end) == 2
    assert Enum.find(range, fn(x) -> rem(x, 2) == 1 end) == 3
    assert Enum.find(range, fn _ -> false end) == nil
    assert Enum.find(range, 0, fn _ -> false end) == 0
  end

  test :find_value do
    range = 2..6
    assert Enum.find_value(range, fn(x) -> rem(x, 2) == 1 end)
  end

  test :find_index do
    range = 2..6
    assert Enum.find_index(range, fn(x) -> rem(x, 2) == 1 end) == 1
  end

  test :empty? do
    range = 1..0
    refute Enum.empty?(range)

    range = 1..2
    refute Enum.empty?(range)
  end

  test :each do
    try do
      range = 1..0
      assert Enum.each(range, fn(x) -> x end) == :ok

      range = 1..3
      assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
      assert Process.get(:enum_test_each) == 6
    after
      Process.delete(:enum_test_each)
    end

    try do
      range = -1..-3
      assert Enum.each(range, fn(x) -> Process.put(:enum_test_each, x * 2) end) == :ok
      assert Process.get(:enum_test_each) == -6
    after
      Process.delete(:enum_test_each)
    end
  end

  test :filter do
    range = 1..3
    assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2]

    range = 1..6
    assert Enum.filter(range, fn(x) -> rem(x, 2) == 0 end) == [2, 4, 6]
  end

  test :filter_with_match do
    range = 1..3
    assert Enum.filter(range, &match?(1, &1)) == [1]
    assert Enum.filter(range, &match?(x when x < 3, &1)) == [1, 2]
    assert Enum.filter(range, &match?(_, &1)) == [1, 2, 3]
  end

  test :filter_map do
    range = 1..3
    assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4]

    range = 2..6
    assert Enum.filter_map(range, fn(x) -> rem(x, 2) == 0 end, &(&1 * 2)) == [4, 8, 12]
  end

  test :flat_map do
    range = 1..3
    assert Enum.flat_map(range, fn(x) -> [x, x] end) == [1, 1, 2, 2, 3, 3]
  end

  test :intersperse do
    range = 1..0
    assert Enum.intersperse(range, true) == [1, true, 0]

    range = 1..3
    assert Enum.intersperse(range, false) == [1, false, 2, false, 3]
  end

  test :into do
    assert Enum.into([a: 1, b: 2], %{}) == %{a: 1, b: 2}
    assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
    assert Enum.into(3..5, [1, 2]) == [1, 2, 3, 4, 5]
    assert Enum.into(1..5, []) == [1, 2, 3, 4, 5]
    assert Enum.into(1..5, [], fn x -> x * 2 end) == [2, 4, 6, 8, 10]
    assert Enum.into(1..3, "numbers: ", &to_string/1) == "numbers: 123"
  end

  test :join do
    range = 1..0
    assert Enum.join(range, " = ") == "1 = 0"

    range = 1..3
    assert Enum.join(range, " = ") == "1 = 2 = 3"
    assert Enum.join(range) == "123"
  end

  test :map_join do
    range = 1..0
    assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 0"

    range = 1..3
    assert Enum.map_join(range, " = ", &(&1 * 2)) == "2 = 4 = 6"
    assert Enum.map_join(range, &(&1 * 2)) == "246"
  end

  test :map do
    range = 1..3
    assert Enum.map(range, fn x -> x * 2 end) == [2, 4, 6]

    range = -1..-3
    assert Enum.map(range, fn x -> x * 2 end) == [-2, -4, -6]
  end

  test :map_reduce do
    range = 1..0
    assert Enum.map_reduce(range, 1, fn(x, acc) -> {x * 2, x + acc} end) == {[2, 0], 2}

    range = 1..3
    assert Enum.map_reduce(range, 1, fn(x, acc) -> {x * 2, x + acc} end) == {[2, 4, 6], 7}
  end

  test :max do
    assert Enum.max(1..1) == 1
    assert Enum.max(1..3) == 3
    assert Enum.max(3..1) == 3
  end

  test :max_by do
    assert Enum.max_by(1..1, fn(x) -> :math.pow(-2, x) end) == 1
    assert Enum.max_by(1..3, fn(x) -> :math.pow(-2, x) end) == 2
  end

  test :min do
    assert Enum.min([1]) == 1
    assert Enum.min([1, 2, 3]) == 1
    assert Enum.min([[], :a, {}]) == :a
  end

  test :min_by do
    assert Enum.min_by(1..1, fn(x) -> :math.pow(-2, x) end) == 1
    assert Enum.min_by(1..3, fn(x) -> :math.pow(-2, x) end) == 3
  end

  test :partition do
    range = 1..3
    assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == {[2], [1, 3]}
  end

  test :reduce do
    range = 1..0
    assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 2

    range = 1..3
    assert Enum.reduce(range, 1, fn(x, acc) -> x + acc end) == 7

    range = 1..3
    assert Enum.reduce(range, fn(x, acc) -> x + acc end) == 6
  end

  test :reject do
    range = 1..3
    assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3]

    range = 1..6
    assert Enum.reject(range, fn(x) -> rem(x, 2) == 0 end) == [1, 3, 5]
  end

  test :reverse do
    assert Enum.reverse(0..0) == [0]
    assert Enum.reverse(1..3) == [3, 2, 1]
    assert Enum.reverse(1..3, 4..6) == [3, 2, 1, 4, 5, 6]
    assert Enum.reverse([1, 2, 3], 4..6) == [3, 2, 1, 4, 5, 6]
    assert Enum.reverse(1..3, [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
  end

  test :reverse_slice do
    assert Enum.reverse_slice(1..6, 2, 0) == [1, 2, 3, 4, 5, 6]
    assert Enum.reverse_slice(1..6, 2, 2) == [1, 2, 4, 3, 5, 6]
    assert Enum.reverse_slice(1..6, 2, 4) == [1, 2, 6, 5, 4, 3]
    assert Enum.reverse_slice(1..6, 2, 10000000) == [1, 2, 6, 5, 4, 3]
    assert Enum.reverse_slice(1..6, 10000000, 4) == [1, 2, 3, 4, 5, 6]
  end

  test :sample_1 do
    # corner cases, independent of the seed
    assert Enum.sample(1..1) == 1

    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample(1..2) == 2
    assert Enum.sample(1..3) == 2
    assert Enum.sample(1..4) == 4
    assert Enum.sample(1..5) == 1
    :random.seed(seed1)
    assert Enum.sample(?a..?z) == ?i
    :random.seed(seed1)
    assert Enum.sample(?0..?9) == ?8
    :random.seed(seed2)
    assert Enum.sample(1..2) == 1
    assert Enum.sample(1..3) == 3
    assert Enum.sample(1..4) == 1
    assert Enum.sample(1..5) == 5
    :random.seed(seed2)
    assert Enum.sample(?a..?z) == ?a
    :random.seed(seed2)
    assert Enum.sample(?0..?9) == ?0
  end

  test :sample_2 do
    # corner cases, independent of the seed
    assert_raise FunctionClauseError, fn -> Enum.sample(1..2, -1) end
    assert Enum.sample(1..1, 0) == []
    assert Enum.sample(1..1, 2) == [1]
    assert Enum.sample(1..2, 0) == []

    # set a fixed seed so the test can be deterministic
    # please note the order of following assertions is important
    seed1 = {1406, 407414, 139258}
    seed2 = {1406, 421106, 567597}
    :random.seed(seed1)
    assert Enum.sample(1..5, 1) == [2]
    assert Enum.sample(1..5, 2) == [4, 1]
    assert Enum.sample(1..5, 3) == [1, 3, 5]
    assert Enum.sample(1..5, 4) == [3, 5, 1, 2]
    assert Enum.sample(1..5, 5) == [1, 2, 5, 4, 3]
    assert Enum.sample(1..5, 6) == [2, 4, 5, 3, 1]
    :random.seed(seed2)
    assert Enum.sample(1..5, 1) == [1]
    assert Enum.sample(1..5, 2) == [1, 5]
    assert Enum.sample(1..5, 3) == [5, 1, 3]
    assert Enum.sample(1..5, 4) == [4, 5, 2, 1]
    assert Enum.sample(1..5, 5) == [4, 3, 1, 5, 2]
    assert Enum.sample(1..5, 6) == [5, 2, 1, 4, 3]
    :random.seed(seed1)
    assert Enum.sample(?a..?z, 1) == 'i'
    assert Enum.sample(?a..?z, 2) == 'cm'
    assert Enum.sample(?a..?z, 3) == 'alp'
    assert Enum.sample(?a..?z, 4) == 'tzmd'
    assert Enum.sample(?a..?z, 5) == 'cuxvb'
    :random.seed(seed2)
    assert Enum.sample(?a..?z, 1) == 'a'
    assert Enum.sample(?a..?z, 2) == 'wk'
    assert Enum.sample(?a..?z, 3) == 'ayj'
    assert Enum.sample(?a..?z, 4) == 'rbcm'
    assert Enum.sample(?a..?z, 5) == 'rhzju'
    :random.seed(seed1)
    assert Enum.sample(?0..?9, 1) == '8'
    assert Enum.sample(?0..?9, 2) == '07'
    assert Enum.sample(?0..?9, 3) == '018'
    assert Enum.sample(?0..?9, 4) == '0856'
    assert Enum.sample(?0..?9, 5) == '03698'
    :random.seed(seed2)
    assert Enum.sample(?0..?9, 1) == '0'
    assert Enum.sample(?0..?9, 2) == '48'
    assert Enum.sample(?0..?9, 3) == '590'
    assert Enum.sample(?0..?9, 4) == '4805'
    assert Enum.sample(?0..?9, 5) == '81945'
  end

  test :scan do
    assert Enum.scan(1..5, &(&1 + &2)) == [1,3,6,10,15]
    assert Enum.scan(1..5, 0, &(&1 + &2)) == [1,3,6,10,15]
  end

  test :shuffle do
    # set a fixed seed so the test can be deterministic
    :random.seed(1374, 347975, 449264)
    assert Enum.shuffle(1..5) == [2, 4, 1, 5, 3]
  end

  test :slice do
    assert Enum.slice(1..5, 0, 0) == []
    assert Enum.slice(1..5, 0, 1) == [1]
    assert Enum.slice(1..5, 0, 2) == [1, 2]
    assert Enum.slice(1..5, 1, 2) == [2, 3]
    assert Enum.slice(1..5, 1, 0) == []
    assert Enum.slice(1..5, 2, 5) == [3, 4, 5]
    assert Enum.slice(1..5, 2, 6) == [3, 4, 5]
    assert Enum.slice(1..5, 5, 5) == []
    assert Enum.slice(1..5, 6, 5) == []
    assert Enum.slice(1..5, 6, 0) == []
    assert Enum.slice(1..5, -6, 0) == []
    assert Enum.slice(1..5, -6, 5) == []
    assert Enum.slice(1..5, -2, 5) == [4, 5]
    assert Enum.slice(1..5, -3, 1) == [3]
  end

  test :slice_range do
    assert Enum.slice(1..5, 0..0) == [1]
    assert Enum.slice(1..5, 0..1) == [1, 2]
    assert Enum.slice(1..5, 0..2) == [1, 2, 3]
    assert Enum.slice(1..5, 1..2) == [2, 3]
    assert Enum.slice(1..5, 1..0) == []
    assert Enum.slice(1..5, 2..5) == [3, 4, 5]
    assert Enum.slice(1..5, 2..6) == [3, 4, 5]
    assert Enum.slice(1..5, 4..4) == [5]
    assert Enum.slice(1..5, 5..5) == []
    assert Enum.slice(1..5, 6..5) == []
    assert Enum.slice(1..5, 6..0) == []
    assert Enum.slice(1..5, -6..0) == []
    assert Enum.slice(1..5, -6..5) == []
    assert Enum.slice(1..5, -5..-1) == [1, 2, 3, 4, 5]
    assert Enum.slice(1..5, -5..-3) == [1, 2, 3]
    assert Enum.slice(1..5, -6..-1) == []
    assert Enum.slice(1..5, -6..-3) == []
  end

  test :sort do
    assert Enum.sort(3..1) == [1, 2, 3]
    assert Enum.sort(2..1) == [1, 2]
    assert Enum.sort(1..1) == [1]

    assert Enum.sort(3..1, &(&1 > &2)) == [3, 2, 1]
    assert Enum.sort(2..1, &(&1 > &2)) == [2, 1]
    assert Enum.sort(1..1, &(&1 > &2)) == [1]
  end

  test :split do
    range = 1..3
    assert Enum.split(range, 0) == {[], [1, 2, 3]}
    assert Enum.split(range, 1) == {[1], [2, 3]}
    assert Enum.split(range, 2) == {[1, 2], [3]}
    assert Enum.split(range, 3) == {[1, 2, 3], []}
    assert Enum.split(range, 4) == {[1, 2, 3], []}
    assert Enum.split(range, -1) == {[1, 2], [3]}
    assert Enum.split(range, -2) == {[1], [2, 3]}
    assert Enum.split(range, -3) == {[], [1, 2, 3]}
    assert Enum.split(range, -10) == {[], [1, 2, 3]}

    range = 1..0
    assert Enum.split(range, 3) == {[1, 0], []}
  end

  test :split_while do
    range = 1..3
    assert Enum.split_while(range, fn(_) -> false end) == {[], [1, 2, 3]}
    assert Enum.split_while(range, fn(_) -> true end) == {[1, 2, 3], []}
    assert Enum.split_while(range, fn(x) -> x > 2 end) == {[], [1, 2, 3]}
    assert Enum.split_while(range, fn(x) -> x > 3 end) == {[], [1, 2, 3]}
    assert Enum.split_while(range, fn(x) -> x < 3 end) == {[1, 2], [3]}

    range = 1..0
    assert Enum.split_while(range, fn(_) -> true end) == {[1, 0], []}
  end

  test :sum do
    assert Enum.sum(1..1) == 1
    assert Enum.sum(1..3) == 6
  end

  test :take do
    range = 1..3
    assert Enum.take(range, 0) == []
    assert Enum.take(range, 1) == [1]
    assert Enum.take(range, 2) == [1, 2]
    assert Enum.take(range, 3) == [1, 2, 3]
    assert Enum.take(range, 4) == [1, 2, 3]
    assert Enum.take(range, -1) == [3]
    assert Enum.take(range, -2) == [2, 3]
    assert Enum.take(range, -4) == [1, 2, 3]

    range = 1..0
    assert Enum.take(range, 3) == [1, 0]
  end

  test :take_every do
    assert Enum.take_every(1..10, 2) == [1, 3, 5, 7, 9]
    assert Enum.take_every(1..2, 2) == [1]
    assert Enum.take_every(1..3, 0) == []
    assert_raise FunctionClauseError, fn ->
      Enum.take_every(1..3, -1)
    end
  end

  test :take_while do
    range = 1..3
    assert Enum.take_while(range, fn(x) -> x > 3 end) == []
    assert Enum.take_while(range, fn(x) -> x <= 1 end) == [1]
    assert Enum.take_while(range, fn(x) -> x <= 3 end) == [1, 2, 3]
    assert Enum.take_while([], fn(_) -> true end) == []
  end

  test :uniq do
    assert Enum.uniq(1..3) == [1, 2, 3]
    assert Enum.uniq(1..3, fn x -> x end) == [1, 2, 3]
  end

  test :zip do
    assert Enum.zip([:a, :b], 1..2) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b], 1..4) == [{:a, 1}, {:b, 2}]
    assert Enum.zip([:a, :b, :c, :d], 1..2) == [{:a, 1}, {:b, 2}]

    assert Enum.zip(1..2, [:a, :b]) == [{1, :a}, {2, :b}]
    assert Enum.zip(1..4, [:a, :b]) == [{1, :a}, {2, :b}]
    assert Enum.zip(1..2, [:a, :b, :c, :d]) == [{1, :a}, {2, :b}]

    assert Enum.zip(1..2, 1..2) == [{1, 1}, {2, 2}]
    assert Enum.zip(1..4, 1..2) == [{1, 1}, {2, 2}]
    assert Enum.zip(1..2, 1..4) == [{1, 1}, {2, 2}]
  end

  test :with_index do
    assert Enum.with_index(1..3) == [{1,0},{2,1},{3,2}]
  end
end

defmodule EnumTest.SideEffects do
  use ExUnit.Case, async: true

  import ExUnit.CaptureIO
  import PathHelpers

  test "take with side effects" do
    stream = Stream.unfold(1, fn x -> IO.puts x; {x, x + 1} end)
    assert capture_io(fn ->
      Enum.take(stream, 1)
    end) == "1\n"
  end

  test "take does not consume next without a need" do
    path = tmp_path("oneliner.txt")
    File.mkdir(Path.dirname(path))

    try do
      File.write!(path, "ONE")

      File.open!(path, [], fn file ->
        iterator = IO.stream(file, :line)
        assert Enum.take(iterator, 1) == ["ONE"]
        assert Enum.take(iterator, 5) == []
      end)
    after
      File.rm(path)
    end
  end

  test "take with no item works as no-op" do
    iterator = File.stream!(fixture_path("unknown.txt"))

    assert Enum.take(iterator, 0) == []
    assert Enum.take(iterator, 0) == []
    assert Enum.take(iterator, 0) == []
    assert Enum.take(iterator, 0) == []
  end
end


DocTest: add a note on using standard output

defmodule ExUnit.DocTest do
  @moduledoc """
  ExUnit.DocTest implements functionality similar to Python's doctest (http://docs.python.org/2/library/doctest.html)

  In a nutshell, it allows to use examples in module/function/macro definitions as tests. In order
  to do that, one needs to invoke `doctest/1` macro from their test case.

  The syntax for examples is as follows. Every new test starts on a new line, with
  an "iex>" prefix. Multiline expressions can be employed if the following lines
  start with either "...>" (recommended) or "iex>" prefix.

  The expected result should start at the next line after "iex>" or "...>" line(s) and
  is terminated either by a newline, new "iex>" prefix or end of the string literal.

  Example:

      iex> [1+1,
      ...>  2]
      [2,
      2]

  Similarly to the interactive use of iex you can use numbers in your "prompts":

      iex(1)> [1+2,
      ...(1)>  3]
      [3,3]

  This is useful in two use cases:

  * Being able to refer to specific numbered scenarios in the documentation piece
  * Copy-pasting examples from an actual iex sessions

  ## Exceptions

  You can also showcase expressions raising an exception, for example:

      iex(1)> binary_to_atom((fn() -> 1 end).())
      ** (ArgumentError) argument error

  What DocTest will be looking for is a line starting with "** (" and it will parse
  it accordingly to extract the exception name and the message. At this moment,
  the exception parser would make the parser treat the next line as a start of a completely
  new expression (if it is prefixed with iex>) or a no-op line with documentation. Thus,
  multiline messages are not supported.

  ## Standard output

  Please note that DocTest will not try to capture the output, it will only check if the
  expression given evaluates to the expected result.

  ## Usage

  Currently, the only way to run doctests is to include them into an ExUnit case
  with a `doctest` macro, for example:

      defmodule MyModule.Test do
        use ExUnit.Case
        doctest MyModule
      end

  See `doctest` macro documentation for more details on how to use it
  """

  defrecord Test, location: nil, line: nil, expr: nil, expected: nil

  @doc """
  This macro is used to generate ExUnit test cases for doctests.

  There are three ways this macro can be used:

  * `doctest(Module)` — will generate tests for all doctests found
     in the module `Module`
  * doctest(Module.function) — will generate tests for all doctests found
     in all arities of function `Module.function`
  * doctest(Module.function/arity) — will generate tests for all doctests found
     in the documentation of function `Module.function/arity`

  Options can also be supplied:

  * `:except` — generate tests for all functions except those listed (list of `{function, arity}` tuples)
  * `:only` — generate tests only forfunctions listed (list of `{function, arity}` tuples)

  This macro is auto-imported into every ExUnit.Case
  """
  defmacro doctest(testspec, options // []) do
    tests = tests(testspec, __CALLER__)

    tests = Enum.filter(tests, fn(test) ->
      {_, f, a} = test.location
      fa = {f,a}
      Enum.all?(options[:except] || [], fn(fa1) -> fa1 != fa end) and
      Enum.all?(options[:only] || [], fn(fa1) -> fa1 == fa end)
    end)

    bodies =
    lc test inlist tests do
       {m, _, _} = test.location
       expr_ast = Code.string_to_ast!(test.expr)
       expected_ast = Code.string_to_ast!(test.expected)
       quote do
         import unquote(m)
         v = unquote(expected_ast)
         case unquote(expr_ast) do
           ^v -> :ok
           instead ->
             raise ExUnit.ExpectationError,
               prelude: "Expected doctest",
               expected: unquote(test.expr),
               actual: inspect(v),
               reason: "evaluate to",
               instead: instead
         end
       end
    end
    tests = Enum.zip(:lists.seq(1, length(tests)), tests)
    lc {{n, test}, body} inlist Enum.zip(tests, bodies) do
      quote do
        test unquote("#{type(test)} (#{n}) at #{mfa(test)}"), do: unquote(body)
      end
    end
  end

  defp tests(module, _) when is_atom(module), do: extract(module)
  defp tests({:__aliases__, _, _} = module, env) do
    {m, _} = Code.eval_quoted(module, [], env)
    tests(m, env)
  end
  defp tests({:., _, [m, f]}, env) do
    {m, _} = Code.eval_quoted(m, [], env)
    extract(m, f)
  end

  defp tests({:/, _, [{:., _, [m, f]}, a]}, env) do
    {m, _} = Code.eval_quoted(m, [], env)
    extract(m, f, a)
  end

  defp mfa(Test[location: {m,nil,nil}, line: line]) do
    "#{inspect m} (#{m.module_info[:compile][:source]}:#{line})"
  end
  defp mfa(Test[location: {m,f,a}, line: line]) do
    "#{inspect m}.#{f}/#{a} (#{m.module_info[:compile][:source]}:#{line})"
  end

  defp type(Test[location: {_, nil, nil}]), do: "moduledoc"
  defp type(_), do: "doc"

  @doc false
  def extract(module) do
    Enum.map(extract_from_doc(module.__info__(:moduledoc)), fn(test) ->
      test.location({module, nil, nil})
    end) ++
    lc {{f, a}, _, _, _, _} inlist module.__info__(:docs) do
       extract(module, f, a)
    end |> List.concat
  end

  def extract(module, function) do
    lc {{f, a}, _, _, _, _} inlist module.__info__(:docs), f == function do
       extract(module, function, a)
    end |> List.concat
  end

  def extract(module, function, arity) do
    doc = List.keyfind(module.__info__(:docs), {function, arity}, 0)
    Enum.map(extract_from_doc(doc), fn(test) -> test.location({module, function, arity}) end)
  end

  defp extract_from_doc(nil), do: []
  defp extract_from_doc({_, negative}) when negative in [false, nil], do: []
  defp extract_from_doc({line, doc}) do
    lines = String.split(doc, %r/\n/) |> Enum.map(function(String.strip/1))
    extract_from_doc(lines, line, "", "",[])
  end
  defp extract_from_doc({_, _, _, _, negative}) when negative in [false, nil], do: []
  defp extract_from_doc({_, line, _, _, doc}) do
    lines = String.split(doc, %r/\n/) |> Enum.map(function(String.strip/1))
    extract_from_doc(lines, line, "", "",[])
  end

  defp extract_from_doc([], _line, "", "", acc), do: Enum.reverse(acc)
  defp extract_from_doc([], line, expr_acc, expected_acc, acc) do
    Enum.reverse([Test.new(expr: expr_acc, line: line, expected: expected_acc)|acc])
  end
  defp extract_from_doc([<< "iex>", _ :: binary>>|_] = list, line, expr_acc, expected_acc, acc) when expr_acc != "" and expected_acc != "" do
    extract_from_doc(list, line, "", "", [Test.new(expr: expr_acc, line: line, expected: expected_acc)|acc])
  end
  defp extract_from_doc([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc == "" do
    extract_from_doc(lines, line, string, expected_acc, acc)
  end
  defp extract_from_doc([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
  end
  defp extract_from_doc([<< "...>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc != "" do
    extract_from_doc(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
  end
  defp extract_from_doc([<< "iex(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(["iex" <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
  end
  defp extract_from_doc([<< "...(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(["..." <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
  end
  defp extract_from_doc([_|lines], line, "", "", acc) do
    extract_from_doc(lines, line, "", "", acc)
  end
  defp extract_from_doc([""|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(lines, line,  "", "", [Test.new(expr: expr_acc, line: line, expected: expected_acc)|acc])
  end
  defp extract_from_doc([<< "** (", string :: binary >>|lines], line, expr_acc, "", acc) do
    opts = Regex.captures %r/(?<exception>.+)\) (?<message>.*)/g, string
    expected = "#{inspect Module.concat([opts[:exception]])}[message: #{inspect String.strip(opts[:message])}]"
    expr_acc = "try do ; #{expr_acc} ; rescue _e -> _e ; end"
    extract_from_doc(lines, line,  "", "", [Test.new(expr: expr_acc, line: line, expected: expected)|acc])
  end
  defp extract_from_doc([expected|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(lines, line, expr_acc, expected_acc <> "\n" <> expected, acc)
  end

  defp skip_iex_number(<< ")", ">", string :: binary >>) do
    ">" <> string
  end
  defp skip_iex_number(<< _ :: 8, string :: binary >>) do
    skip_iex_number(string)
  end

end

defmodule ExUnit.DocTest do
  @moduledoc """
  ExUnit.DocTest implements functionality similar to Python's doctest (http://docs.python.org/2/library/doctest.html)

  In a nutshell, it allows to use examples in module/function/macro definitions as tests. In order
  to do that, one needs to invoke `doctest/1` macro from their test case.

  The syntax for examples is as follows. Every new test starts on a new line, with
  an "iex>" prefix. Multiline expressions can be employed if the following lines
  start with either "...>" (recommended) or "iex>" prefix.

  The expected result should start at the next line after "iex>" or "...>" line(s) and
  is terminated either by a newline, new "iex>" prefix or end of the string literal.

  Example:

      iex> [1+1,
      ...>  2]
      [2,
      2]

  Similarly to the interactive use of iex you can use numbers in your "prompts":

      iex(1)> [1+2,
      ...(1)>  3]
      [3,3]

  This is useful in two use cases:

  * Being able to refer to specific numbered scenarios in the documentation piece
  * Copy-pasting examples from an actual iex sessions

  ## Exceptions

  You can also showcase expressions raising an exception, for example:

      iex(1)> binary_to_atom((fn() -> 1 end).())
      ** (ArgumentError) argument error

  What DocTest will be looking for is a line starting with "** (" and it will parse
  it accordingly to extract the exception name and the message. At this moment,
  the exception parser would make the parser treat the next line as a start of a completely
  new expression (if it is prefixed with iex>) or a no-op line with documentation. Thus,
  multiline messages are not supported.

  ## Usage

  Currently, the only way to run doctests is to include them into an ExUnit case
  with a `doctest` macro, for example:

      defmodule MyModule.Test do
        use ExUnit.Case
        doctest MyModule
      end

  See `doctest` macro documentation for more details on how to use it
  """

  defrecord Test, location: nil, line: nil, expr: nil, expected: nil

  @doc """
  This macro is used to generate ExUnit test cases for doctests.

  There are three ways this macro can be used:

  * `doctest(Module)` — will generate tests for all doctests found
     in the module `Module`
  * doctest(Module.function) — will generate tests for all doctests found
     in all arities of function `Module.function`
  * doctest(Module.function/arity) — will generate tests for all doctests found
     in the documentation of function `Module.function/arity`

  Options can also be supplied:

  * `:except` — generate tests for all functions except those listed (list of `{function, arity}` tuples)
  * `:only` — generate tests only forfunctions listed (list of `{function, arity}` tuples)

  This macro is auto-imported into every ExUnit.Case
  """
  defmacro doctest(testspec, options // []) do
    tests = tests(testspec, __CALLER__)

    tests = Enum.filter(tests, fn(test) ->
      {_, f, a} = test.location
      fa = {f,a}
      Enum.all?(options[:except] || [], fn(fa1) -> fa1 != fa end) and
      Enum.all?(options[:only] || [], fn(fa1) -> fa1 == fa end)
    end)

    bodies =
    lc test inlist tests do
       {m, _, _} = test.location
       expr_ast = Code.string_to_ast!(test.expr)
       expected_ast = Code.string_to_ast!(test.expected)
       quote do
         import unquote(m)
         v = unquote(expected_ast)
         case unquote(expr_ast) do
           ^v -> :ok
           instead ->
             raise ExUnit.ExpectationError,
               prelude: "Expected doctest",
               expected: unquote(test.expr),
               actual: inspect(v),
               reason: "evaluate to",
               instead: instead
         end
       end
    end
    tests = Enum.zip(:lists.seq(1, length(tests)), tests)
    lc {{n, test}, body} inlist Enum.zip(tests, bodies) do
      quote do
        test unquote("#{type(test)} (#{n}) at #{mfa(test)}"), do: unquote(body)
      end
    end
  end

  defp tests(module, _) when is_atom(module), do: extract(module)
  defp tests({:__aliases__, _, _} = module, env) do
    {m, _} = Code.eval_quoted(module, [], env)
    tests(m, env)
  end
  defp tests({:., _, [m, f]}, env) do
    {m, _} = Code.eval_quoted(m, [], env)
    extract(m, f)
  end

  defp tests({:/, _, [{:., _, [m, f]}, a]}, env) do
    {m, _} = Code.eval_quoted(m, [], env)
    extract(m, f, a)
  end

  defp mfa(Test[location: {m,nil,nil}, line: line]) do
    "#{inspect m} (#{m.module_info[:compile][:source]}:#{line})"
  end
  defp mfa(Test[location: {m,f,a}, line: line]) do
    "#{inspect m}.#{f}/#{a} (#{m.module_info[:compile][:source]}:#{line})"
  end

  defp type(Test[location: {_, nil, nil}]), do: "moduledoc"
  defp type(_), do: "doc"

  @doc false
  def extract(module) do
    Enum.map(extract_from_doc(module.__info__(:moduledoc)), fn(test) ->
      test.location({module, nil, nil})
    end) ++
    lc {{f, a}, _, _, _, _} inlist module.__info__(:docs) do
       extract(module, f, a)
    end |> List.concat
  end

  def extract(module, function) do
    lc {{f, a}, _, _, _, _} inlist module.__info__(:docs), f == function do
       extract(module, function, a)
    end |> List.concat
  end

  def extract(module, function, arity) do
    doc = List.keyfind(module.__info__(:docs), {function, arity}, 0)
    Enum.map(extract_from_doc(doc), fn(test) -> test.location({module, function, arity}) end)
  end

  defp extract_from_doc(nil), do: []
  defp extract_from_doc({_, negative}) when negative in [false, nil], do: []
  defp extract_from_doc({line, doc}) do
    lines = String.split(doc, %r/\n/) |> Enum.map(function(String.strip/1))
    extract_from_doc(lines, line, "", "",[])
  end
  defp extract_from_doc({_, _, _, _, negative}) when negative in [false, nil], do: []
  defp extract_from_doc({_, line, _, _, doc}) do
    lines = String.split(doc, %r/\n/) |> Enum.map(function(String.strip/1))
    extract_from_doc(lines, line, "", "",[])
  end

  defp extract_from_doc([], _line, "", "", acc), do: Enum.reverse(acc)
  defp extract_from_doc([], line, expr_acc, expected_acc, acc) do
    Enum.reverse([Test.new(expr: expr_acc, line: line, expected: expected_acc)|acc])
  end
  defp extract_from_doc([<< "iex>", _ :: binary>>|_] = list, line, expr_acc, expected_acc, acc) when expr_acc != "" and expected_acc != "" do
    extract_from_doc(list, line, "", "", [Test.new(expr: expr_acc, line: line, expected: expected_acc)|acc])
  end
  defp extract_from_doc([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc == "" do
    extract_from_doc(lines, line, string, expected_acc, acc)
  end
  defp extract_from_doc([<< "iex>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
  end
  defp extract_from_doc([<< "...>", string :: binary>>|lines], line, expr_acc, expected_acc, acc) when expr_acc != "" do
    extract_from_doc(lines, line, expr_acc <> "\n" <> string, expected_acc, acc)
  end
  defp extract_from_doc([<< "iex(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(["iex" <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
  end
  defp extract_from_doc([<< "...(", _ :: 8, string :: binary>>|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(["..." <> skip_iex_number(string)|lines], line, expr_acc, expected_acc, acc)
  end
  defp extract_from_doc([_|lines], line, "", "", acc) do
    extract_from_doc(lines, line, "", "", acc)
  end
  defp extract_from_doc([""|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(lines, line,  "", "", [Test.new(expr: expr_acc, line: line, expected: expected_acc)|acc])
  end
  defp extract_from_doc([<< "** (", string :: binary >>|lines], line, expr_acc, "", acc) do
    opts = Regex.captures %r/(?<exception>.+)\) (?<message>.*)/g, string
    expected = "#{inspect Module.concat([opts[:exception]])}[message: #{inspect String.strip(opts[:message])}]"
    expr_acc = "try do ; #{expr_acc} ; rescue _e -> _e ; end"
    extract_from_doc(lines, line,  "", "", [Test.new(expr: expr_acc, line: line, expected: expected)|acc])
  end
  defp extract_from_doc([expected|lines], line, expr_acc, expected_acc, acc) do
    extract_from_doc(lines, line, expr_acc, expected_acc <> "\n" <> expected, acc)
  end

  defp skip_iex_number(<< ")", ">", string :: binary >>) do
    ">" <> string
  end
  defp skip_iex_number(<< _ :: 8, string :: binary >>) do
    skip_iex_number(string)
  end

end