Current section
Files
Jump to
Current section
Files
lib/zigler/unit.ex
defmodule Zigler.Unit do
@moduledoc """
Hooks your zig code into ExUnit, by converting zig tests into ExUnit tests.
### Usage
#### Example
Inside your zig code (`dependent.zig`):
```
const beam = @import("beam.zig");
const assert = beam.assert;
fn one() i64 {
return 1;
}
test "the one function returns one" {
assert(one() == 1);
}
```
Inside your elixir code:
```
defmodule MyZigModule do
use Zigler, app: :my_app
~Z\"""
const dependent = @import("dependent.zig");
/// nif: some_nif_fn/1
...
\"""
end
```
Inside your test module:
```
defmodule MyZigTest do
use ExUnit.Case, async: true
use Zigler.Unit
zigtest MyZigModule
end
```
### Scope
This module will run tests in all zig code that resides in the same code
directory as the base module (or overridden directory, if applicable). Zig
code in subdirectories will not be subjected to test conversion, so if you
would like to run a subset of tests using the Zig test facility (and without
the support of a BEAM VM), you should put them in subdirectories.
"""
defstruct [:title, :name]
@typedoc false
@type t :: %__MODULE__{title: String.t, }
alias Zigler.Unit.Parser
@doc false
def string_to_hash(str) do
hash = :md5
|> :crypto.hash(str)
|> Base.encode16
"test_" <> hash
end
defmacro __using__(_) do
quote do
# tests are always going to be released in safe mode.
@release_mode :safe
@on_load :__load_nifs__
# don't persist the app or the version, we are going to get that from the parent.
Module.register_attribute(__MODULE__, :zig_specs, accumulate: true)
Module.register_attribute(__MODULE__, :zig_code, accumulate: true, persist: true)
Module.register_attribute(__MODULE__, :zig_imports, accumulate: true)
Module.register_attribute(__MODULE__, :zig_src_dir, persist: true)
Module.register_attribute(__MODULE__, :zig_test, persist: true)
@zig_test true
@before_compile Zigler.Compiler
import Zigler.Unit
end
end
@doc """
loads a module that wraps a zigler NIF, consults the corresponding zig code,
generating the corresponding zig tests.
Must be called from within a module that has run `use ExUnit.Case`.
"""
defmacro zigtest(mod) do
Process.sleep(5000)
module = Macro.expand(mod, __CALLER__)
source_file = module.__info__(:compile)[:source]
src_dir = Path.dirname(source_file)
code = module.__info__(:attributes)[:zig_code]
|> IO.iodata_to_binary
|> Parser.get_tests(source_file)
[zigler_app] = module.__info__(:attributes)[:zigler_app]
[zig_version] = module.__info__(:attributes)[:zig_version]
code_spec = Enum.map(code.tests, &{&1.name, {[], "void"}})
empty_functions = code.tests
|> Enum.map(&%{&1 | name: &1.name |> String.split(".") |> List.last})
|> Enum.map(&Zigler.empty_function(String.to_atom(&1.name), 0))
compilation = quote do
@zigler_app unquote(zigler_app)
@zig_version unquote(zig_version)
@zig_code unquote(code.code)
@zig_specs unquote(code_spec)
@zig_src_dir unquote(src_dir)
unquote_splicing(empty_functions)
end
test_list = Enum.map(code.tests, &{&1.title, &1.name})
in_ex_unit = Application.started_applications
|> Enum.any?(fn {app, _, _} -> app == :ex_unit end)
test = make_code(__CALLER__.module, __CALLER__.file, test_list , in_ex_unit)
[compilation, test]
end
defp make_code(module, file, tests, true) do
quote bind_quoted: [module: module, file: file, tests: tests] do
# register our tests.
env = __ENV__
for {name, test} <- Zigler.Unit.__zigtests__(module, tests) do
@file file
test_name = ExUnit.Case.register_test(env, :zigtest, name, [])
def unquote(test_name)(_), do: unquote(test)
end
end
end
# for testing purposes only:
defp make_code(module, _, tests, false) do
quote bind_quoted: [module: module, tests: tests] do
# register our tests.
for {name, test} <- Zigler.Unit.__zigtests__(module, tests) do
test_name = name |> string_to_hash |> String.to_atom
def unquote(test_name)(_), do: unquote(test)
end
end
end
def __zigtests__(module, tests) do
Enum.map(tests, fn
{title, name} -> {title, test_content(module, name)}
end)
end
defp test_content(module, name) do
atom_name = name
|> String.split(".")
|> List.last
|> String.to_atom
quote do
try do
apply(unquote(module), unquote(atom_name), [])
:ok
rescue
e in ErlangError ->
error = [
message: "Zig test failed",
doctest: ExUnit.AssertionError.no_value(),
expr: ExUnit.AssertionError.no_value(),
left: ExUnit.AssertionError.no_value(),
right: ExUnit.AssertionError.no_value()
]
reraise ExUnit.AssertionError, error, __STACKTRACE__
end
end
end
end