From 76f54752da0fe11ce601480daf38ea036dd2f802 Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Wed, 20 Mar 2024 20:53:36 +0000 Subject: [PATCH] process.pid_from_dynamic Closes https://github.com/gleam-lang/erlang/issues/41 --- .github/workflows/test.yml | 4 +- CHANGELOG.md | 4 + gleam.toml | 2 +- src/gleam/erlang.gleam | 2 +- src/gleam/erlang/process.gleam | 26 ++- src/gleam_erlang_ffi.erl | 7 +- test/gleam/erlang/atom_test.gleam | 2 +- test/gleam/erlang/env_test.gleam | 2 +- test/gleam/erlang/node_tests.gleam | 2 +- test/gleam/erlang/process_test.gleam | 268 +++++++++++++-------------- test/gleam/erlang_test.gleam | 4 +- 11 files changed, 164 insertions(+), 159 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f4834e..74c73fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,8 +14,8 @@ jobs: - uses: actions/checkout@v3 - uses: erlef/setup-beam@v1 with: - otp-version: "25.2" + otp-version: "26.2" rebar3-version: "3" - gleam-version: "0.32" + gleam-version: "1.0.0" - run: gleam test - run: gleam format --check src test diff --git a/CHANGELOG.md b/CHANGELOG.md index cdb4088..4fd044e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.25.0 - 2024-03-20 + +- Updated for `gleam_stdlib` v0.33.0. + ## v0.24.0 - 2024-01-03 - Updated for `gleam_stdlib` v0.33.0. diff --git a/gleam.toml b/gleam.toml index 5a01266..99ee059 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,6 +1,6 @@ name = "gleam_erlang" -version = "0.24.0" +version = "0.25.0" licences = ["Apache-2.0"] description = "A Gleam library for working with Erlang" diff --git a/src/gleam/erlang.gleam b/src/gleam/erlang.gleam index 6119995..a1af182 100644 --- a/src/gleam/erlang.gleam +++ b/src/gleam/erlang.gleam @@ -1,7 +1,7 @@ import gleam/dynamic.{type Dynamic} -import gleam/list import gleam/erlang/atom.{type Atom} import gleam/erlang/charlist.{type Charlist} +import gleam/list @external(erlang, "io_lib", "format") fn erl_format(a: String, b: List(a)) -> Charlist diff --git a/src/gleam/erlang/process.gleam b/src/gleam/erlang/process.gleam index b621714..50f414a 100644 --- a/src/gleam/erlang/process.gleam +++ b/src/gleam/erlang/process.gleam @@ -1,7 +1,7 @@ -import gleam/string -import gleam/dynamic.{type Dynamic} +import gleam/dynamic.{type DecodeErrors, type Dynamic} import gleam/erlang.{type Reference} import gleam/erlang/atom.{type Atom} +import gleam/string /// A `Pid` (or Process identifier) is a reference to an Erlang process. Each /// process has a `Pid` and it is one of the lowest level building blocks of @@ -563,10 +563,9 @@ pub fn try_call( let result = new_selector() |> selecting(reply_subject, Ok) - |> selecting_process_down( - monitor, - fn(down: ProcessDown) { Error(CalleeDown(reason: down.reason)) }, - ) + |> selecting_process_down(monitor, fn(down: ProcessDown) { + Error(CalleeDown(reason: down.reason)) + }) |> select(timeout) // Demonitor the process and close the channels as we're done @@ -744,3 +743,18 @@ pub fn unregister(name: Atom) -> Result(Nil, Nil) /// @external(erlang, "gleam_erlang_ffi", "process_named") pub fn named(name: Atom) -> Result(Pid, Nil) + +/// Checks to see whether a `Dynamic` value is a pid, and return the pid if +/// it is. +/// +/// ## Examples +/// +/// > import gleam/dynamic +/// > from_dynamic(dynamic.from(process.self())) +/// Ok(process.self()) +/// +/// > from_dynamic(dynamic.from(123)) +/// Error([DecodeError(expected: "Pid", found: "Int", path: [])]) +/// +@external(erlang, "gleam_erlang_ffi", "pid_from_dynamic") +pub fn pid_from_dynamic(from from: Dynamic) -> Result(Pid, DecodeErrors) diff --git a/src/gleam_erlang_ffi.erl b/src/gleam_erlang_ffi.erl index 2138fd1..36492e2 100644 --- a/src/gleam_erlang_ffi.erl +++ b/src/gleam_erlang_ffi.erl @@ -6,7 +6,7 @@ new_selector/0, link/1, insert_selector_handler/3, select/1, select/2, trap_exits/1, map_selector/2, merge_selector/2, flush_messages/0, priv_directory/1, connect_node/1, register_process/2, unregister_process/1, - process_named/1, identity/1 + process_named/1, identity/1, pid_from_dynamic/1 ]). -spec atom_from_string(binary()) -> {ok, atom()} | {error, atom_not_loaded}. @@ -20,6 +20,11 @@ atom_from_dynamic(Data) when is_atom(Data) -> atom_from_dynamic(Data) -> {error, [{decode_error, <<"Atom">>, gleam@dynamic:classify(Data), []}]}. +pid_from_dynamic(Data) when is_pid(Data) -> + {ok, Data}; +pid_from_dynamic(Data) -> + {error, [{decode_error, <<"Pid">>, gleam@dynamic:classify(Data), []}]}. + -spec get_line(io:prompt()) -> {ok, unicode:unicode_binary()} | {error, eof | no_data}. get_line(Prompt) -> case io:get_line(Prompt) of diff --git a/test/gleam/erlang/atom_test.gleam b/test/gleam/erlang/atom_test.gleam index b8dbaaf..954ef4a 100644 --- a/test/gleam/erlang/atom_test.gleam +++ b/test/gleam/erlang/atom_test.gleam @@ -1,5 +1,5 @@ -import gleam/erlang/atom import gleam/dynamic.{DecodeError} +import gleam/erlang/atom pub fn from_string_test() { let assert Ok(_) = atom.from_string("ok") diff --git a/test/gleam/erlang/env_test.gleam b/test/gleam/erlang/env_test.gleam index d35a3c6..ef93b0c 100644 --- a/test/gleam/erlang/env_test.gleam +++ b/test/gleam/erlang/env_test.gleam @@ -1,5 +1,5 @@ -import gleam/erlang/os import gleam/dict +import gleam/erlang/os pub fn get_all_test() { os.set_env("MYVAR", "MYVALUE") diff --git a/test/gleam/erlang/node_tests.gleam b/test/gleam/erlang/node_tests.gleam index becd830..f98e68f 100644 --- a/test/gleam/erlang/node_tests.gleam +++ b/test/gleam/erlang/node_tests.gleam @@ -1,5 +1,5 @@ -import gleam/erlang/node import gleam/erlang/atom +import gleam/erlang/node // TODO: Improve these tests by spawning a peer node. diff --git a/test/gleam/erlang/process_test.gleam b/test/gleam/erlang/process_test.gleam index 531df9f..824b05d 100644 --- a/test/gleam/erlang/process_test.gleam +++ b/test/gleam/erlang/process_test.gleam @@ -1,10 +1,10 @@ -import gleam/int +import gleam/dynamic.{DecodeError} +import gleam/erlang/atom +import gleam/erlang/process.{ProcessDown} import gleam/float -import gleam/option.{Some} -import gleam/dynamic import gleam/function -import gleam/erlang/process.{ProcessDown} -import gleam/erlang/atom +import gleam/int +import gleam/option.{Some} pub fn self_test() { let subject = process.new_subject() @@ -100,15 +100,12 @@ pub fn monitor_test() { // Spawn child let parent_subject = process.new_subject() let pid = - process.start( - linked: False, - running: fn() { - let subject = process.new_subject() - process.send(parent_subject, subject) - // Wait for the parent to send a message before exiting - process.receive(subject, 150) - }, - ) + process.start(linked: False, running: fn() { + let subject = process.new_subject() + process.send(parent_subject, subject) + // Wait for the parent to send a message before exiting + process.receive(subject, 150) + }) // Monitor child let monitor = process.monitor_process(pid) @@ -133,15 +130,12 @@ pub fn demonitor_test() { // Spawn child let parent_subject = process.new_subject() let pid = - process.start( - linked: False, - running: fn() { - let subject = process.new_subject() - process.send(parent_subject, subject) - // Wait for the parent to send a message before exiting - process.receive(subject, 150) - }, - ) + process.start(linked: False, running: fn() { + let subject = process.new_subject() + process.send(parent_subject, subject) + // Wait for the parent to send a message before exiting + process.receive(subject, 150) + }) // Monitor child let monitor = process.monitor_process(pid) @@ -163,18 +157,15 @@ pub fn demonitor_test() { pub fn try_call_test() { let parent_subject = process.new_subject() - process.start( - linked: True, - running: fn() { - // Send the child subject to the parent so it can call the child - let child_subject = process.new_subject() - process.send(parent_subject, child_subject) - // Wait for the subject to be messaged - let assert Ok(#(x, reply)) = process.receive(child_subject, 50) - // Reply - process.send(reply, x + 1) - }, - ) + process.start(linked: True, running: fn() { + // Send the child subject to the parent so it can call the child + let child_subject = process.new_subject() + process.send(parent_subject, child_subject) + // Wait for the subject to be messaged + let assert Ok(#(x, reply)) = process.receive(child_subject, 50) + // Reply + process.send(reply, x + 1) + }) let assert Ok(call_subject) = process.receive(parent_subject, 50) @@ -186,18 +177,15 @@ pub fn try_call_test() { pub fn try_call_timeout_test() { let parent_subject = process.new_subject() - process.start( - linked: True, - running: fn() { - // Send the call subject to the parent - let child_subject = process.new_subject() - process.send(parent_subject, child_subject) - // Wait for the subject to be called - let assert Ok(_) = process.receive(child_subject, 50) - // Never reply - process.sleep(100) - }, - ) + process.start(linked: True, running: fn() { + // Send the call subject to the parent + let child_subject = process.new_subject() + process.send(parent_subject, child_subject) + // Wait for the subject to be called + let assert Ok(_) = process.receive(child_subject, 50) + // Never reply + process.sleep(100) + }) let assert Ok(call_subject) = process.receive(parent_subject, 50) @@ -209,18 +197,15 @@ pub fn try_call_timeout_test() { pub fn call_test() { let parent_subject = process.new_subject() - process.start( - linked: True, - running: fn() { - // Send the child subject to the parent so it can call the child - let child_subject = process.new_subject() - process.send(parent_subject, child_subject) - // Wait for the subject to be messaged - let assert Ok(#(x, reply)) = process.receive(child_subject, 50) - // Reply - process.send(reply, x + 1) - }, - ) + process.start(linked: True, running: fn() { + // Send the child subject to the parent so it can call the child + let child_subject = process.new_subject() + process.send(parent_subject, child_subject) + // Wait for the subject to be messaged + let assert Ok(#(x, reply)) = process.receive(child_subject, 50) + // Reply + process.send(reply, x + 1) + }) let assert Ok(call_subject) = process.receive(parent_subject, 50) @@ -256,98 +241,81 @@ pub fn selecting_record_test() { |> process.select(0) let assert Error(Nil) = process.new_selector() - |> process.selecting_record3( - "c", - fn(a, b) { #(dynamic.unsafe_coerce(a), dynamic.unsafe_coerce(b)) }, - ) + |> process.selecting_record3("c", fn(a, b) { + #(dynamic.unsafe_coerce(a), dynamic.unsafe_coerce(b)) + }) |> process.select(0) let assert Ok(#(22, 23, 24, 25, 26, 27, 28)) = process.new_selector() - |> process.selecting_record8( - "g", - fn(a, b, c, d, e, f, g) { - #( - dynamic.unsafe_coerce(a), - dynamic.unsafe_coerce(b), - dynamic.unsafe_coerce(c), - dynamic.unsafe_coerce(d), - dynamic.unsafe_coerce(e), - dynamic.unsafe_coerce(f), - dynamic.unsafe_coerce(g), - ) - }, - ) + |> process.selecting_record8("g", fn(a, b, c, d, e, f, g) { + #( + dynamic.unsafe_coerce(a), + dynamic.unsafe_coerce(b), + dynamic.unsafe_coerce(c), + dynamic.unsafe_coerce(d), + dynamic.unsafe_coerce(e), + dynamic.unsafe_coerce(f), + dynamic.unsafe_coerce(g), + ) + }) |> process.select(0) let assert Ok(#(16, 17, 18, 19, 20, 21)) = process.new_selector() - |> process.selecting_record7( - "f", - fn(a, b, c, d, e, f) { - #( - dynamic.unsafe_coerce(a), - dynamic.unsafe_coerce(b), - dynamic.unsafe_coerce(c), - dynamic.unsafe_coerce(d), - dynamic.unsafe_coerce(e), - dynamic.unsafe_coerce(f), - ) - }, - ) + |> process.selecting_record7("f", fn(a, b, c, d, e, f) { + #( + dynamic.unsafe_coerce(a), + dynamic.unsafe_coerce(b), + dynamic.unsafe_coerce(c), + dynamic.unsafe_coerce(d), + dynamic.unsafe_coerce(e), + dynamic.unsafe_coerce(f), + ) + }) |> process.select(0) let assert Ok(#(11, 12, 13, 14, 15)) = process.new_selector() - |> process.selecting_record6( - "e", - fn(a, b, c, d, e) { - #( - dynamic.unsafe_coerce(a), - dynamic.unsafe_coerce(b), - dynamic.unsafe_coerce(c), - dynamic.unsafe_coerce(d), - dynamic.unsafe_coerce(e), - ) - }, - ) + |> process.selecting_record6("e", fn(a, b, c, d, e) { + #( + dynamic.unsafe_coerce(a), + dynamic.unsafe_coerce(b), + dynamic.unsafe_coerce(c), + dynamic.unsafe_coerce(d), + dynamic.unsafe_coerce(e), + ) + }) |> process.select(0) let assert Ok(#(7, 8, 9, 10)) = process.new_selector() - |> process.selecting_record5( - "d", - fn(a, b, c, d) { - #( - dynamic.unsafe_coerce(a), - dynamic.unsafe_coerce(b), - dynamic.unsafe_coerce(c), - dynamic.unsafe_coerce(d), - ) - }, - ) + |> process.selecting_record5("d", fn(a, b, c, d) { + #( + dynamic.unsafe_coerce(a), + dynamic.unsafe_coerce(b), + dynamic.unsafe_coerce(c), + dynamic.unsafe_coerce(d), + ) + }) |> process.select(0) let assert Ok(#(4, 5, 6)) = process.new_selector() - |> process.selecting_record4( - "c", - fn(a, b, c) { - #( - dynamic.unsafe_coerce(a), - dynamic.unsafe_coerce(b), - dynamic.unsafe_coerce(c), - ) - }, - ) + |> process.selecting_record4("c", fn(a, b, c) { + #( + dynamic.unsafe_coerce(a), + dynamic.unsafe_coerce(b), + dynamic.unsafe_coerce(c), + ) + }) |> process.select(0) let assert Ok(#(2, 3)) = process.new_selector() - |> process.selecting_record3( - "b", - fn(a, b) { #(dynamic.unsafe_coerce(a), dynamic.unsafe_coerce(b)) }, - ) + |> process.selecting_record3("b", fn(a, b) { + #(dynamic.unsafe_coerce(a), dynamic.unsafe_coerce(b)) + }) |> process.select(0) let assert Ok(1) = @@ -378,18 +346,16 @@ pub fn linking_self_test() { pub fn linking_new_test() { let assert True = - process.link(process.start( - linked: False, - running: fn() { process.sleep(100) }, - )) + process.link( + process.start(linked: False, running: fn() { process.sleep(100) }), + ) } pub fn relinking_test() { let assert True = - process.link(process.start( - linked: True, - running: fn() { process.sleep(100) }, - )) + process.link( + process.start(linked: True, running: fn() { process.sleep(100) }), + ) } pub fn linking_dead_test() { @@ -400,18 +366,16 @@ pub fn linking_dead_test() { pub fn unlink_unlinked_test() { let assert Nil = - process.unlink(process.start( - linked: False, - running: fn() { process.sleep(100) }, - )) + process.unlink( + process.start(linked: False, running: fn() { process.sleep(100) }), + ) } pub fn unlink_linked_test() { let assert Nil = - process.unlink(process.start( - linked: True, - running: fn() { process.sleep(100) }, - )) + process.unlink( + process.start(linked: True, running: fn() { process.sleep(100) }), + ) } pub fn unlink_dead_test() { @@ -588,3 +552,21 @@ pub fn unregister_name_test() { let assert Error(Nil) = process.named(name) let _ = process.unregister(name) } + +pub fn pid_from_dynamic_test() { + let result = + process.self() + |> dynamic.from + |> process.pid_from_dynamic + let assert True = result == Ok(process.self()) + + let assert Error([DecodeError(expected: "Pid", found: "Int", path: [])]) = + 1 + |> dynamic.from + |> process.pid_from_dynamic + + let assert Error([DecodeError(expected: "Pid", found: "List", path: [])]) = + [] + |> dynamic.from + |> process.pid_from_dynamic +} diff --git a/test/gleam/erlang_test.gleam b/test/gleam/erlang_test.gleam index 45bb94e..347c2ef 100644 --- a/test/gleam/erlang_test.gleam +++ b/test/gleam/erlang_test.gleam @@ -1,8 +1,8 @@ -import gleam/string import gleam/dynamic -import gleam/iterator import gleam/erlang.{UnknownApplication} import gleam/erlang/atom +import gleam/iterator +import gleam/string pub fn term_to_binary_test() { let term = dynamic.from(#(1, "2", <<"hello":utf8>>))