Skip to content

Commit 997a798

Browse files
committed
Implementation of a basic test framework for IEx. Closes elixir-lang#919
1 parent a3aa1e8 commit 997a798

File tree

6 files changed

+191
-2
lines changed

6 files changed

+191
-2
lines changed

lib/ex_unit/lib/ex_unit/callbacks.ex

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ defmodule ExUnit.Callbacks do
2929
:ok
3030
end
3131
32+
teardown context do
33+
assert context[:hello] == "world"
34+
:ok
35+
end
36+
3237
test "always pass" do
3338
assert true
3439
end

lib/iex/lib/iex.ex

+5-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,11 @@ defmodule IEx do
205205
:application.start(:iex)
206206
end
207207
208-
defp boot_config(opts) do
208+
@doc """
209+
Returns default config used to launch IEx. This config is also used by
210+
IEx.TestFramework.
211+
"""
212+
def boot_config(opts) do
209213
scope = :elixir.scope_for_eval(
210214
file: "iex",
211215
delegate_locals_to: IEx.Helpers

lib/iex/test/iex/helpers_test.exs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Code.require_file "../test_helper.exs", __DIR__
2+
3+
defmodule IEx.Helpers.Test do
4+
use IEx.Case
5+
6+
test "h helper" do
7+
assert "# IEx.Helpers\n\nWelcome to Interactive Elixir" <> _ = capture_iex("h")
8+
end
9+
10+
test "h helper module" do
11+
assert "# Enumerable\n\nThis is the protocol used by the `Enum` module" <> _ = capture_iex("h Enumerable")
12+
assert capture_iex("h :whatever") == "Could not load module :whatever: nofile\n:ok"
13+
end
14+
15+
test "h helper function" do
16+
expand_1_re = %r/\* def expand\(path\)\n\nConverts the path to an absolute one and expands/
17+
expand_2_re = %r/\* def expand\(path, relative_to\)\n\nExpands the path relative to the path given as the second argument/
18+
19+
assert capture_iex("h Path.expand/1") =~ expand_1_re
20+
assert capture_iex("h Path.expand/2") =~ expand_2_re
21+
22+
output = capture_iex("h Path.expand")
23+
assert output =~ expand_1_re
24+
assert output =~ expand_2_re
25+
26+
assert capture_iex("h pwd") == "* def pwd()\n\nPrints the current working directory.\n\n:ok"
27+
end
28+
end

lib/iex/test/iex/interaction_test.exs

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
Code.require_file "../test_helper.exs", __DIR__
2+
3+
defmodule IEx.InteractionTest do
4+
use IEx.Case
5+
6+
### basic interaction ###
7+
8+
test "empty input" do
9+
assert capture_iex("\n") == "nil"
10+
end
11+
12+
test "normal input" do
13+
assert capture_iex("1 + 2") == "3"
14+
end
15+
16+
test "exception" do
17+
assert capture_iex("1 + :atom\n:this_is_still_working") =~ %r/^#{iex_format_exception(ArithmeticError,
18+
"bad argument in arithmetic expression")}.+\n:this_is_still_working$/s
19+
end
20+
21+
test "empty history at the start" do
22+
assert "** (RuntimeError) Out of bounds" <> _ = capture_iex("v(-1)")
23+
end
24+
25+
test "empty history at the start redux" do
26+
assert "** (RuntimeError) Out of bounds" <> _ = capture_iex("v(1)")
27+
end
28+
29+
test "no break" do
30+
input = """
31+
["a
32+
b
33+
c
34+
"""
35+
assert capture_iex(input) == ""
36+
end
37+
38+
test "break" do
39+
input = """
40+
["a
41+
b
42+
c
43+
#iex:break
44+
"""
45+
assert "** (TokenMissingError) iex:1: incomplete expression" <> _ = capture_iex(input)
46+
end
47+
48+
### .iex file loading ###
49+
50+
test "no .iex" do
51+
assert "** (UndefinedFunctionError) undefined function: IEx.Helpers.my_variable/0" <> _ = capture_iex("my_variable")
52+
end
53+
54+
test ".iex" do
55+
File.write!("dot-iex", "my_variable = 144")
56+
assert capture_iex("my_variable", [], "dot-iex") == "144"
57+
after
58+
File.rm!("dot-iex")
59+
end
60+
61+
test "nested .iex" do
62+
File.write!("dot-iex-1", "nested_var = 13\nimport IO")
63+
File.write!("dot-iex", "import_file \"dot-iex-1\"\nmy_variable=14")
64+
65+
input = "nested_var\nmy_variable\nputs \"hello\""
66+
assert capture_iex(input, [], "dot-iex") == "13\n14\nhello\n:ok"
67+
after
68+
File.rm("dot-iex-1")
69+
File.rm!("dot-iex")
70+
end
71+
end

lib/iex/test/iex/options_test.exs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Code.require_file "../test_helper.exs", __DIR__
2+
3+
defmodule IEx.Options.Test do
4+
use IEx.Case
5+
6+
test "color" do
7+
opts = [colors: [enabled: true, eval_result: "red"]]
8+
assert capture_iex("1 + 2", opts) == "\e[31m3\e[0m"
9+
end
10+
11+
test "inspect opts" do
12+
opts = [inspect: [limit: 3, raw: true]]
13+
assert capture_iex("[1,2,3,4,5]\nArgumentError[]", opts) ==
14+
"[1,2,3,...]\n{ArgumentError,:__exception__,\"argument error\"}"
15+
16+
opts = [inspect: [raw: false]]
17+
assert capture_iex("ArgumentError[]", opts) == "ArgumentError[message: \"argument error\"]"
18+
end
19+
20+
test "history size" do
21+
opts = [history_size: 3]
22+
assert capture_iex("1\n2\n3\nv(1)", opts) == "1\n2\n3\n1"
23+
assert "1\n2\n3\n4\n** (RuntimeError) Out of bounds" <> _ = capture_iex("1\n2\n3\n4\nv(1)", opts)
24+
assert "1\n2\n3\n4\n** (RuntimeError) Out of bounds" <> _ = capture_iex("1\n2\n3\n4\nv(-4)", opts)
25+
assert "1\n2\n3\n4\n2\n** (RuntimeError) Out of bounds" <> _ = capture_iex("1\n2\n3\n4\nv(2)\nv(2)", opts)
26+
end
27+
end
28+

lib/iex/test/test_helper.exs

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,55 @@
11
:application.start(:iex)
2-
ExUnit.start []
2+
ExUnit.start []
3+
4+
defmodule IEx.Case do
5+
defmacro __using__(_) do
6+
quote do
7+
use ExUnit.Case, async: false
8+
import unquote(__MODULE__)
9+
10+
setup do
11+
opts = IEx.Options.get
12+
IEx.Options.set :colors, [enabled: false]
13+
{ :ok, [iex_opts: opts] }
14+
end
15+
16+
teardown context do
17+
IEx.Options.set context[:iex_opts]
18+
:ok
19+
end
20+
end
21+
end
22+
23+
def capture_iex(input, options // [], dot_iex_path // "") do
24+
Enum.each options, fn { opt, value } ->
25+
IEx.Options.set(opt, value)
26+
end
27+
28+
ExUnit.CaptureIO.capture_io(input, fn ->
29+
IEx.Server.start(iex_config(dot_iex_path: dot_iex_path))
30+
end) |> strip_iex
31+
end
32+
33+
def iex_exception(name, message // "") do
34+
%r/#{iex_format_exception(name, message)}/
35+
end
36+
37+
def iex_format_exception(name, message // "") do
38+
"\\*\\* \\(#{Module.to_binary name}\\) (.*?)#{message}"
39+
end
40+
41+
defp iex_config(opts) do
42+
IEx.boot_config(opts)
43+
end
44+
45+
defp strip_iex(string) do
46+
string
47+
|> strip_line # strip the greeting
48+
|> String.strip
49+
end
50+
51+
defp strip_line(string) do
52+
Regex.replace %r/\A.+?$/ms, string, ""
53+
end
54+
end
55+

0 commit comments

Comments
 (0)