diff --git a/Makefile b/Makefile index 77e47dc..4866a1c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ # Apprentice binary CC = gcc -CFLAGS = -std=c99 -g -Wall -Wextra -Werror -LDFLAGS = -lm -lmagic +ERL_EI_INCLUDE:=$(shell erl -eval 'io:format("~s", [code:lib_dir(erl_interface, include)])' -s init stop -noshell | head -1) +ERL_EI_LIB:=$(shell erl -eval 'io:format("~s", [code:lib_dir(erl_interface, lib)])' -s init stop -noshell | head -1) +CFLAGS = -std=c99 -g -Wall -Wextra -Werror -I$(ERL_EI_INCLUDE) +LDFLAGS = -L/usr/include/linux/ -L$(ERL_EI_LIB) -lm -lmagic -lei -lpthread HEADER_FILES = src C_SOURCE_FILES = src/apprentice.c OBJECT_FILES = $(C_SOURCE_FILES:.c=.o) diff --git a/lib/gen_magic/config.ex b/lib/gen_magic/config.ex index c971e7c..0ee2ace 100644 --- a/lib/gen_magic/config.ex +++ b/lib/gen_magic/config.ex @@ -12,7 +12,7 @@ defmodule GenMagic.Config do end def get_port_options(options) do - arguments = [:use_stdio, :stderr_to_stdout, :binary, :exit_status] + arguments = [:use_stdio, :binary, :exit_status, {:packet, 2}] case get_executable_arguments(options) do [] -> arguments diff --git a/lib/gen_magic/server.ex b/lib/gen_magic/server.ex index fc76885..18e5955 100644 --- a/lib/gen_magic/server.ex +++ b/lib/gen_magic/server.ex @@ -168,7 +168,7 @@ defmodule GenMagic.Server do end @doc false - def starting(:enter, _, %{request: nil, port: nil} = data) do + def starting(:enter, x, %{request: nil, port: nil} = data) do port = Port.open(data.port_name, data.port_options) {:keep_state, %{data | port: port}, data.startup_timeout} end @@ -184,8 +184,24 @@ defmodule GenMagic.Server do end @doc false - def starting(:info, {port, {:data, "ok\n"}}, %{port: port} = data) do - {:next_state, :available, data} + def starting(:info, {port, {:data, binary}}, %{port: port} = data) do + case :erlang.binary_to_term(binary) do + :ready -> + {:next_state, :available, data} + end + end + + def starting(:info, {port, {:exit_status, code}}, %{port: port} = data) do + error = + case code do + 1 -> :no_database + 2 -> :no_argument + 3 -> :missing_database + 4 -> :term_error + 5 -> :ei_error + end + + {:stop, {:error, error}, data} end @doc false @@ -196,7 +212,8 @@ defmodule GenMagic.Server do @doc false def available({:call, from}, {:perform, path}, data) do data = %{data | cycles: data.cycles + 1, request: {path, from, :erlang.now()}} - _ = send(data.port, {self(), {:command, "file; " <> path <> "\n"}}) + command = :erlang.term_to_binary({:file, path}) + _ = send(data.port, {self(), {:command, command}}) {:next_state, :processing, data} end @@ -231,7 +248,7 @@ defmodule GenMagic.Server do @doc false def recycling(:enter, _, %{request: nil, port: port} = data) when is_port(port) do - _ = send(data.port, {self(), :close}) + _ = send(data.port, {self(), {:command, :erlang.term_to_binary({:stop, :recycle})}}) {:keep_state_and_data, data.startup_timeout} end @@ -246,19 +263,26 @@ defmodule GenMagic.Server do end @doc false - def recycling(:info, {port, :closed}, %{port: port} = data) do + def recycling(:info, {port, {:exit_status, 0}}, %{port: port} = data) do {:next_state, :starting, %{data | port: nil, cycles: 0}} end - defp handle_response("ok; " <> message) do - case message |> String.trim() |> String.split("\t") do - [mime_type, encoding, content] -> {:ok, Result.build(mime_type, encoding, content)} - _ -> {:error, :malformed_response} - end - end + @errnos %{ + 2 => :enoent, + 13 => :eaccess, + 21 => :eisdir, + 20 => :enotdir, + 12 => :enomem, + 24 => :emfile + } + @errno Map.keys(@errnos) - defp handle_response("error; " <> message) do - {:error, String.trim(message)} + defp handle_response(data) do + case :erlang.binary_to_term(data) do + {:ok, {mime_type, encoding, content}} -> {:ok, Result.build(mime_type, encoding, content)} + {:error, {errno, _}} when errno in @errno -> {:error, @errnos[errno]} + {:error, {errno, string}} -> {:error, "#{errno}: #{string}"} + end end defp handle_status_call(from, state, data) do diff --git a/src/apprentice.c b/src/apprentice.c index 8105b14..6d164f8 100644 --- a/src/apprentice.c +++ b/src/apprentice.c @@ -13,16 +13,18 @@ // Where each argument either refers to a compiled or uncompiled magic database, or the default // database. They will be loaded in the sequence that they were specified. Note that you must // specify at least one database. +// Erlang Term // -// Once the program starts, it will print info statements if run from a terminal, then it will -// print `ok`. From this point onwards, additional commands can be passed: -// -// file; +// -- main: send atom ready +// enter loop // -// Results will be printed tab-separated, e.g.: +// -- while +// get {:file, path} -> process_file -> ok | error +// {:bytes, path} -> process_bytes -> ok | error +// ok: {:ok, {type, encoding, name}} +// error: {:error, :badarg} | {:error, {errno, String.t()}} +// {:stop, _} -> exit(ERROR_OK) -> exit 0 // -// ok; application/zip binary Zip archive data, at least v1.0 to extract - #include #include #include @@ -33,8 +35,8 @@ #include #include #include +#include #include - #define USAGE "[--database-file | --database-default, ...]" #define DELIMITER "\t" @@ -42,6 +44,8 @@ #define ERROR_NO_DATABASE 1 #define ERROR_NO_ARGUMENT 2 #define ERROR_MISSING_DATABASE 3 +#define ERROR_BAD_TERM 4 +#define ERROR_EI 5 #define ANSI_INFO "\x1b[37m" // gray #define ANSI_OK "\x1b[32m" // green @@ -52,16 +56,20 @@ #define MAGIC_FLAGS_COMMON (MAGIC_CHECK|MAGIC_ERROR) magic_t magic_setup(int flags); +typedef char byte; + +int read_cmd(byte *buf); +int write_cmd(byte *buf, int len); + void setup_environment(); void setup_options(int argc, char **argv); void setup_options_file(char *optarg); void setup_options_default(); void setup_system(); +int process_command(byte *buf); void process_line(char *line); -void process_file(char *path); -void print_info(const char *format, ...); -void print_ok(const char *format, ...); -void print_error(const char *format, ...); +void process_file(char *path, ei_x_buff *result); +void error(ei_x_buff *result, const char *error); struct magic_file { struct magic_file *prev; @@ -75,20 +83,84 @@ static magic_t magic_mime_encoding; // MAGIC_MIME_ENCODING static magic_t magic_type_name; // MAGIC_NONE int main (int argc, char **argv) { + ei_init(); setup_environment(); setup_options(argc, argv); setup_system(); - printf("ok\n"); - fflush(stdout); - char line[4096]; - while (fgets(line, 4096, stdin)) { - process_line(line); + ei_x_buff ok_buf; + if (ei_x_new_with_version(&ok_buf) || ei_x_encode_atom(&ok_buf, "ready")) return 5; + write_cmd(ok_buf.buff, ok_buf.index); + if (ei_x_free(&ok_buf) != 0) + exit(ERROR_EI); + + byte buf[5000]; + while (read_cmd(buf) > 0) { + process_command(buf); } return 0; } +int process_command(byte *buf) { + ei_x_buff result; + char atom[128]; + int index, version, arity; + index = 0; + + if (ei_decode_version(buf, &index, &version) != 0) + exit(ERROR_BAD_TERM); + + // Initialize result + if (ei_x_new_with_version(&result) || ei_x_encode_tuple_header(&result, 2)) exit(ERROR_EI); + + if (ei_decode_tuple_header(buf, &index, &arity) != 0) { + error(&result, "badarg"); + return 1; + } + + if (arity != 2) { + error(&result, "badarg"); + return 1; + } + + if (ei_decode_atom(buf, &index, atom) != 0) { + error(&result, "badarg"); + return 1; + } + + if (strncmp(atom, "file", 3) == 0) { + int pathtype; + int pathsize; + char path[4097]; + ei_get_type(buf, &index, &pathtype, &pathsize); + + if (pathtype == ERL_BINARY_EXT && pathsize < 4096) { + long bin_length; + ei_decode_binary(buf, &index, path, &bin_length); + path[pathsize] = '\0'; + process_file(path, &result); + } else { + error(&result, "badarg"); + return 1; + } + } else if (strncmp(atom, "bytes", 3) == 0) { + ei_x_encode_atom(&result, "ok"); + ei_x_encode_atom(&result, "bytes_not_implemented"); + } else if (strncmp(atom, "stop", 3) == 0) { + exit(ERROR_OK); + } else { + error(&result, "badarg"); + return 1; + } + + write_cmd(result.buff, result.index); + + if (ei_x_free(&result) != 0) + exit(ERROR_EI); + return 0; +} + void setup_environment() { opterr = 0; } @@ -119,7 +191,6 @@ void setup_options(int argc, char **argv) { } case '?': default: { - print_info("%s %s\n", basename(argv[0]), USAGE); exit(ERROR_NO_ARGUMENT); break; } @@ -128,9 +199,7 @@ void setup_options(int argc, char **argv) { } void setup_options_file(char *optarg) { - print_info("Requested database %s", optarg); if (0 != access(optarg, R_OK)) { - print_error("Missing Database"); exit(ERROR_MISSING_DATABASE); } @@ -147,7 +216,6 @@ void setup_options_file(char *optarg) { } void setup_options_default() { - print_info("requested default database"); struct magic_file *next = malloc(sizeof(struct magic_file)); next->path = NULL; @@ -165,12 +233,10 @@ void setup_system() { } magic_t magic_setup(int flags) { - print_info("starting libmagic instance for flags %i", flags); magic_t magic = magic_open(flags); struct magic_file *current_database = magic_database; if (!current_database) { - print_error("no database configured"); exit(ERROR_NO_DATABASE); } @@ -178,14 +244,6 @@ magic_t magic_setup(int flags) { current_database = current_database->prev; } while (current_database) { - if (isatty(STDERR_FILENO)) { - fprintf(stderr, ANSI_IGNORE); - } - if (!current_database->path) { - print_info("loading default database"); - } else { - print_info("loading database %s", current_database->path); - } magic_load(magic, current_database->path); if (isatty(STDERR_FILENO)) { fprintf(stderr, ANSI_RESET); @@ -195,90 +253,110 @@ magic_t magic_setup(int flags) { return magic; } -void process_line(char *line) { - char path[4096]; - - if (0 == strcmp(line, "exit\n")) { - exit(ERROR_OK); - } - if (1 != sscanf(line, "file; %[^\n]s", path)) { - print_error("invalid commmand"); - return; - } - - if (0 != access(path, R_OK)) { - print_error("unable to access file"); - return; - } - - process_file(path); -} - -void process_file(char *path) { +void process_file(char *path, ei_x_buff *result) { const char *mime_type_result = magic_file(magic_mime_type, path); const char *mime_type_error = magic_error(magic_mime_type); - const char *mine_encoding_result = magic_file(magic_mime_encoding, path); - const char *mine_encoding_error = magic_error(magic_mime_encoding); + int mime_type_errno = magic_errno(magic_mime_type); + + if (mime_type_errno > 0) { + ei_x_encode_atom(result, "error"); + ei_x_encode_tuple_header(result, 2); + long errlon = (long)mime_type_errno; + ei_x_encode_long(result, errlon); + ei_x_encode_binary(result, mime_type_error, strlen(mime_type_error)); + return; + } + + const char *mime_encoding_result = magic_file(magic_mime_encoding, path); + const char *mime_encoding_error = magic_error(magic_mime_encoding); + int mime_encoding_errno = magic_errno(magic_mime_encoding); + + if (mime_encoding_error) { + ei_x_encode_atom(result, "error"); + ei_x_encode_tuple_header(result, 2); + long errlon = (long)mime_encoding_errno; + ei_x_encode_long(result, errlon); + ei_x_encode_binary(result, mime_encoding_error, strlen(mime_encoding_error)); + return; + } + const char *type_name_result = magic_file(magic_type_name, path); const char *type_name_error = magic_error(magic_type_name); - - if (mime_type_error) { - print_error(mime_type_error); - return; - } - - if (mine_encoding_error) { - print_error(mine_encoding_error); - return; - } + int type_name_errno = magic_errno(magic_type_name); if (type_name_error) { - print_error(type_name_error); + ei_x_encode_atom(result, "error"); + ei_x_encode_tuple_header(result, 2); + long errlon = (long)type_name_errno; + ei_x_encode_long(result, errlon); + ei_x_encode_binary(result, type_name_error, strlen(type_name_error)); return; } - print_ok("%s%s%s%s%s", mime_type_result, DELIMITER, mine_encoding_result, DELIMITER, type_name_result); + ei_x_encode_atom(result, "ok"); + ei_x_encode_tuple_header(result, 3); + ei_x_encode_binary(result, mime_type_result, strlen(mime_type_result)); + ei_x_encode_binary(result, mime_encoding_result, strlen(mime_encoding_result)); + ei_x_encode_binary(result, type_name_result, strlen(type_name_result)); + return; } -void print_info(const char *format, ...) { - if (!isatty(STDOUT_FILENO)) { - return; - } +// From https://erlang.org/doc/tutorial/erl_interface.html +int read_exact(byte *buf, int len) +{ + int i, got=0; - printf(ANSI_INFO "info; " ANSI_RESET); - va_list arguments; - va_start(arguments, format); - vprintf(format, arguments); - va_end(arguments); - printf("\n"); + do { + if ((i = read(0, buf+got, len-got)) <= 0){ + return(i); + } + got += i; + } while (got> 8) & 0xff; + write_exact(&li, 1); + + li = len & 0xff; + write_exact(&li, 1); + + return write_exact(buf, len); +} + +void error(ei_x_buff *result, const char *error) { + ei_x_encode_atom(result, "error"); + ei_x_encode_atom(result, error); + write_cmd(result->buff, result->index); + + if (ei_x_free(result) != 0) + exit(ERROR_EI); } diff --git a/test/gen_magic/apprentice_test.exs b/test/gen_magic/apprentice_test.exs new file mode 100644 index 0000000..a28002e --- /dev/null +++ b/test/gen_magic/apprentice_test.exs @@ -0,0 +1,101 @@ +defmodule GenMagic.ApprenticeTest do + use GenMagic.MagicCase + + test "sends ready" do + port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([])) + assert_ready(port) + end + + test "stops" do + port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([])) + assert_ready(port) + send(port, {self(), {:command, :erlang.term_to_binary({:stop, :stop})}}) + assert_receive {^port, {:exit_status, 0}} + end + + test "exits with no database" do + opts = [:use_stdio, :binary, :exit_status, {:packet, 2}, {:args, []}] + port = Port.open(GenMagic.Config.get_port_name(), opts) + assert_receive {^port, {:exit_status, 1}} + end + + test "exits with a non existent database" do + opts = [ + {:args, ["--database-file", "/no/such/database"]}, + :use_stdio, + :binary, + :exit_status, + {:packet, 2} + ] + + port = Port.open(GenMagic.Config.get_port_name(), opts) + assert_receive {^port, {:exit_status, 3}} + end + + describe "port" do + setup do + port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([])) + assert_ready(port) + %{port: port} + end + + test "exits with badly formatted erlang terms", %{port: port} do + send(port, {self(), {:command, "i forgot to term_to_binary!!"}}) + assert_receive {^port, {:exit_status, 4}} + end + + test "errors with wrong command", %{port: port} do + send(port, {self(), {:command, :erlang.term_to_binary(:wrong)}}) + assert_receive {^port, {:data, data}} + assert {:error, :badarg} = :erlang.binary_to_term(data) + refute_receive _ + + send(port, {self(), {:command, :erlang.term_to_binary({:file, 42})}}) + assert_receive {^port, {:data, data}} + assert {:error, :badarg} = :erlang.binary_to_term(data) + refute_receive _ + + send(port, {self(), {:command, :erlang.term_to_binary("more wrong")}}) + assert_receive {^port, {:data, data}} + assert {:error, :badarg} = :erlang.binary_to_term(data) + refute_receive _ + + send(port, {self(), {:command, :erlang.term_to_binary({"no", "no"})}}) + assert_receive {^port, {:data, data}} + assert {:error, :badarg} = :erlang.binary_to_term(data) + refute_receive _ + end + + test "works", %{port: port} do + send(port, {self(), {:command, :erlang.term_to_binary({:file, Path.expand("Makefile")})}}) + assert_receive {^port, {:data, data}} + assert {:ok, _} = :erlang.binary_to_term(data) + end + + test "fails with non existent file", %{port: port} do + send(port, {self(), {:command, :erlang.term_to_binary({:file, "/path/to/nowhere"})}}) + assert_receive {^port, {:data, data}} + assert {:error, _} = :erlang.binary_to_term(data) + end + + test "works with a 4096 path", %{port: port} do + file = too_big() <> "/a" + File.mkdir_p!(too_big()) + File.touch!(file) + on_exit(fn -> File.rm_rf!("/tmp/testmagicex/") end) + send(port, {self(), {:command, :erlang.term_to_binary({:file, file})}}) + assert_receive {^port, {:data, data}} + assert {:ok, _} = :erlang.binary_to_term(data) + refute_receive _ + end + end + + def assert_ready(port) do + assert_receive {^port, {:data, data}} + assert :ready == :erlang.binary_to_term(data) + end + + def too_big() do + "/tmp/testmagicex/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + end +end diff --git a/test/gen_magic/server_test.exs b/test/gen_magic/server_test.exs index e152145..53e5830 100644 --- a/test/gen_magic/server_test.exs +++ b/test/gen_magic/server_test.exs @@ -12,6 +12,7 @@ defmodule GenMagic.ServerTest do assert {:ok, _} = GenMagic.Server.perform(pid, path) assert {:ok, %{cycles: 2}} = GenMagic.Server.status(pid) assert {:ok, _} = GenMagic.Server.perform(pid, path) + Process.sleep(100) assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid) end @@ -20,10 +21,13 @@ defmodule GenMagic.ServerTest do path = absolute_path("Makefile") assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid) assert {:ok, _} = GenMagic.Server.perform(pid, path) + Process.sleep(100) assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid) assert {:ok, _} = GenMagic.Server.perform(pid, path) + Process.sleep(100) assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid) assert {:ok, _} = GenMagic.Server.perform(pid, path) + Process.sleep(100) assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid) end end diff --git a/test/support/magic_case.ex b/test/support/magic_case.ex index 4915861..53df01e 100644 --- a/test/support/magic_case.ex +++ b/test/support/magic_case.ex @@ -20,8 +20,8 @@ defmodule GenMagic.MagicCase do |> Stream.flat_map(&Enum.shuffle/1) end - def assert_no_file({:error, message}) do - assert "unable to access file" = message + def assert_no_file(message) do + assert {:error, :enoent} = message end def absolute_path(path) do