Skip to content

Commit

Permalink
Create a proper OTP application and add some tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
thmsmlr committed Dec 2, 2024
1 parent 3ebf9b3 commit d5ac699
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 12 deletions.
17 changes: 17 additions & 0 deletions lib/app.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Livescript.App do
use Application

def start(_type, [script_path]) do
qualified_script_path = Path.absname(script_path) |> Path.expand()

children = [
{Task.Supervisor, name: Livescript.TaskSupervisor},
{Livescript.Executor, []},
{Livescript, qualified_script_path},
{Task, fn -> Livescript.TCP.server() end}
]

opts = [strategy: :one_for_one, name: Livescript.Supervisor]
{:ok, _} = Supervisor.start_link(children, opts)
end
end
12 changes: 10 additions & 2 deletions lib/livescript.ex
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ defmodule Livescript do
unescape: false
]

with {:ok, {_, _, quoted}} <- Code.string_to_quoted(code),
{:ok, {_, _, precise_quoted}} <- Code.string_to_quoted(code, parse_opts) do
with {:ok, quoted} <- string_to_quoted_expressions(code),
{:ok, precise_quoted} <- string_to_quoted_expressions(code, parse_opts) do
exprs =
Enum.zip(quoted, precise_quoted)
|> Enum.map(fn {quoted, precise_quoted} ->
Expand Down Expand Up @@ -319,6 +319,14 @@ defmodule Livescript do
end
end

defp string_to_quoted_expressions(code, opts \\ []) do
case Code.string_to_quoted(code, opts) do
{:ok, {:__block__, _, quoted}} -> {:ok, quoted}
{:ok, quoted} -> {:ok, [quoted]}
{:error, reason} -> {:error, reason}
end
end

@doc """
Merges adjacent expressions into blocks. Two expressions are considered adjacent
if the end line of one expression is immediately followed by the start line of another.
Expand Down
11 changes: 1 addition & 10 deletions lib/mix/tasks/livescript.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,7 @@ defmodule Mix.Tasks.Livescript do
Task.async(fn ->
qualified_exs_path = Path.absname(exs_path) |> Path.expand()
Logger.put_module_level(Livescript, :info)

children = [
{Task.Supervisor, name: Livescript.TaskSupervisor},
{Livescript.Executor, []},
{Livescript, qualified_exs_path},
{Task, fn -> Livescript.TCP.server() end}
]

opts = [strategy: :one_for_one, name: Livescript.Supervisor]
{:ok, _} = Supervisor.start_link(children, opts)
{:ok, _} = Livescript.App.start(:normal, [qualified_exs_path])
Process.sleep(:infinity)
end)
end
Expand Down
170 changes: 170 additions & 0 deletions test/livescript_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,174 @@ defmodule LivescriptTest do
assert common ++ rest2 == second
end
end

describe "e2e" do
setup do
unique_id =
:crypto.hash(:md5, :erlang.term_to_binary(:erlang.make_ref())) |> Base.encode16()

script_path = Path.join(System.tmp_dir!(), "livescript_test_#{unique_id}")
File.touch!(script_path)

{:ok, iex_app_pid} = IEx.App.start(:normal, [])
{:ok, livescript_app_pid} = Livescript.App.start(:normal, [script_path])

iex_server =
spawn_link(fn ->
IEx.Server.run([])
Process.sleep(:infinity)
end)

on_exit(fn ->
File.rm!(script_path)
Process.exit(iex_app_pid, :shutdown)
Process.exit(iex_server, :shutdown)
Process.exit(livescript_app_pid, :shutdown)
end)

%{script_path: script_path}
end

defp call_home_with(val) do
quote do
send(
:erlang.list_to_pid(unquote(:erlang.pid_to_list(self()))),
{:__livescript_test_return, unquote(val)}
)
end
|> Macro.to_string()
end

defp get_return_value() do
receive do
{:__livescript_test_return, val} -> val
after
5000 -> raise "Timeout waiting for return value"
end
end

defp update_script(script_path, code) do
mtime = File.stat!(script_path).mtime
File.write!(script_path, code)
wait_for_change(script_path, mtime)
end

defp wait_for_change(path, mtime) do
new_mtime = File.stat!(path).mtime

if new_mtime == mtime do
File.touch!(path)
Process.sleep(30)
wait_for_change(path, mtime)
end
end

test "can run a simple script", %{script_path: script_path} do
update_script(script_path, """
IO.puts("Hello World")
IO.puts("This is a test")
#{call_home_with(:__livescript_test_ok)}
""")

assert get_return_value() == :__livescript_test_ok
end

test "can run a script with bindings", %{script_path: script_path} do
update_script(script_path, """
a = 1
b = 2
#{call_home_with(quote do: a + b)}
""")

assert get_return_value() == 3
end

test "overwrites a binding", %{script_path: script_path} do
update_script(script_path, """
a = 1
b = 2
#{call_home_with(quote do: a + b)}
""")

assert get_return_value() == 3

update_script(script_path, """
a = 1
b = 3
#{call_home_with(quote do: a + b)}
""")

assert get_return_value() == 4
end

test "error doesn't crash the server", %{script_path: script_path} do
update_script(script_path, """
a = 1
raise "This is a test error"
IO.inspect(a, label: "a")
""")

update_script(script_path, """
a = 1
IO.inspect(a, label: "a")
#{call_home_with(quote do: a)}
""")

assert get_return_value() == 1
end

test "can use structs", %{script_path: script_path} do
update_script(script_path, """
defmodule Test do
defstruct a: 1
end
t = %Test{a: 2}
#{call_home_with(quote do: t)}
""")

assert %{a: 2} = get_return_value()
end

test "doesn't rerun stale expressions", %{script_path: script_path} do
update_script(script_path, """
a = 1
#{call_home_with(quote do: a)}
""")

assert get_return_value() == 1

update_script(script_path, """
a = 1
#{call_home_with(quote do: a)}
b = 2
#{call_home_with(quote do: a + b)}
""")

# If not true, we rerun the first expression and get 1,
# then the second and get 3
assert get_return_value() == 3
end

test "is running in same session", %{script_path: script_path} do
update_script(script_path, """
Process.put(:livescript_test_key, :foobar)
#{call_home_with(:ok)}
""")

assert :ok = get_return_value()

update_script(script_path, """
#{call_home_with(quote do: Process.get(:livescript_test_key))}
""")

assert :foobar = get_return_value()
end

test "can run empty script", %{script_path: script_path} do
update_script(script_path, "")
update_script(script_path, "#{call_home_with(:ok)}")
assert :ok = get_return_value()
end
end
end

0 comments on commit d5ac699

Please sign in to comment.