This commit is contained in:
Jordan Bracco 2020-06-15 22:23:14 +02:00
parent 52f6aa7281
commit b5777bf9e8
31 changed files with 373 additions and 218 deletions

20
.builds/alpine.yaml Normal file
View file

@ -0,0 +1,20 @@
image: alpine/latest
packages:
- g++
- make
- elixir
- file
- file-dev
sources:
- https://git.sr.ht/~href/gen_magic
tasks:
- setup: |
mix local.hex --force
- build: |
cd gen_magic
mix deps.get
MIX_ENV=test mix compile
- test: |
cd gen_magic
mix test

17
.builds/archlinux.yaml Normal file
View file

@ -0,0 +1,17 @@
image: archlinux
packages:
- elixir
- file
sources:
- https://git.sr.ht/~href/gen_magic
tasks:
- setup: |
mix local.hex --force
- build: |
cd gen_magic
mix deps.get
MIX_ENV=test mix compile
- test: |
cd gen_magic
mix test

View file

@ -0,0 +1,20 @@
image: debian/oldstable
packages:
- build-essential
- erlang
- erlang-dev
- elixir
- libmagic-dev
sources:
- https://git.sr.ht/~href/gen_magic
tasks:
- setup: |
mix local.hex --force
- build: |
cd gen_magic
mix deps.get
MIX_ENV=test mix compile
- test: |
cd gen_magic
mix test

View file

@ -0,0 +1,20 @@
image: debian/stable
packages:
- build-essential
- erlang
- erlang-dev
- elixir
- libmagic-dev
sources:
- https://git.sr.ht/~href/gen_magic
tasks:
- setup: |
mix local.hex --force
- build: |
cd gen_magic
mix deps.get
MIX_ENV=test mix compile
- test: |
cd gen_magic
mix test

View file

@ -0,0 +1,20 @@
image: debian/testing
packages:
- build-essential
- erlang
- erlang-dev
- elixir
- libmagic-dev
sources:
- https://git.sr.ht/~hrefhref/gen_magic
tasks:
- setup: |
mix local.hex --force
- build: |
cd gen_magic
mix deps.get
MIX_ENV=test mix compile
- test: |
cd gen_magic
mix test

View file

@ -0,0 +1,22 @@
image: fedora/latest
packages:
- make
- gcc
- kernel-devel
- erlang
- elixir
- file-devel
sources:
- https://git.sr.ht/~href/gen_magic
tasks:
- setup: |
sudo dnf -y group install 'Development Tools'
mix local.hex --force
- build: |
cd gen_magic
mix deps.get
MIX_ENV=test mix compile
- test: |
cd gen_magic
mix test

17
.builds/freebsd.yaml Normal file
View file

@ -0,0 +1,17 @@
image: freebsd/latest
packages:
- elixir
- gmake
sources:
- https://git.sr.ht/~href/gen_magic
tasks:
- setup: |
mix local.hex --force
- build: |
cd gen_magic
mix deps.get
MIX_ENV=test mix compile
- test: |
cd gen_magic
mix test

View file

@ -7,7 +7,24 @@ The format is based on [Keep a Changelog][1], and this project adheres to [Seman
[1]: https://keepachangelog.com/en/1.0.0/ [1]: https://keepachangelog.com/en/1.0.0/
[2]: https://semver.org/spec/v2.0.0.html [2]: https://semver.org/spec/v2.0.0.html
## [Unreleased] ## majic [Unreleased]
## Added
- Forked gen_magic.
- Pool: `Majic.Pool`
- Unified API: `Majic.perform/1,2,3`
## Changed
- C port now using erl_interface
- `Majic.Server.reload/2,3`
- `Majic.Server.recycle/2,3`
- Bytes support: `Majic.Server.perform(ref, {:bytes, <<>>})`
- Builds on Musl
- Better error and timeout handling
## gen_majic [1.0]
### Added ### Added

View file

@ -1,5 +1,3 @@
# Apprentice binary
ERL_EI_INCLUDE:=$(shell erl -eval 'io:format("~s", [code:lib_dir(erl_interface, include)])' -s init stop -noshell | head -1) 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) 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 -Werror CFLAGS = -std=c99 -g -Wall -Werror
@ -9,9 +7,9 @@ LDLIBS = -lpthread -lei -lm -lmagic
PRIV = priv/ PRIV = priv/
RM = rm -Rf RM = rm -Rf
all: priv/apprentice all: priv/libmagic_port
priv/apprentice: src/apprentice.c priv/libmagic_port: src/libmagic_port.c
mkdir -p priv mkdir -p priv
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@

View file

@ -1,17 +1,24 @@
# GenMagic # Majic
**GenMagic** provides supervised and customisable access to [libmagic](http://man7.org/linux/man-pages/man3/libmagic.3.html) using a supervised external process. **Majic** provides a robust integration of [libmagic](http://man7.org/linux/man-pages/man3/libmagic.3.html) for Elixir.
With this library, you can start an one-off process to run a single check, or run the process as a daemon if you expect to run many checks. With this library, you can start an one-off process to run a single check, or run the process as a daemon if you expect to run
many checks.
It is a friendly fork of [gen_magic](https://github.com/evadne/gen_magic) featuring a (arguably) more robust C-code
using erl_interface, built in pooling, unified/clean API, and an optional Plug.
This package is regulary tested on multiple platforms (Debian, macOS, Fedora, Alpine, FreeBSD) to ensure it'll work fine
in any environment.
## Installation ## Installation
The package can be installed by adding `gen_magic` to your list of dependencies in `mix.exs`: The package can be installed by adding `majic` to your list of dependencies in `mix.exs`:
```elixir ```elixir
def deps do def deps do
[ [
{:gen_magic, "~> 1.0.0"} {:majic, "~> 1.0"}
] ]
end end
``` ```
@ -22,34 +29,34 @@ Compilation of the underlying C program is automatic and handled by [elixir_make
## Usage ## Usage
Depending on the use case, you may utilise a single (one-off) GenMagic process without reusing it as a daemon, or utilise a connection pool (such as Poolboy) in your application to run multiple persistent GenMagic processes. Depending on the use case, you may utilise a single (one-off) Majic process without reusing it as a daemon, or utilise a connection pool (such as Poolboy) in your application to run multiple persistent Majic processes.
To use GenMagic directly, you can use `GenMagic.Helpers.perform_once/1`: To use Majic directly, you can use `Majic.Helpers.perform_once/1`:
```elixir ```elixir
iex(1)> GenMagic.perform(".", once: true) iex(1)> Majic.perform(".", once: true)
{:ok, {:ok,
%GenMagic.Result{ %Majic.Result{
content: "directory", content: "directory",
encoding: "binary", encoding: "binary",
mime_type: "inode/directory" mime_type: "inode/directory"
}} }}
``` ```
To use the GenMagic server as a daemon, you can start it first, keep a reference, then feed messages to it as you require: To use the Majic server as a daemon, you can start it first, keep a reference, then feed messages to it as you require:
```elixir ```elixir
{:ok, pid} = GenMagic.Server.start_link([]) {:ok, pid} = Majic.Server.start_link([])
{:ok, result} = GenMagic.perform(path, server: pid) {:ok, result} = Majic.perform(path, server: pid)
``` ```
See `GenMagic.Server.start_link/1` and `t:GenMagic.Server.option/0` for more information on startup parameters. See `Majic.Server.start_link/1` and `t:Majic.Server.option/0` for more information on startup parameters.
See `GenMagic.Result` for details on the result provided. See `Majic.Result` for details on the result provided.
## Configuration ## Configuration
When using `GenMagic.Server.start_link/1` to start a persistent server, or `GenMagic.Helpers.perform_once/2` to run an ad-hoc request, you can override specific options to suit your use case. When using `Majic.Server.start_link/1` to start a persistent server, or `Majic.Helpers.perform_once/2` to run an ad-hoc request, you can override specific options to suit your use case.
| Name | Default | Description | | Name | Default | Description |
| - | - | - | | - | - | - |
@ -58,18 +65,18 @@ When using `GenMagic.Server.start_link/1` to start a persistent server, or `GenM
| `:recycle_threshold` | 10 | Number of cycles before the C process is replaced | | `:recycle_threshold` | 10 | Number of cycles before the C process is replaced |
| `:database_patterns` | `[:default]` | Databases to load | | `:database_patterns` | `[:default]` | Databases to load |
See `t:GenMagic.Server.option/0` for details. See `t:Majic.Server.option/0` for details.
### Use Cases ### Use Cases
### Ad-Hoc Requests ### Ad-Hoc Requests
For ad-hoc requests, you can use the helper method `GenMagic.Helpers.perform_once/2`: For ad-hoc requests, you can use the helper method `Majic.Helpers.perform_once/2`:
```elixir ```elixir
iex(1)> GenMagic.perform(Path.join(File.cwd!(), "Makefile"), once: true) iex(1)> Majic.perform(Path.join(File.cwd!(), "Makefile"), once: true)
{:ok, {:ok,
%GenMagic.Result{ %Majic.Result{
content: "makefile script, ASCII text", content: "makefile script, ASCII text",
encoding: "us-ascii", encoding: "us-ascii",
mime_type: "text/x-makefile" mime_type: "text/x-makefile"
@ -83,22 +90,22 @@ The Server should be run under a supervisor which provides resiliency.
Here we run it under a supervisor: Here we run it under a supervisor:
```elixir ```elixir
iex(1)> {:ok, pid} = Supervisor.start_link([{GenMagic.Server, name: :gen_magic}], strategy: :one_for_one) iex(1)> {:ok, pid} = Supervisor.start_link([{Majic.Server, name: :gen_magic}], strategy: :one_for_one)
{:ok, #PID<0.199.0>} {:ok, #PID<0.199.0>}
``` ```
Now we can ask it to inspect a file: Now we can ask it to inspect a file:
```elixir ```elixir
iex(2)> GenMagic.perform(Path.expand("~/.bash_history"), server: :gen_magic) iex(2)> Majic.perform(Path.expand("~/.bash_history"), server: :gen_magic)
{:ok, %GenMagic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}} {:ok, %Majic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}}
``` ```
Note that in this case we have opted to use a named process. Note that in this case we have opted to use a named process.
### Pool ### Pool
For concurrency *and* resiliency, you may start the `GenMagic.Pool`. By default, it will start a `GenMagic.Server` For concurrency *and* resiliency, you may start the `Majic.Pool`. By default, it will start a `Majic.Server`
worker per online scheduler: worker per online scheduler:
You can add a pool in your application supervisor by adding it as a child: You can add a pool in your application supervisor by adding it as a child:
@ -107,18 +114,18 @@ You can add a pool in your application supervisor by adding it as a child:
children = children =
[ [
# ... # ...
{GenMagic.Pool, [name: YourApp.GenMagicPool, pool_size: 2]} {Majic.Pool, [name: YourApp.MajicPool, pool_size: 2]}
] ]
opts = [strategy: :one_for_one, name: Pleroma.Supervisor] opts = [strategy: :one_for_one, name: YourApp.Supervisor]
Supervisor.start_link(children, opts) Supervisor.start_link(children, opts)
``` ```
And then you can use it with `GenMagic.perform/2` with `pool: YourApp.GenMagicPool` option: And then you can use it with `Majic.perform/2` with `pool: YourApp.MajicPool` option:
``` ```
iex(1)> GenMagic.perform(Path.expand("~/.bash_history"), pool: YourApp.GenMagicPool) iex(1)> Majic.perform(Path.expand("~/.bash_history"), pool: YourApp.MajicPool)
{:ok, %GenMagic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}} {:ok, %Majic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}}
``` ```
### Check Uploaded Files ### Check Uploaded Files
@ -127,12 +134,12 @@ If you use Phoenix, you can inspect the file from your controller:
```elixir ```elixir
def upload(conn, %{"upload" => %{path: path}}) do, def upload(conn, %{"upload" => %{path: path}}) do,
{:ok, result} = GenMagic.perform(path, server: :gen_magic) {:ok, result} = Majic.perform(path, server: :gen_magic)
text(conn, "Received your file containing #{result.content}") text(conn, "Received your file containing #{result.content}")
end end
``` ```
Obviously, it will be more ideal if you have wrapped `GenMagic.Server` in a pool such as Poolboy, to avoid constantly starting and stopping the underlying C program. Obviously, it will be more ideal if you have wrapped `Majic.Server` in a pool such as Poolboy, to avoid constantly starting and stopping the underlying C program.
## Notes ## Notes
@ -147,8 +154,11 @@ find . -name *ex | xargs mix run test/soak.exs
## Acknowledgements ## Acknowledgements
During design and prototype development of this library, the Author has drawn inspiration from the following individuals, and therefore thanks all contributors for their generosity: During design and prototype development of this library, the Author has drawn inspiration from the following individuals, and therefore
thanks all contributors for their generosity:
- [Evadne Wu](https://github.com/evadne)
- Original work
- Mr [James Every](https://github.com/devstopfix) - Mr [James Every](https://github.com/devstopfix)
- Enhanced Elixir Wrapper (based on GenServer) - Enhanced Elixir Wrapper (based on GenServer)
- Initial Hex packaging (v.0.22) - Initial Hex packaging (v.0.22)

View file

@ -1,17 +1,11 @@
defmodule GenMagic do defmodule Majic do
@moduledoc """
Top-level namespace for GenMagic, the libmagic client for Elixir.
See `GenMagic.Server` or the README for usage.
"""
@doc """ @doc """
Perform on `path`. Perform on `path`.
An option of `server: ServerName`, `pool: PoolName` or `once: true` must be passed. An option of `server: ServerName`, `pool: PoolName` or `once: true` must be passed.
""" """
@type name :: {:pool, atom()} | {:server, GenMagic.Server.t()} | {:once, true}
@type option :: name @type option :: name
when name: {:pool, atom()} | {:server, GenMagic.Server.t()} | {:once, true}
@spec perform(GenMagic.Server.target(), [option()]) :: GenMagic.Server.result() @spec perform(GenMagic.Server.target(), [option()]) :: GenMagic.Server.result()
def perform(path, opts, timeout \\ 5000) do def perform(path, opts, timeout \\ 5000) do
@ -34,7 +28,7 @@ defmodule GenMagic do
end end
defp do_perform({mod, name}, path, timeout) do defp do_perform({mod, name}, path, timeout) do
mod.perform(name, path, tiemout) mod.perform(name, path, timeout)
end end
end end

View file

@ -1,7 +1,7 @@
defmodule GenMagic.Config do defmodule Majic.Config do
@moduledoc false @moduledoc false
@otp_app Mix.Project.config()[:app] @otp_app Mix.Project.config()[:app]
@executable_name "apprentice" @executable_name "libmagic_port"
@startup_timeout 1_000 @startup_timeout 1_000
@process_timeout 30_000 @process_timeout 30_000
@recycle_threshold :infinity @recycle_threshold :infinity

View file

@ -1,10 +1,10 @@
defmodule GenMagic.Helpers do defmodule Majic.Helpers do
@moduledoc """ @moduledoc """
Contains convenience functions for one-off use. Contains convenience functions for one-off use.
""" """
alias GenMagic.Result alias Majic.Result
alias GenMagic.Server alias Majic.Server
@spec perform_once(Path.t() | {:bytes, binary}, [Server.option()]) :: @spec perform_once(Path.t() | {:bytes, binary}, [Server.option()]) ::
{:ok, Result.t()} | {:error, term()} {:ok, Result.t()} | {:error, term()}
@ -16,9 +16,9 @@ defmodule GenMagic.Helpers do
## Example ## Example
iex(1)> {:ok, result} = GenMagic.Helpers.perform_once(".") iex(1)> {:ok, result} = Majic.Helpers.perform_once(".")
iex(2)> result iex(2)> result
%GenMagic.Result{content: "directory", encoding: "binary", mime_type: "inode/directory"} %Majic.Result{content: "directory", encoding: "binary", mime_type: "inode/directory"}
""" """
def perform_once(path, options \\ []) do def perform_once(path, options \\ []) do
with {:ok, pid} <- Server.start_link(options), with {:ok, pid} <- Server.start_link(options),

View file

@ -1,6 +1,6 @@
defmodule GenMagic.Pool do defmodule Majic.Pool do
@behaviour NimblePool @behaviour NimblePool
@moduledoc "Pool of `GenMagic.Server`" @moduledoc "Pool of `Majic.Server`"
def child_spec(opts) do def child_spec(opts) do
%{ %{
@ -25,7 +25,7 @@ defmodule GenMagic.Pool do
pool, pool,
:checkout, :checkout,
fn _, server -> fn _, server ->
{GenMagic.Server.perform(server, path, timeout), server} {Majic.Server.perform(server, path, timeout), server}
end, end,
pool_timeout pool_timeout
) )
@ -46,7 +46,7 @@ defmodule GenMagic.Pool do
@impl NimblePool @impl NimblePool
def init_worker(options) do def init_worker(options) do
{:ok, server} = GenMagic.Server.start_link(options || []) {:ok, server} = Majic.Server.start_link(options || [])
{:ok, server, options} {:ok, server, options}
end end

View file

@ -1,4 +1,4 @@
defmodule GenMagic.Result do defmodule Majic.Result do
@moduledoc """ @moduledoc """
Represents the results obtained from libmagic. Represents the results obtained from libmagic.

View file

@ -1,4 +1,4 @@
defmodule GenMagic.Server do defmodule Majic.Server do
@moduledoc """ @moduledoc """
Provides access to the underlying libmagic client, which performs file introspection. Provides access to the underlying libmagic client, which performs file introspection.
@ -6,9 +6,9 @@ defmodule GenMagic.Server do
""" """
@behaviour :gen_statem @behaviour :gen_statem
alias GenMagic.Result alias Majic.Result
alias GenMagic.Server.Data alias Majic.Server.Data
alias GenMagic.Server.Status alias Majic.Server.Status
import Kernel, except: [send: 2] import Kernel, except: [send: 2]
require Logger require Logger
@ -34,7 +34,7 @@ defmodule GenMagic.Server do
Can be set to `:infinity`. Can be set to `:infinity`.
Please note that, if you have chosen a custom timeout value, you should also pass it when Please note that, if you have chosen a custom timeout value, you should also pass it when
using `GenMagic.Server.perform/3`. using `Majic.Server.perform/3`.
- `:recycle_threshold`: Specifies the number of requests processed before the underlying C - `:recycle_threshold`: Specifies the number of requests processed before the underlying C
program is recycled. program is recycled.
@ -169,7 +169,7 @@ defmodule GenMagic.Server do
@impl :gen_statem @impl :gen_statem
def init(options) do def init(options) do
import GenMagic.Config import Majic.Config
data = %Data{ data = %Data{
port_name: get_port_name(), port_name: get_port_name(),

View file

@ -1,4 +1,4 @@
defmodule GenMagic.Server.Data do defmodule Majic.Server.Data do
@moduledoc false @moduledoc false
@type request :: {Path.t(), {pid(), term()}, requested_at :: integer()} @type request :: {Path.t(), {pid(), term()}, requested_at :: integer()}

View file

@ -1,4 +1,4 @@
defmodule GenMagic.Server.Status do defmodule Majic.Server.Status do
@moduledoc """ @moduledoc """
Represents Status of the underlying Server. Represents Status of the underlying Server.
""" """
@ -12,7 +12,7 @@ defmodule GenMagic.Server.Status do
recycling is enabled. recycling is enabled.
""" """
@type t :: %__MODULE__{ @type t :: %__MODULE__{
state: GenMagic.Server.state(), state: Majic.Server.state(),
cycles: non_neg_integer() cycles: non_neg_integer()
} }

10
mix.exs
View file

@ -1,13 +1,13 @@
defmodule GenMagic.MixProject do defmodule Majic.MixProject do
use Mix.Project use Mix.Project
if :erlang.system_info(:otp_release) < '21' do if :erlang.system_info(:otp_release) < '21' do
raise "GenMagic requires Erlang/OTP 21 or newer" raise "Majic requires Erlang/OTP 21 or newer"
end end
def project do def project do
[ [
app: :gen_magic, app: :majic,
version: "1.0.0", version: "1.0.0",
elixir: "~> 1.7", elixir: "~> 1.7",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
@ -16,9 +16,9 @@ defmodule GenMagic.MixProject do
package: package(), package: package(),
deps: deps(), deps: deps(),
dialyzer: dialyzer(), dialyzer: dialyzer(),
name: "GenMagic", name: "Majic",
description: "File introspection with libmagic", description: "File introspection with libmagic",
source_url: "https://github.com/evadne/gen_magic", source_url: "https://github.com/hrefhref/majic",
docs: docs() docs: docs()
] ]
end end

View file

@ -1,69 +0,0 @@
defmodule GenMagicTest do
use GenMagic.MagicCase
alias GenMagic.Result
doctest GenMagic
@iterations 100
test "Makefile is text file" do
{:ok, pid} = GenMagic.Server.start_link([])
path = absolute_path("Makefile")
assert {:ok, %{mime_type: "text/x-makefile"}} = GenMagic.Server.perform(pid, path)
end
@tag external: true
test "Load test local files" do
{:ok, pid} = GenMagic.Server.start_link([])
files_stream()
|> Stream.cycle()
|> Stream.take(@iterations)
|> Stream.map(&assert {:ok, %Result{}} = GenMagic.Server.perform(pid, &1))
|> Enum.all?()
|> assert
end
test "Non-existent file" do
{:ok, pid} = GenMagic.Server.start_link([])
path = missing_filename()
assert_no_file(GenMagic.Server.perform(pid, path))
end
test "Named process" do
{:ok, pid} = GenMagic.Server.start_link(name: :gen_magic)
path = absolute_path("Makefile")
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(:gen_magic)
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
assert {:ok, %Result{} = result} = GenMagic.Server.perform(:gen_magic, path)
assert {:ok, %{cycles: 1}} = GenMagic.Server.status(:gen_magic)
assert {:ok, %{cycles: 1}} = GenMagic.Server.status(pid)
assert "text/x-makefile" = result.mime_type
end
describe "custom database" do
setup do
database = absolute_path("elixir.mgc")
on_exit(fn -> File.rm(database) end)
{_, 0} = System.cmd("file", ["-C", "-m", absolute_path("test/elixir")])
[database: database]
end
test "recognises Elixir files", %{database: database} do
{:ok, pid} = GenMagic.Server.start_link(database_patterns: [database])
path = absolute_path("mix.exs")
assert {:ok, %Result{} = result} = GenMagic.Server.perform(pid, path)
assert "text/x-elixir" = result.mime_type
assert "us-ascii" = result.encoding
assert "Elixir module source text" = result.content
end
test "recognises Elixir files after a reload", %{database: database} do
{:ok, pid} = GenMagic.Server.start_link([])
path = absolute_path("mix.exs")
{:ok, %Result{mime_type: mime}} = GenMagic.Server.perform(pid, path)
refute mime == "text/x-elixir"
:ok = GenMagic.Server.reload(pid, [database])
assert {:ok, %Result{mime_type: "text/x-elixir"}} = GenMagic.Server.perform(pid, path)
end
end
end

View file

@ -1,9 +0,0 @@
defmodule GenMagic.HelpersTest do
use GenMagic.MagicCase
doctest GenMagic.Helpers
test "perform_once" do
path = absolute_path("Makefile")
assert {:ok, %{mime_type: "text/x-makefile"}} = GenMagic.Helpers.perform_once(path)
end
end

View file

@ -1,16 +0,0 @@
defmodule GenMagic.PoollTest do
use GenMagic.MagicCase
test "pool" do
{:ok, _} = GenMagic.Pool.start_link(name: TestPool, pool_size: 2)
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = GenMagic.Pool.perform(TestPool, absolute_path("Makefile"))
end
end

View file

@ -1,44 +0,0 @@
defmodule GenMagic.ServerTest do
use GenMagic.MagicCase
doctest GenMagic.Server
describe "recycle_threshold" do
test "resets" do
{:ok, pid} = GenMagic.Server.start_link(recycle_threshold: 3)
path = absolute_path("Makefile")
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
assert {:ok, _} = GenMagic.Server.perform(pid, path)
assert {:ok, %{cycles: 1}} = GenMagic.Server.status(pid)
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
test "resets before reply" do
{:ok, pid} = GenMagic.Server.start_link(recycle_threshold: 1)
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
test "recycle" do
{:ok, pid} = GenMagic.Server.start_link([])
path = absolute_path("Makefile")
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
assert {:ok, _} = GenMagic.Server.perform(pid, path)
assert {:ok, %{cycles: 1}} = GenMagic.Server.status(pid)
assert :ok = GenMagic.Server.recycle(pid)
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
end
end

View file

@ -0,0 +1,9 @@
defmodule Majic.HelpersTest do
use Majic.MagicCase
doctest Majic.Helpers
test "perform_once" do
path = absolute_path("Makefile")
assert {:ok, %{mime_type: "text/x-makefile"}} = Majic.Helpers.perform_once(path)
end
end

69
test/majic/majic_test.exs Normal file
View file

@ -0,0 +1,69 @@
defmodule MajicTest do
use Majic.MagicCase
alias Majic.Result
doctest Majic
@iterations 100
test "Makefile is text file" do
{:ok, pid} = Majic.Server.start_link([])
path = absolute_path("Makefile")
assert {:ok, %{mime_type: "text/x-makefile"}} = Majic.Server.perform(pid, path)
end
@tag external: true
test "Load test local files" do
{:ok, pid} = Majic.Server.start_link([])
files_stream()
|> Stream.cycle()
|> Stream.take(@iterations)
|> Stream.map(&assert {:ok, %Result{}} = Majic.Server.perform(pid, &1))
|> Enum.all?()
|> assert
end
test "Non-existent file" do
{:ok, pid} = Majic.Server.start_link([])
path = missing_filename()
assert_no_file(Majic.Server.perform(pid, path))
end
test "Named process" do
{:ok, pid} = Majic.Server.start_link(name: :gen_magic)
path = absolute_path("Makefile")
assert {:ok, %{cycles: 0}} = Majic.Server.status(:gen_magic)
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
assert {:ok, %Result{} = result} = Majic.Server.perform(:gen_magic, path)
assert {:ok, %{cycles: 1}} = Majic.Server.status(:gen_magic)
assert {:ok, %{cycles: 1}} = Majic.Server.status(pid)
assert "text/x-makefile" = result.mime_type
end
describe "custom database" do
setup do
database = absolute_path("elixir.mgc")
on_exit(fn -> File.rm(database) end)
{_, 0} = System.cmd("file", ["-C", "-m", absolute_path("test/elixir")])
[database: database]
end
test "recognises Elixir files", %{database: database} do
{:ok, pid} = Majic.Server.start_link(database_patterns: [database])
path = absolute_path("mix.exs")
assert {:ok, %Result{} = result} = Majic.Server.perform(pid, path)
assert "text/x-elixir" = result.mime_type
assert "us-ascii" = result.encoding
assert "Elixir module source text" = result.content
end
test "recognises Elixir files after a reload", %{database: database} do
{:ok, pid} = Majic.Server.start_link([])
path = absolute_path("mix.exs")
{:ok, %Result{mime_type: mime}} = Majic.Server.perform(pid, path)
refute mime == "text/x-elixir"
:ok = Majic.Server.reload(pid, [database])
assert {:ok, %Result{mime_type: "text/x-elixir"}} = Majic.Server.perform(pid, path)
end
end
end

16
test/majic/pool_test.exs Normal file
View file

@ -0,0 +1,16 @@
defmodule Majic.PoollTest do
use Majic.MagicCase
test "pool" do
{:ok, _} = Majic.Pool.start_link(name: TestPool, pool_size: 2)
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
assert {:ok, _} = Majic.Pool.perform(TestPool, absolute_path("Makefile"))
end
end

View file

@ -1,17 +1,17 @@
defmodule GenMagic.ApprenticeTest do defmodule Majic.ApprenticeTest do
use GenMagic.MagicCase use Majic.MagicCase
@tmp_path "/tmp/testgenmagicx" @tmp_path "/tmp/testgenmagicx"
require Logger require Logger
test "sends ready" do test "sends ready" do
port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([])) port = Port.open(Majic.Config.get_port_name(), Majic.Config.get_port_options([]))
on_exit(fn -> send(port, {self(), :close}) end) on_exit(fn -> send(port, {self(), :close}) end)
assert_ready_and_init_default(port) assert_ready_and_init_default(port)
end end
test "stops" do test "stops" do
port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([])) port = Port.open(Majic.Config.get_port_name(), Majic.Config.get_port_options([]))
on_exit(fn -> send(port, {self(), :close}) end) on_exit(fn -> send(port, {self(), :close}) end)
assert_ready_and_init_default(port) assert_ready_and_init_default(port)
send(port, {self(), {:command, :erlang.term_to_binary({:stop, :stop})}}) send(port, {self(), {:command, :erlang.term_to_binary({:stop, :stop})}})
@ -20,7 +20,7 @@ defmodule GenMagic.ApprenticeTest do
test "exits with non existent database with an error" do test "exits with non existent database with an error" do
opts = [:use_stdio, :binary, :exit_status, {:packet, 2}, {:args, []}] opts = [:use_stdio, :binary, :exit_status, {:packet, 2}, {:args, []}]
port = Port.open(GenMagic.Config.get_port_name(), opts) port = Port.open(Majic.Config.get_port_name(), opts)
on_exit(fn -> send(port, {self(), :close}) end) on_exit(fn -> send(port, {self(), :close}) end)
assert_ready(port) assert_ready(port)
@ -34,7 +34,7 @@ defmodule GenMagic.ApprenticeTest do
describe "port" do describe "port" do
setup do setup do
port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([])) port = Port.open(Majic.Config.get_port_name(), Majic.Config.get_port_options([]))
on_exit(fn -> send(port, {self(), :close}) end) on_exit(fn -> send(port, {self(), :close}) end)
assert_ready_and_init_default(port) assert_ready_and_init_default(port)
%{port: port} %{port: port}

View file

@ -0,0 +1,44 @@
defmodule Majic.ServerTest do
use Majic.MagicCase
doctest Majic.Server
describe "recycle_threshold" do
test "resets" do
{:ok, pid} = Majic.Server.start_link(recycle_threshold: 3)
path = absolute_path("Makefile")
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
assert {:ok, _} = Majic.Server.perform(pid, path)
assert {:ok, %{cycles: 1}} = Majic.Server.status(pid)
assert {:ok, _} = Majic.Server.perform(pid, path)
assert {:ok, %{cycles: 2}} = Majic.Server.status(pid)
assert {:ok, _} = Majic.Server.perform(pid, path)
Process.sleep(100)
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
end
test "resets before reply" do
{:ok, pid} = Majic.Server.start_link(recycle_threshold: 1)
path = absolute_path("Makefile")
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
assert {:ok, _} = Majic.Server.perform(pid, path)
Process.sleep(100)
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
assert {:ok, _} = Majic.Server.perform(pid, path)
Process.sleep(100)
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
assert {:ok, _} = Majic.Server.perform(pid, path)
Process.sleep(100)
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
end
end
test "recycle" do
{:ok, pid} = Majic.Server.start_link([])
path = absolute_path("Makefile")
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
assert {:ok, _} = Majic.Server.perform(pid, path)
assert {:ok, %{cycles: 1}} = Majic.Server.status(pid)
assert :ok = Majic.Server.recycle(pid)
assert {:ok, %{cycles: 0}} = Majic.Server.status(pid)
end
end

View file

@ -8,7 +8,7 @@ defmodule Soak do
def perform_infinite([]), do: false def perform_infinite([]), do: false
def perform_infinite(paths) do def perform_infinite(paths) do
{:ok, pid} = GenMagic.Server.start_link(database_patterns: ["/usr/local/share/misc/*.mgc"]) {:ok, pid} = Majic.Server.start_link(database_patterns: ["/usr/local/share/misc/*.mgc"])
perform_infinite(paths, [], pid, 0) perform_infinite(paths, [], pid, 0)
end end
@ -19,7 +19,7 @@ defmodule Soak do
defp perform_infinite([path | paths], done, pid, count) do defp perform_infinite([path | paths], done, pid, count) do
if rem(count, 1000) == 0, do: IO.puts(Integer.to_string(count)) if rem(count, 1000) == 0, do: IO.puts(Integer.to_string(count))
{:ok, %GenMagic.Result{}} = GenMagic.Server.perform(pid, path) {:ok, %Majic.Result{}} = Majic.Server.perform(pid, path)
perform_infinite(paths, [path | done], pid, count + 1) perform_infinite(paths, [path | done], pid, count + 1)
end end
end end

View file

@ -1,4 +1,4 @@
defmodule GenMagic.MagicCase do defmodule Majic.MagicCase do
@moduledoc false @moduledoc false
use ExUnit.CaseTemplate use ExUnit.CaseTemplate