Skip to content

Commit 515f752

Browse files
committed
elixir: async tests
Provide tests to exercise the async callback type conversions. Signed-off-by: Nathan Perry <nathan@tailscale.com> Change-Id: Iafe01153ca52b9f618c80666de5e6f186a6a6964 Change-Id: I9d1d3e23de8a558fc6f0f81677b6237e6a6a6964 Signed-off-by: Nathan Perry <nathan@tailscale.com>
1 parent 66729dd commit 515f752

9 files changed

Lines changed: 159 additions & 3 deletions

File tree

ts_elixir/config/config.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Config
2+
3+
config :tailscale,
4+
testing_nifs: false,
5+
profile: :debug
6+
7+
import_config "#{config_env()}.exs"

ts_elixir/config/dev.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Config
2+
3+
config :tailscale,
4+
testing_nifs: true

ts_elixir/config/prod.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Config
2+
3+
config :tailscale,
4+
profile: :release

ts_elixir/config/test.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Config
2+
3+
config :tailscale,
4+
testing_nifs: true

ts_elixir/lib/tailscale/native.ex

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
defmodule Tailscale.Native do
2+
@moduledoc false
3+
4+
@testing_nifs Application.compile_env!(:tailscale, :testing_nifs)
5+
@profile Application.compile_env!(:tailscale, :profile)
6+
7+
@features (if @testing_nifs do
8+
["testing-nifs"]
9+
else
10+
[]
11+
end)
12+
213
use Rustler,
314
otp_app: :tailscale,
4-
crate: :ts_elixir
5-
6-
@moduledoc false
15+
crate: :ts_elixir,
16+
mode: @profile,
17+
features: @features
718

819
# The Elixir side of the Rustler bindings to `tailscale-rs`.
920
#

ts_elixir/native/ts_elixir/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ tokio = { workspace = true, features = ["full"] }
2020
tracing = { workspace = true }
2121
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
2222

23+
[features]
24+
# Additional testing functions that can directly trigger panics and errors to exercise the code in
25+
# development.
26+
testing-nifs = []
27+
2328
[lib]
2429
crate-type = ["cdylib"]
2530

ts_elixir/native/ts_elixir/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ mod helpers;
1616
mod ip_or_self;
1717
mod node_info;
1818
mod tcp;
19+
#[cfg(feature = "testing-nifs")]
20+
mod testing_nifs;
1921
mod udp;
2022

2123
use async_reply::{AsyncReply, try_reply_async};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! NIFs that intentionally return errors, panic, and raise exceptions.
2+
//!
3+
//! These are intended for testing the async message passing code and require the
4+
//! `testing-nifs` feature flag to be enabled.
5+
6+
use rustler::{Env, Error};
7+
8+
use crate::async_reply::{AsyncReply, try_reply_async};
9+
10+
#[rustler::nif]
11+
pub fn async_panic(env: Env, msg: Option<String>) -> AsyncReply {
12+
try_reply_async(env, async move {
13+
if let Some(msg) = msg {
14+
panic!("{msg}");
15+
} else {
16+
panic!()
17+
}
18+
19+
// Needed to indicate return type
20+
#[allow(unreachable_code)]
21+
Ok(())
22+
})
23+
}
24+
25+
#[rustler::nif]
26+
pub fn async_error<'e>(env: Env<'e>, s: String, atom: bool) -> AsyncReply<'e> {
27+
try_reply_async(env, async move {
28+
Result::<(), _>::Err(if atom {
29+
Error::Atom(String::leak(s))
30+
} else {
31+
Error::Term(Box::new(s))
32+
})
33+
})
34+
}
35+
36+
#[rustler::nif]
37+
pub fn async_raise<'e>(env: Env<'e>, s: String, atom: bool) -> AsyncReply<'e> {
38+
try_reply_async(env, async move {
39+
Result::<(), _>::Err(if atom {
40+
Error::RaiseAtom(String::leak(s))
41+
} else {
42+
Error::RaiseTerm(Box::new(s))
43+
})
44+
})
45+
}
46+
47+
#[rustler::nif]
48+
pub fn async_badarg<'e>(env: Env<'e>) -> AsyncReply<'e> {
49+
try_reply_async(env, async move { Result::<(), _>::Err(Error::BadArg) })
50+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
defmodule Tailscale.Test.AsyncCallbacks do
2+
use ExUnit.Case, async: true
3+
require Tailscale.Util
4+
alias Tailscale.Native
5+
6+
defmacrop await(block, local) do
7+
if local do
8+
quote do
9+
Tailscale.Util.await_local(unquote(block))
10+
end
11+
else
12+
quote do
13+
Tailscale.Util.await(unquote(block))
14+
end
15+
end
16+
end
17+
18+
for local <- [true, false] do
19+
describe "async calls (local: #{local})" do
20+
for msg <- ["msg", nil] do
21+
test "panic (msg: #{msg})" do
22+
result = await(Native.async_panic(unquote(msg)), local)
23+
24+
{:error, {:nif_panic, arg}} = result
25+
26+
if unquote(msg) != nil do
27+
assert(arg == "msg")
28+
end
29+
end
30+
end
31+
32+
for atom <- [true, false] do
33+
test "error (atom: #{atom})" do
34+
assert(
35+
await(Native.async_error("msg", unquote(atom)), local) ==
36+
{:error,
37+
if unquote(atom) do
38+
:msg
39+
else
40+
"msg"
41+
end}
42+
)
43+
end
44+
45+
test("raise (atom: #{atom})") do
46+
assert_raise RuntimeError, fn ->
47+
msg =
48+
if unquote(atom) do
49+
"Elixir.RuntimeError"
50+
else
51+
"msg"
52+
end
53+
54+
await(
55+
Native.async_raise(msg, unquote(atom)),
56+
local
57+
)
58+
end
59+
end
60+
end
61+
62+
test "badarg" do
63+
assert_raise ArgumentError, fn ->
64+
await(Native.async_badarg(), local)
65+
end
66+
end
67+
end
68+
end
69+
end

0 commit comments

Comments
 (0)