This test runs `System.cmd/3` against a symlink to the `echo` command.
This works when `echo` is its own binary and when it's in a multicall
binary that's dispatched using `argv[0]`. It doesn't work for the new
Rust/uutils coreutils which doesn't dispatch via `argv[0]`. In this
case, intended call to echo produces no output:
```
1) test Unix cmd/3 with absolute and relative paths (SystemTest)
test/elixir/system_test.exs:210
match (=) failed
code: assert {"hello\n", 0} = System.cmd(Path.join(File.cwd!(), @echo), ["hello"], arg0: "echo")
left: {"hello\n", 0}
right: {"", 0}
stacktrace:
test/elixir/system_test.exs:223: anonymous fn/0 in SystemTest."test Unix cmd/3 with absolute and relative paths"/1
(elixir 1.21.0-dev) lib/file.ex:2100: File.cd!/2
test/elixir/system_test.exs:215: (test)
```
This fix switches the binary used for the test over to `sh` which fixes
the issue on Ubuntu 26.04. This was also verified on Ubuntu 24.04,
Ubuntu 22.04, Alpine Linux 3.24, macOS Tahoe (26.5.1) and MacOS Sequoia
(15.3.2).
365 lines
11 KiB
Elixir
365 lines
11 KiB
Elixir
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
|
# SPDX-FileCopyrightText: 2012 Plataformatec
|
|
|
|
Code.require_file("test_helper.exs", __DIR__)
|
|
|
|
defmodule SystemTest do
|
|
use ExUnit.Case
|
|
import PathHelpers
|
|
|
|
test "build_info/0" do
|
|
build_info = System.build_info()
|
|
assert is_map(build_info)
|
|
assert is_binary(build_info[:build])
|
|
assert is_binary(build_info[:date])
|
|
assert is_binary(build_info[:revision])
|
|
assert is_binary(build_info[:version])
|
|
assert is_binary(build_info[:otp_release])
|
|
|
|
if build_info[:revision] != "" do
|
|
assert String.length(build_info[:revision]) >= 7
|
|
end
|
|
|
|
version_file = Path.join([__DIR__, "../../../..", "VERSION"]) |> Path.expand()
|
|
{:ok, version} = File.read(version_file)
|
|
assert build_info[:version] == String.trim(version)
|
|
assert build_info[:build] =~ "compiled with Erlang/OTP"
|
|
end
|
|
|
|
test "user_home/0" do
|
|
assert is_binary(System.user_home())
|
|
assert is_binary(System.user_home!())
|
|
end
|
|
|
|
test "tmp_dir/0" do
|
|
assert is_binary(System.tmp_dir())
|
|
assert is_binary(System.tmp_dir!())
|
|
end
|
|
|
|
test "endianness/0" do
|
|
assert System.endianness() in [:little, :big]
|
|
assert System.endianness() == System.compiled_endianness()
|
|
end
|
|
|
|
test "pid/0" do
|
|
assert is_binary(System.pid())
|
|
end
|
|
|
|
test "argv/0" do
|
|
list = elixir(~c"-e \"IO.inspect System.argv()\" -- -o opt arg1 arg2 --long-opt 10")
|
|
{args, _} = Code.eval_string(list, [])
|
|
assert args == ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
|
|
end
|
|
|
|
@test_var "SYSTEM_ELIXIR_ENV_TEST_VAR"
|
|
|
|
test "get_env/put_env/delete_env" do
|
|
assert System.get_env(@test_var) == nil
|
|
assert System.get_env(@test_var, "SAMPLE") == "SAMPLE"
|
|
assert System.fetch_env(@test_var) == :error
|
|
|
|
message = "could not fetch environment variable #{inspect(@test_var)} because it is not set"
|
|
assert_raise System.EnvError, message, fn -> System.fetch_env!(@test_var) end
|
|
|
|
System.put_env(@test_var, "SAMPLE")
|
|
|
|
assert System.get_env(@test_var) == "SAMPLE"
|
|
assert System.get_env()[@test_var] == "SAMPLE"
|
|
assert System.fetch_env(@test_var) == {:ok, "SAMPLE"}
|
|
assert System.fetch_env!(@test_var) == "SAMPLE"
|
|
|
|
System.delete_env(@test_var)
|
|
assert System.get_env(@test_var) == nil
|
|
|
|
assert_raise ArgumentError, ~r[cannot execute System.put_env/2 for key with \"=\"], fn ->
|
|
System.put_env("FOO=BAR", "BAZ")
|
|
end
|
|
end
|
|
|
|
test "put_env/2" do
|
|
System.put_env(%{@test_var => "MAP_STRING"})
|
|
assert System.get_env(@test_var) == "MAP_STRING"
|
|
|
|
System.put_env([{String.to_unsafe_atom(@test_var), "KW_ATOM"}])
|
|
assert System.get_env(@test_var) == "KW_ATOM"
|
|
|
|
System.put_env([{String.to_unsafe_atom(@test_var), nil}])
|
|
assert System.get_env(@test_var) == nil
|
|
end
|
|
|
|
test "cmd/2 raises for null bytes" do
|
|
assert_raise ArgumentError, ~r"cannot execute System.cmd/3 for program with null byte", fn ->
|
|
System.cmd("null\0byte", [])
|
|
end
|
|
end
|
|
|
|
test "cmd/3 raises with non-binary arguments" do
|
|
assert_raise ArgumentError, ~r"all arguments for System.cmd/3 must be binaries", fn ->
|
|
System.cmd("ls", [~c"/usr"])
|
|
end
|
|
end
|
|
|
|
describe "Windows" do
|
|
@describetag :windows
|
|
|
|
test "cmd/2" do
|
|
assert {"hello\r\n", 0} = System.cmd("cmd", ~w[/c echo hello])
|
|
end
|
|
|
|
test "cmd/3 (with options)" do
|
|
opts = [
|
|
into: [],
|
|
cd: File.cwd!(),
|
|
env: %{"foo" => "bar", "baz" => nil},
|
|
arg0: "echo",
|
|
stderr_to_stdout: true,
|
|
parallelism: true,
|
|
use_stdio: true
|
|
]
|
|
|
|
assert {["hello\r\n"], 0} = System.cmd("cmd", ~w[/c echo hello], opts)
|
|
end
|
|
|
|
@echo "echo-elixir-test"
|
|
@tag :tmp_dir
|
|
test "cmd/3 with absolute and relative paths", config do
|
|
echo = Path.join(config.tmp_dir, @echo)
|
|
File.mkdir_p!(Path.dirname(echo))
|
|
File.ln_s!(System.find_executable("cmd"), echo)
|
|
|
|
File.cd!(Path.dirname(echo), fn ->
|
|
# There is a bug in OTP where find_executable is finding
|
|
# entries on the current directory. If this is the case,
|
|
# we should avoid the assertion below.
|
|
if !System.find_executable(@echo) do
|
|
assert :enoent = catch_error(System.cmd(@echo, ~w[/c echo hello]))
|
|
end
|
|
|
|
assert {"hello\r\n", 0} =
|
|
System.cmd(Path.join(File.cwd!(), @echo), ~w[/c echo hello], [{:arg0, "echo"}])
|
|
end)
|
|
end
|
|
|
|
test "shell/1" do
|
|
assert {"hello\r\n", 0} = System.shell("echo hello")
|
|
end
|
|
|
|
test "shell/2 (with options)" do
|
|
opts = [
|
|
into: [],
|
|
cd: File.cwd!(),
|
|
env: %{"foo" => "bar", "baz" => nil},
|
|
stderr_to_stdout: true,
|
|
parallelism: true,
|
|
use_stdio: true
|
|
]
|
|
|
|
assert {["bar\r\n"], 0} = System.shell("echo %foo%", opts)
|
|
end
|
|
end
|
|
|
|
describe "Unix" do
|
|
@describetag :unix
|
|
|
|
test "cmd/2" do
|
|
assert {"hello\n", 0} = System.cmd("echo", ["hello"])
|
|
end
|
|
|
|
test "cmd/3 (with options)" do
|
|
opts = [
|
|
into: [],
|
|
cd: File.cwd!(),
|
|
env: %{"foo" => "bar", "baz" => nil},
|
|
arg0: "echo",
|
|
stderr_to_stdout: true,
|
|
parallelism: true,
|
|
use_stdio: true
|
|
]
|
|
|
|
assert {["hello\n"], 0} = System.cmd("echo", ["hello"], opts)
|
|
end
|
|
|
|
test "cmd/3 (can't use `use_stdio: false, stderr_to_stdout: true`)" do
|
|
opts = [
|
|
into: [],
|
|
cd: File.cwd!(),
|
|
env: %{"foo" => "bar", "baz" => nil},
|
|
arg0: "echo",
|
|
stderr_to_stdout: true,
|
|
use_stdio: false
|
|
]
|
|
|
|
message = ~r"cannot use \"stderr_to_stdout: true\" and \"use_stdio: false\""
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
System.cmd("echo", ["hello"], opts)
|
|
end
|
|
end
|
|
|
|
test "cmd/3 by line" do
|
|
assert {["hello", "world"], 0} =
|
|
System.cmd("echo", ["hello\nworld"], into: [], lines: 1024)
|
|
|
|
assert {["hello", "world"], 0} =
|
|
System.cmd("echo", ["-n", "hello\nworld"], into: [], lines: 3)
|
|
end
|
|
|
|
@echo "echo-elixir-test"
|
|
@tag :tmp_dir
|
|
test "cmd/3 with absolute and relative paths", config do
|
|
echo = Path.join(config.tmp_dir, @echo)
|
|
File.mkdir_p!(Path.dirname(echo))
|
|
File.ln_s!(System.find_executable("sh"), echo)
|
|
|
|
File.cd!(Path.dirname(echo), fn ->
|
|
# There is a bug in OTP where find_executable is finding
|
|
# entries on the current directory. If this is the case,
|
|
# we should avoid the assertion below.
|
|
if !System.find_executable(@echo) do
|
|
assert :enoent = catch_error(System.cmd(@echo, ["-c", "echo hello"]))
|
|
end
|
|
|
|
assert {"hello\n", 0} =
|
|
System.cmd(Path.join(File.cwd!(), @echo), ["-c", "echo hello"], [{:arg0, "sh"}])
|
|
end)
|
|
end
|
|
|
|
test "shell/1" do
|
|
assert {"hello\n", 0} = System.shell("echo hello")
|
|
end
|
|
|
|
test "shell/1 with interpolation" do
|
|
assert {"1\n2\n", 0} = System.shell("x=1; echo $x; echo '2'")
|
|
end
|
|
|
|
test "shell/1 with empty string" do
|
|
assert {"", 0} = System.shell("")
|
|
assert {"", 0} = System.shell(" ")
|
|
end
|
|
|
|
@tag timeout: 1_000
|
|
test "shell/1 returns when command awaits input" do
|
|
assert {"", 0} = System.shell("cat", close_stdin: true)
|
|
end
|
|
|
|
test "shell/1 with comment" do
|
|
assert {"1\n", 0} = System.shell("echo '1' # comment")
|
|
end
|
|
|
|
test "shell/2 (with options)" do
|
|
opts = [
|
|
into: [],
|
|
cd: File.cwd!(),
|
|
env: %{"foo" => "bar", "baz" => nil},
|
|
stderr_to_stdout: true,
|
|
use_stdio: true
|
|
]
|
|
|
|
assert {["bar\n"], 0} = System.shell("echo $foo", opts)
|
|
end
|
|
end
|
|
|
|
@tag :unix
|
|
test "vm signals" do
|
|
assert System.trap_signal(:sigquit, :example, fn -> :ok end) == {:ok, :example}
|
|
assert System.trap_signal(:sigquit, :example, fn -> :ok end) == {:error, :already_registered}
|
|
assert {:ok, ref} = System.trap_signal(:sigquit, fn -> :ok end)
|
|
|
|
assert System.untrap_signal(:sigquit, :example) == :ok
|
|
assert System.trap_signal(:sigquit, :example, fn -> :ok end) == {:ok, :example}
|
|
assert System.trap_signal(:sigquit, ref, fn -> :ok end) == {:error, :already_registered}
|
|
|
|
assert System.untrap_signal(:sigusr1, :example) == {:error, :not_found}
|
|
assert System.untrap_signal(:sigquit, :example) == :ok
|
|
assert System.untrap_signal(:sigquit, :example) == {:error, :not_found}
|
|
assert System.untrap_signal(:sigquit, ref) == :ok
|
|
assert System.untrap_signal(:sigquit, ref) == {:error, :not_found}
|
|
end
|
|
|
|
@tag :unix
|
|
test "os signals" do
|
|
parent = self()
|
|
|
|
assert System.trap_signal(:sighup, :example, fn ->
|
|
send(parent, :sighup_called)
|
|
:ok
|
|
end) == {:ok, :example}
|
|
|
|
{"", 0} = System.cmd("kill", ["-s", "hup", System.pid()])
|
|
|
|
assert_receive :sighup_called
|
|
after
|
|
System.untrap_signal(:sighup, :example)
|
|
end
|
|
|
|
test "find_executable/1" do
|
|
assert System.find_executable("erl")
|
|
assert is_binary(System.find_executable("erl"))
|
|
assert !System.find_executable("does-not-really-exist-from-elixir")
|
|
|
|
message = ~r"cannot execute System.find_executable/1 for program with null byte"
|
|
|
|
assert_raise ArgumentError, message, fn ->
|
|
System.find_executable("null\0byte")
|
|
end
|
|
end
|
|
|
|
test "monotonic_time/0" do
|
|
assert is_integer(System.monotonic_time())
|
|
end
|
|
|
|
test "monotonic_time/1" do
|
|
assert is_integer(System.monotonic_time(:nanosecond))
|
|
assert abs(System.monotonic_time(:microsecond)) < abs(System.monotonic_time(:nanosecond))
|
|
end
|
|
|
|
test "system_time/0" do
|
|
assert is_integer(System.system_time())
|
|
end
|
|
|
|
test "system_time/1" do
|
|
assert is_integer(System.system_time(:nanosecond))
|
|
assert abs(System.system_time(:microsecond)) < abs(System.system_time(:nanosecond))
|
|
end
|
|
|
|
test "time_offset/0 and time_offset/1" do
|
|
assert is_integer(System.time_offset())
|
|
assert is_integer(System.time_offset(:second))
|
|
end
|
|
|
|
test "os_time/0" do
|
|
assert is_integer(System.os_time())
|
|
end
|
|
|
|
test "os_time/1" do
|
|
assert is_integer(System.os_time(:nanosecond))
|
|
assert abs(System.os_time(:microsecond)) < abs(System.os_time(:nanosecond))
|
|
end
|
|
|
|
test "unique_integer/0 and unique_integer/1" do
|
|
assert is_integer(System.unique_integer())
|
|
assert System.unique_integer([:positive]) > 0
|
|
|
|
assert System.unique_integer([:positive, :monotonic]) <
|
|
System.unique_integer([:positive, :monotonic])
|
|
end
|
|
|
|
test "convert_time_unit/3" do
|
|
time = System.monotonic_time(:nanosecond)
|
|
assert abs(System.convert_time_unit(time, :nanosecond, :microsecond)) < abs(time)
|
|
end
|
|
|
|
test "schedulers/0" do
|
|
assert System.schedulers() >= 1
|
|
end
|
|
|
|
test "schedulers_online/0" do
|
|
assert System.schedulers_online() >= 1
|
|
end
|
|
|
|
test "otp_release/0" do
|
|
assert is_binary(System.otp_release())
|
|
end
|
|
end
|