From c85d6d825009d4f7872e62d95d140ec40f468a8c Mon Sep 17 00:00:00 2001 From: Carlos Andres Bolanos Date: Sat, 8 Feb 2025 10:32:43 +0100 Subject: [PATCH] [#7] Migrate adapter to Nebulex v3 --- .credo.exs | 183 ++++++- .formatter.exs | 4 +- .github/workflows/ci.yml | 30 +- .gitignore | 2 + .tool-versions | 2 + README.md | 40 +- benchmarks/benchmark.exs | 15 +- coveralls.json | 6 +- lib/nebulex/adapters/cachex.ex | 472 +++++++++++------- .../cachex/custom_actions/count_all.ex | 27 + .../cachex/custom_actions/delete_all.ex | 27 + .../adapters/cachex/custom_actions/fetch.ex | 21 + .../adapters/cachex/custom_actions/get_all.ex | 32 ++ .../adapters/cachex/custom_actions/put_new.ex | 23 + .../cachex/custom_actions/put_new_all.ex | 38 ++ lib/nebulex/adapters/cachex/ets_utils.ex | 113 +++++ lib/nebulex/adapters/cachex/router.ex | 21 + mix.exs | 42 +- mix.lock | 44 +- .../adapters/cachex/cachex_error_test.exs | 35 ++ test/nebulex/adapters/cachex/info_test.exs | 60 +++ .../adapters/cachex/local_error_test.exs | 35 ++ test/nebulex/adapters/cachex/local_test.exs | 26 + test/nebulex/adapters/cachex/stats_test.exs | 116 +++++ .../adapters/cachex/transaction_test.exs | 96 ++++ test/nebulex_adapters_cachex/local_test.exs | 26 - .../multilevel_test.exs | 34 -- .../partitioned_test.exs | 41 -- .../replicated_test.exs | 17 - test/nebulex_adapters_cachex/stats_test.exs | 135 ----- test/support/test_cache.ex | 75 --- test/support/test_cache.exs | 25 + test/test_helper.exs | 24 +- 33 files changed, 1260 insertions(+), 627 deletions(-) create mode 100644 .tool-versions create mode 100644 lib/nebulex/adapters/cachex/custom_actions/count_all.ex create mode 100644 lib/nebulex/adapters/cachex/custom_actions/delete_all.ex create mode 100644 lib/nebulex/adapters/cachex/custom_actions/fetch.ex create mode 100644 lib/nebulex/adapters/cachex/custom_actions/get_all.ex create mode 100644 lib/nebulex/adapters/cachex/custom_actions/put_new.ex create mode 100644 lib/nebulex/adapters/cachex/custom_actions/put_new_all.ex create mode 100644 lib/nebulex/adapters/cachex/ets_utils.ex create mode 100644 lib/nebulex/adapters/cachex/router.ex create mode 100644 test/nebulex/adapters/cachex/cachex_error_test.exs create mode 100644 test/nebulex/adapters/cachex/info_test.exs create mode 100644 test/nebulex/adapters/cachex/local_error_test.exs create mode 100644 test/nebulex/adapters/cachex/local_test.exs create mode 100644 test/nebulex/adapters/cachex/stats_test.exs create mode 100644 test/nebulex/adapters/cachex/transaction_test.exs delete mode 100644 test/nebulex_adapters_cachex/local_test.exs delete mode 100644 test/nebulex_adapters_cachex/multilevel_test.exs delete mode 100644 test/nebulex_adapters_cachex/partitioned_test.exs delete mode 100644 test/nebulex_adapters_cachex/replicated_test.exs delete mode 100644 test/nebulex_adapters_cachex/stats_test.exs delete mode 100644 test/support/test_cache.ex create mode 100644 test/support/test_cache.exs diff --git a/.credo.exs b/.credo.exs index fdef457..db0f4c3 100644 --- a/.credo.exs +++ b/.credo.exs @@ -1,19 +1,188 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# %{ + # + # You can have as many configs as you like in the `configs:` field. configs: [ %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # name: "default", + # + # These are the files included in the analysis: files: %{ - included: ["lib/", "src/", "test/", "benchmarks/"], - excluded: [~r"/_build/", ~r"/deps/"] + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "benchmarks/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: true, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # checks: [ - # Readability Checks - {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 100}, + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, - # TODO and FIXME do not cause the build to fail - {Credo.Check.Design.TagTODO, exit_status: 0}, - {Credo.Check.Design.TagFIXME, exit_status: 0} + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, [priority: :low]}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 0]}, + {Credo.Check.Design.TagFIXME, [exit_status: 0]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 100]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 40]}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, [max_line_count: 200]}, + # {Credo.Check.Refactor.MapInto, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + # {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.MixEnv, false}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeExec, []}, + + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just replace `false` with `[]`) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, + {Credo.Check.Consistency.UnusedVariableNames, false}, + {Credo.Check.Design.DuplicatedCode, false}, + {Credo.Check.Readability.AliasAs, false}, + {Credo.Check.Readability.BlockPipe, false}, + {Credo.Check.Readability.ImplTrue, false}, + {Credo.Check.Readability.MultiAlias, false}, + {Credo.Check.Readability.SeparateAliasRequire, false}, + {Credo.Check.Readability.SinglePipe, false}, + {Credo.Check.Readability.Specs, false}, + {Credo.Check.Readability.StrictModuleLayout, false}, + {Credo.Check.Readability.WithCustomTaggedTuple, false}, + {Credo.Check.Refactor.ABCSize, false}, + {Credo.Check.Refactor.AppendSingleItem, false}, + {Credo.Check.Refactor.DoubleBooleanNegation, false}, + {Credo.Check.Refactor.ModuleDependencies, false}, + {Credo.Check.Refactor.NegatedIsNil, false}, + {Credo.Check.Refactor.PipeChainStart, false}, + {Credo.Check.Refactor.VariableRebinding, false}, + {Credo.Check.Warning.LeakyEnvironment, false}, + {Credo.Check.Warning.MapGetUnsafePass, false}, + {Credo.Check.Warning.UnsafeToAtom, false} + + # + # Custom checks can be created using `mix credo.gen.check`. + # ] } ] diff --git a/.formatter.exs b/.formatter.exs index 323fd85..5c3e351 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,6 @@ # Used by "mix format" [ - inputs: ["{mix,.formatter}.exs", "{config,lib,test,benchmarks}/**/*.{ex,exs}"] + import_deps: [:nebulex], + inputs: ["{mix,.formatter}.exs", "{config,lib,test,benchmarks}/**/*.{ex,exs}"], + line_length: 100 ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98501c1..fe1aeb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,25 +9,33 @@ on: - main jobs: - nebulex_test: + test: name: >- - Nebulex.Adapters.Cachex Test (Elixir ${{ matrix.elixir }} - / OTP ${{ matrix.otp }} / OS ${{ matrix.os }}) + Nebulex.Adapters.Cachex Test (Elixir ${{ matrix.elixir }} / + OTP ${{ matrix.otp }} / + OS ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: matrix: include: - - elixir: 1.14.x - otp: 25.x + - elixir: 1.18.x + otp: 27.x os: 'ubuntu-latest' style: true coverage: true + sobelow: true dialyzer: true - - elixir: 1.13.x - otp: 24.x + - elixir: 1.17.x + otp: 26.x + os: 'ubuntu-latest' + - elixir: 1.16.x + otp: 26.x os: 'ubuntu-latest' - - elixir: 1.12.x + - elixir: 1.15.x + otp: 25.x + os: 'ubuntu-latest' + - elixir: 1.14.x otp: 23.x os: 'ubuntu-20.04' @@ -53,6 +61,8 @@ jobs: key: >- ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + restore-keys: | + ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix- - name: Cache _build uses: actions/cache@v3 @@ -61,6 +71,8 @@ jobs: key: >- ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-build-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + restore-keys: | + ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-build- - name: Install Nebulex run: mix nbx.setup @@ -98,7 +110,7 @@ jobs: id: plt-cache with: path: priv/plts - key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt-v2 + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt-v1 if: ${{ matrix.dialyzer }} - name: Create PLTs diff --git a/.gitignore b/.gitignore index 7e88d67..ffbf9b4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ erl_crash.dump /priv .sobelow* /nebulex +Elixir* +*.coverdata diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..e4c737a --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +elixir 1.18.2-otp-27 +erlang 27.2.1 diff --git a/README.md b/README.md index 37ad7f9..d4bcabd 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Add `:nebulex_adapters_cachex` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:nebulex_adapters_cachex, "~> 2.1"} + {:nebulex_adapters_cachex, "~> 3.0"} ] end ``` @@ -43,7 +43,6 @@ environment, usually defined in your `config/config.exs`: ```elixir config :my_app, MyApp.Cache, - limit: 1_000_000, stats: true, ... ``` @@ -82,42 +81,31 @@ defp cachex_opts do [ expiration: expiration( - # default record expiration - default: :timer.seconds(60), - # how often cleanup should occur interval: :timer.seconds(30), + # default record expiration + default: :timer.seconds(60), + # whether to enable lazy checking lazy: true ), - # complex limit - limit: limit( - size: 500, - policy: Cachex.Policy.LRW, - reclaim: 0.5, - options: [] - ), - ... ] end ``` -> See [Cachex.start_link/1][cachex_start_link] for more information +> See [Cachex.start_link/2][cachex_start_link] for more information about the options. -[cachex_start_link]: https://hexdocs.pm/cachex/Cachex.html#start_link/1 +[cachex_start_link]: https://hexdocs.pm/cachex/Cachex.html#start_link/2 ## Distributed caching topologies -In the same way we use the distributed adapters and the multilevel one to -create distributed topologies, we can also do the same but instead of using -the built-in local adapter using Cachex. - -For example, let's define a multi-level cache (near cache topology), where -the L1 is a local cache using Cachex and the L2 is a partitioned cache. +Using the distributed adapters with `Cachex` as a primary storage is possible. + For example, let's define a multi-level cache (near cache topology), where + the L1 is a local cache using Cachex and the L2 is a partitioned cache. ```elixir defmodule MyApp.NearCache do @@ -146,8 +134,8 @@ And the configuration may look like: config :my_app, MyApp.NearCache, model: :inclusive, levels: [ - {MyApp.NearCache.L1, [limit: 100_000]}, - {MyApp.NearCache.L2, primary: [limit: 1_000_000]} + {MyApp.NearCache.L1, []}, + {MyApp.NearCache.L2, primary: [transactions: true]} ] ``` @@ -164,9 +152,9 @@ by `Nebulex.Adapters.Cachex`. ## Testing -Since `Nebulex.Adapters.Cachex` uses the support modules and shared tests -from `Nebulex` and by default its test folder is not included in the Hex -dependency, the following steps are required for running the tests. +Since this adapter uses support modules and shared tests from `Nebulex`, +but the test folder is not included in the Hex dependency, the following +steps are required to run the tests. First of all, make sure you set the environment variable `NEBULEX_PATH` to `nebulex`: diff --git a/benchmarks/benchmark.exs b/benchmarks/benchmark.exs index 0ea31dc..7892a8f 100644 --- a/benchmarks/benchmark.exs +++ b/benchmarks/benchmark.exs @@ -8,11 +8,8 @@ defmodule Cache do end benchmarks = %{ - "get" => fn input -> - Cache.get(input) - end, - "get_all" => fn input -> - Cache.get_all([input, "foo", "bar"]) + "fetch" => fn input -> + Cache.fetch(input) end, "put" => fn input -> Cache.put(input, input) @@ -35,8 +32,8 @@ benchmarks = %{ "has_key?" => fn input -> Cache.has_key?(input) end, - "size" => fn _input -> - Cache.size() + "count_all" => fn _input -> + Cache.count_all() end, "ttl" => fn input -> Cache.ttl(input) @@ -50,8 +47,8 @@ benchmarks = %{ "update" => fn input -> Cache.update(input, 1, &Kernel.+(&1, 1)) end, - "all" => fn _input -> - Cache.all() + "get_all" => fn input -> + Cache.get_all(in: [input, "foo", "bar"]) end } diff --git a/coveralls.json b/coveralls.json index c79b80e..9f4bf6f 100644 --- a/coveralls.json +++ b/coveralls.json @@ -2,8 +2,8 @@ "coverage_options": { "minimum_coverage": 100 }, - "skip_files": [ - "test/support/*" + "test/support/*", + "lib/nebulex/adapters/cachex/ets_utils.ex" ] -} +} \ No newline at end of file diff --git a/lib/nebulex/adapters/cachex.ex b/lib/nebulex/adapters/cachex.ex index 00daba6..44f1073 100644 --- a/lib/nebulex/adapters/cachex.ex +++ b/lib/nebulex/adapters/cachex.ex @@ -1,18 +1,15 @@ defmodule Nebulex.Adapters.Cachex do @moduledoc """ - Nebulex adapter for [Cachex][Cachex]. + Nebulex adapter for [Cachex][cachex]. - [Cachex]: http://hexdocs.pm/cachex/Cachex.html - - By means of this adapter, you can configure Cachex as the cache backend - and use it through the Nebulex API. + [cachex]: http://hexdocs.pm/cachex/Cachex.html ## Options Since Nebulex is just a wrapper on top of Cachex, the options are the same as - [Cachex.start_link/1][cachex_start_link]. + [Cachex.start_link/2][cachex_start_link]. - [cachex_start_link]: https://hexdocs.pm/cachex/Cachex.html#start_link/1 + [cachex_start_link]: https://hexdocs.pm/cachex/Cachex.html#start_link/2 ## Example @@ -28,7 +25,6 @@ defmodule Nebulex.Adapters.Cachex do environment, usually defined in your `config/config.exs`: config :my_app, MyApp.Cache, - limit: 1_000_000, stats: true, ... @@ -63,42 +59,30 @@ defmodule Nebulex.Adapters.Cachex do [ expiration: expiration( - # default record expiration - default: :timer.seconds(60), - # how often cleanup should occur interval: :timer.seconds(30), + # default record expiration + default: :timer.seconds(60), + # whether to enable lazy checking lazy: true ), - # complex limit - limit: limit( - size: 500, - policy: Cachex.Policy.LRW, - reclaim: 0.5, - options: [] - ), + # hooks + hooks: [ + hook(module: MyHook, name: :my_hook, args: { }) + ], ... ] end - > See [Cachex.start_link/1][cachex_start_link] for more information. - - ## Telemetry events - - This adapter emits the recommended Telemetry events. - See the "Telemetry events" section in `Nebulex.Cache` - for more information. + > See [Cachex.start_link/2][cachex_start_link] for more information. ## Distributed caching topologies - In the same way we use the distributed adapters and the multilevel one to - create distributed topologies, we can also do the same but instead of using - the built-in local adapter using Cachex. - + Using the distributed adapters with `Cachex` as a primary storage is possible. For example, let's define a multi-level cache (near cache topology), where the L1 is a local cache using Cachex and the L2 is a partitioned cache. @@ -126,8 +110,8 @@ defmodule Nebulex.Adapters.Cachex do config :my_app, MyApp.NearCache, model: :inclusive, levels: [ - {MyApp.NearCache.L1, [limit: 100_000]}, - {MyApp.NearCache.L2, primary: [limit: 1_000_000]} + {MyApp.NearCache.L1, []}, + {MyApp.NearCache.L2, primary: [transactions: true]} ] > **NOTE:** You could also use [NebulexRedisAdapter][nbx_redis_adapter] for @@ -138,289 +122,393 @@ defmodule Nebulex.Adapters.Cachex do See [Nebulex examples](https://github.com/cabol/nebulex_examples). You will find examples for all different topologies, even using other adapters like - Redis; for all examples you can just replace `Nebulex.Adapters.Local` by - `Nebulex.Adapters.Cachex`. + Redis; for all examples using the `Nebulex.Adapters.Local` adapter, you can + replace it by `Nebulex.Adapters.Cachex`. """ # Provide Cache Implementation @behaviour Nebulex.Adapter - @behaviour Nebulex.Adapter.Entry + @behaviour Nebulex.Adapter.KV @behaviour Nebulex.Adapter.Queryable - @behaviour Nebulex.Adapter.Persistence - @behaviour Nebulex.Adapter.Stats + @behaviour Nebulex.Adapter.Transaction - # Inherit default transaction implementation - use Nebulex.Adapter.Transaction + # Inherit default info implementation + use Nebulex.Adapters.Common.Info - import Nebulex.Adapter - import Nebulex.Helpers + import Cachex.Spec + import Nebulex.Utils - alias Cachex.{Options, Query} - alias Nebulex.Entry + alias __MODULE__.Router + alias Cachex.Query + alias Cachex.Services.Locksmith + alias Nebulex.Adapter + alias Nebulex.Adapter.Transaction.Options, as: TxnOptions + alias Nebulex.Cache.Options, as: NbxOptions - @compile {:inline, to_ttl: 1} + # Nebulex options + @nbx_start_opts NbxOptions.__compile_opts__() ++ NbxOptions.__start_opts__() ## Nebulex.Adapter @impl true - defmacro __before_compile__(_), do: :ok + defmacro __before_compile__(_env) do + quote do + @doc """ + A convenience function to return the Cachex cache name. + """ + def cache_name(opts \\ []) do + name = Keyword.get(opts, :name, __MODULE__) + + name + |> Adapter.lookup_meta() + |> Map.fetch!(:cachex_name) + end + end + end @impl true def init(opts) do - name = - normalize_module_name([ - opts[:name] || Keyword.fetch!(opts, :cache), - Cachex - ]) + # Get the cache name (required) + name = opts[:name] || Keyword.fetch!(opts, :cache) + + # Maybe use stats + stats = Keyword.get(opts, :stats, true) + + # Stats hooks + stats_hooks = + if Keyword.get(opts, :stats, true) do + [hook(module: Cachex.Stats)] + else + [] + end adapter_meta = %{ name: name, - telemetry: Keyword.fetch!(opts, :telemetry), - telemetry_prefix: Keyword.fetch!(opts, :telemetry_prefix), - stats: Options.get(opts, :stats, &is_boolean/1, false) + cachex_name: camelize_and_concat([name, Cachex]), + stats: stats } child_spec = opts - |> Keyword.put(:name, name) - |> Keyword.put(:stats, adapter_meta.stats) + |> Keyword.drop(@nbx_start_opts) + |> Keyword.put(:name, adapter_meta.cachex_name) + |> Keyword.update(:hooks, stats_hooks, &(stats_hooks ++ &1)) |> Cachex.child_spec() {:ok, child_spec, adapter_meta} end - ## Nebulex.Adapter.Entry + ## Nebulex.Adapter.KV @impl true - defspan get(adapter_meta, key, _opts) do - Cachex.get!(adapter_meta.name, key) + def fetch(%{cachex_name: name}, key, opts) do + name + |> Router.route({:fetch, [key, opts]}) + |> handle_response() end @impl true - defspan get_all(adapter_meta, keys, _opts) do - Enum.reduce(keys, %{}, fn key, acc -> - if value = Cachex.get!(adapter_meta.name, key) do - Map.put(acc, key, value) - else - acc + def put(%{cachex_name: name}, key, value, on_write, ttl, keep_ttl?, _opts) do + do_put(on_write, name, key, value, ttl, keep_ttl?) + end + + defp do_put(:put, name, key, value, ttl, true) do + Cachex.transaction(name, [key], fn worker -> + with {:ok, false} <- Cachex.update(worker, key, value), + {:ok, _} <- Cachex.put(worker, key, value, expire: to_ttl(ttl)) do + {:ok, true} end end) + |> handle_response(true) end - @impl true - defspan put(adapter_meta, key, value, ttl, on_write, _opts) do - do_put(adapter_meta.name, key, value, ttl, on_write) + defp do_put(:put, name, key, value, ttl, false) do + name + |> Cachex.put(key, value, expire: to_ttl(ttl)) + |> handle_response() end - defp do_put(name, key, value, ttl, :put) do - Cachex.put!(name, key, value, ttl: to_ttl(ttl)) + defp do_put(:replace, name, key, value, ttl, false) do + Cachex.transaction(name, [key], fn worker -> + with {:ok, true} <- Cachex.update(worker, key, value), + {:ok, _} <- Cachex.expire(worker, key, to_ttl(ttl)) do + {:ok, true} + end + end) + |> handle_response(true) end - defp do_put(name, key, value, ttl, :replace) do - Cachex.update!(name, key, value, ttl: to_ttl(ttl)) + defp do_put(:replace, name, key, value, _ttl, true) do + name + |> Cachex.update(key, value) + |> handle_response() end - defp do_put(name, key, value, ttl, :put_new) do - # FIXME: This is a workaround since Cachex does not support a direct action - # for put_new. Fix it if a better solution comes up. - if Cachex.get!(name, key) do - false - else - Cachex.put!(name, key, value, ttl: to_ttl(ttl)) - end + defp do_put(:put_new, name, key, value, ttl, _keep_ttl?) do + name + |> Router.route({:put_new, [key, value, [expire: to_ttl(ttl)]]}) + |> handle_response() end @impl true - defspan put_all(adapter_meta, entries, ttl, on_write, _opts) do - do_put_all(adapter_meta.name, entries, ttl, on_write) - end + def put_all(adapter_meta, entries, on_write, ttl, opts) - defp do_put_all(name, entries, ttl, on_write) when is_map(entries) do + def put_all(%{cachex_name: name}, entries, on_write, ttl, _opts) when is_map(entries) do do_put_all(name, :maps.to_list(entries), ttl, on_write) end - defp do_put_all(name, entries, ttl, :put) when is_list(entries) do - Cachex.put_many!(name, entries, ttl: to_ttl(ttl)) + def put_all(%{cachex_name: name}, entries, on_write, ttl, _opts) do + do_put_all(name, entries, ttl, on_write) end - defp do_put_all(name, entries, ttl, :put_new) when is_list(entries) do - {keys, _} = Enum.unzip(entries) + defp do_put_all(name, entries, ttl, :put) do + name + |> Cachex.put_many(entries, expire: to_ttl(ttl)) + |> handle_response() + end - # FIXME: This is a workaround since Cachex does not support a direct action - # for put_new. Fix it if a better solution comes up. - Cachex.transaction!(name, keys, fn worker -> - if Enum.any?(keys, &(worker |> Cachex.exists?(&1) |> elem(1))) do - false - else - Cachex.put_many!(worker, entries, ttl: to_ttl(ttl)) - end - end) + defp do_put_all(name, entries, ttl, :put_new) do + name + |> Router.route({:put_new_all, [entries, [expire: to_ttl(ttl)]]}) + |> handle_response() end @impl true - defspan delete(adapter_meta, key, _opts) do - true = Cachex.del!(adapter_meta.name, key) - :ok + def delete(%{cachex_name: name}, key, _opts) do + with {:ok, true} <- Cachex.del(name, key) do + :ok + end + |> handle_response() end @impl true - defspan take(adapter_meta, key, _opts) do - Cachex.take!(adapter_meta.name, key) + def take(%{cachex_name: name}, key, _opts) do + case {Cachex.exists?(name, key), Cachex.take(name, key)} do + {{:ok, true}, {:ok, nil}} -> + {:ok, nil} + + {{:ok, false}, {:ok, nil}} -> + wrap_error Nebulex.KeyError, key: key, cache: name, reason: :not_found + + {_ignore, result} -> + result + end + |> handle_response() end @impl true - defspan has_key?(adapter_meta, key) do - {:ok, bool} = Cachex.exists?(adapter_meta.name, key) - bool + def has_key?(%{cachex_name: name}, key, _opts) do + name + |> Cachex.exists?(key) + |> handle_response() end @impl true - defspan ttl(adapter_meta, key) do - cond do - # Key does exist and has a TTL associated with it - ttl = Cachex.ttl!(adapter_meta.name, key) -> - ttl - - # Key does exist and hasn't a TTL associated with it - Cachex.get!(adapter_meta.name, key) -> - :infinity - - # Key does not exist - true -> - nil - end + def ttl(%{cachex_name: name}, key, _opts) do + # FIXME: This is a workaround due to Cachex nil returned ambiguity + Cachex.transaction(name, [key], fn worker -> + with {:ok, nil} <- Cachex.ttl(worker, key), + {:ok, bool} <- Cachex.exists?(worker, key) do + if bool do + # Key does exist and hasn't a TTL associated with it + {:ok, :infinity} + else + # Key does not exist + wrap_error Nebulex.KeyError, key: key, cache: worker, reason: :not_found + end + end + end) + |> handle_response(true) end @impl true - defspan expire(adapter_meta, key, ttl) do - Cachex.expire!(adapter_meta.name, key, to_ttl(ttl)) + def expire(%{cachex_name: name}, key, ttl, _opts) do + name + |> Cachex.expire(key, to_ttl(ttl)) + |> handle_response() end @impl true - defspan touch(adapter_meta, key) do - Cachex.touch!(adapter_meta.name, key) + def touch(%{cachex_name: name}, key, _opts) do + name + |> Cachex.touch(key) + |> handle_response() end @impl true - defspan update_counter(adapter_meta, key, amount, ttl, default, _opts) do - do_update_counter(adapter_meta.name, key, amount, ttl, default) + def update_counter(%{cachex_name: name}, key, amount, default, ttl, _opts) do + # FIXME: This is a workaround since Cachex does not support `:ttl` option + Cachex.transaction(name, [key], fn worker -> + with {:ok, exists?} <- Cachex.exists?(worker, key) do + do_update_counter(worker, key, amount, default, ttl, exists?) + end + end) + |> handle_response(true) end - defp do_update_counter(name, key, amount, :infinity, default) do - Cachex.incr!(name, key, amount, initial: default) + defp do_update_counter(name, key, amount, default, _ttl, true) do + Cachex.incr(name, key, amount, default: default) end - defp do_update_counter(name, key, incr, ttl, default) do - # FIXME: This is a workaround since Cachex does not support `:ttl` here. - # Fix it if a better solution comes up. - Cachex.transaction!(name, [key], fn worker -> - counter = Cachex.incr!(worker, key, incr, initial: default) - if ttl = to_ttl(ttl), do: Cachex.expire!(worker, key, ttl) - counter - end) + defp do_update_counter(name, key, amount, default, ttl, false) do + with {:ok, _} = ok <- Cachex.incr(name, key, amount, default: default), + {:ok, _} <- Cachex.expire(name, key, to_ttl(ttl)) do + ok + end end ## Nebulex.Adapter.Queryable @impl true - defspan execute(adapter_meta, operation, query, _opts) do - do_execute(adapter_meta.name, operation, query) - end + def execute(adapter_meta, query_meta, opts) - defp do_execute(name, :count_all, nil) do - Cachex.size!(name) + def execute(_adapter_meta, %{op: :get_all, query: {:in, []}}, _opts) do + {:ok, []} end - defp do_execute(name, :delete_all, nil) do - Cachex.clear!(name) + def execute(_adapter_meta, %{op: op, query: {:in, []}}, _opts) + when op in [:count_all, :delete_all] do + {:ok, 0} end - defp do_execute(name, :delete_all, :expired) do - Cachex.purge!(name) + def execute(%{cachex_name: name}, %{op: :count_all, query: {:q, nil}}, _opts) do + name + |> Cachex.size() + |> handle_response() end - defp do_execute(name, :all, query) do + def execute(%{cachex_name: name}, %{op: :delete_all, query: {:q, nil}}, _opts) do name - |> do_stream(query, []) - |> Enum.to_list() + |> Cachex.clear() + |> handle_response() end - defp do_execute(_name, operation, query) do - raise Nebulex.QueryError, message: "unsupported #{operation}", query: query + def execute(%{cachex_name: name}, %{op: :delete_all, query: {:q, :expired}}, _opts) do + name + |> Cachex.purge() + |> handle_response() end - @impl true - defspan stream(adapter_meta, query, opts) do - do_stream(adapter_meta.name, query, opts) + def execute(%{cachex_name: name}, %{op: :count_all, query: {:in, keys}}, opts) + when is_list(keys) do + name + |> Router.route({:count_all, [keys, opts]}) + |> handle_response() end - defp do_stream(name, nil, opts) do - do_stream(name, Query.create(true, :key), opts) + def execute(%{cachex_name: name}, %{op: :delete_all, query: {:in, keys}}, opts) + when is_list(keys) do + name + |> Router.route({:delete_all, [keys, opts]}) + |> handle_response() end - defp do_stream(name, query, opts) do - query = maybe_return_entry(query, opts[:return]) - Cachex.stream!(name, query, batch_size: opts[:page_size] || 20) - rescue - e in Cachex.ExecutionError -> - reraise Nebulex.QueryError, [message: e.message, query: query], __STACKTRACE__ + def execute(adapter_meta, query, opts) do + with {:ok, stream} <- stream(adapter_meta, query, Keyword.put_new(opts, :max_entries, 25)) do + {:ok, Enum.to_list(stream)} + end + |> handle_response() end - defp maybe_return_entry([{pattern, conds, _ret}], :key) do - [{pattern, conds, [:"$1"]}] - end + @impl true + def stream(adapter_meta, query_meta, opts) - defp maybe_return_entry([{pattern, conds, _ret}], :value) do - [{pattern, conds, [:"$4"]}] + def stream(adapter_meta, %{query: {:q, nil}, select: select} = query, opts) do + stream(adapter_meta, %{query | query: {:q, Query.build(output: select)}}, opts) end - defp maybe_return_entry([{pattern, conds, _ret}], {:key, :value}) do - [{pattern, conds, [{{:"$1", :"$4"}}]}] + def stream(%{cachex_name: name}, %{query: {:q, query}}, opts) do + with {:error, :invalid_match} <- + Cachex.stream(name, query, buffer: Keyword.fetch!(opts, :max_entries)) do + raise Nebulex.QueryError, message: "invalid query #{inspect(query)}", query: query + end + |> handle_response() end - defp maybe_return_entry([{pattern, conds, _ret}], :entry) do - [{pattern, conds, [%Entry{key: :"$1", value: :"$4", touched: :"$2", ttl: :"$3"}]}] - end + def stream(%{cachex_name: name}, %{query: {:in, keys}, select: select}, opts) do + max_entries = Keyword.fetch!(opts, :max_entries) - defp maybe_return_entry(query, _return), do: query + keys + |> Stream.chunk_every(max_entries) + |> Stream.map(&Router.route(name, {:get_all, [&1, select, [max_entries: max_entries]]})) + |> Stream.flat_map(& &1) + |> wrap_ok() + end - ## Nebulex.Adapter.Persistence + ## Nebulex.Adapter.Transaction @impl true - defspan dump(adapter_meta, path, opts) do - case Cachex.dump(adapter_meta.name, path, opts) do - {:ok, true} -> :ok - {:error, _} = error -> error - end + def transaction(%{cachex_name: name}, fun, opts) do + opts = TxnOptions.validate!(opts) + keys = Keyword.fetch!(opts, :keys) + + name + |> Cachex.transaction(keys, fun) + |> handle_response() end @impl true - defspan load(adapter_meta, path, opts) do - case Cachex.load(adapter_meta.name, path, opts) do - {:ok, true} -> :ok - {:error, _} = error -> error - end + def in_transaction?(_adapter_meta, _opts) do + {:ok, Locksmith.transaction?()} end - ## Nebulex.Adapter.Stats + ## Nebulex.Adapter.Info @impl true - defspan stats(adapter_meta) do - if adapter_meta.stats do - {meta, stats} = - adapter_meta.name - |> Cachex.stats!() - |> Map.pop(:meta, %{}) - - %Nebulex.Stats{ - measurements: stats, - metadata: meta - } + def info(adapter_meta, spec, opts) + + def info(%{cachex_name: name} = adapter_meta, :all, opts) do + with {:ok, stats} <- Cachex.stats(name) do + {:ok, base_info} = super(adapter_meta, :all, opts) + + {:ok, Map.merge(base_info, %{stats: stats})} end + |> handle_response() + end + + def info(%{cachex_name: name}, :stats, _opts) do + name + |> Cachex.stats() + |> handle_response() + end + + def info(adapter_meta, spec, opts) when is_list(spec) do + Enum.reduce(spec, {:ok, %{}}, fn s, {:ok, acc} -> + {:ok, info} = info(adapter_meta, s, opts) + + {:ok, Map.put(acc, s, info)} + end) + |> handle_response() + end + + def info(adapter_meta, spec, opts) do + super(adapter_meta, spec, opts) end ## Private Functions defp to_ttl(:infinity), do: nil defp to_ttl(ttl), do: ttl + + defp handle_response(response, transaction? \\ false) + + defp handle_response({:error, reason}, false) when is_nebulex_exception(reason) do + {:error, reason} + end + + defp handle_response({:error, reason}, false) do + wrap_error Nebulex.Error, reason: reason + end + + defp handle_response(other, false) do + other + end + + defp handle_response({:ok, response}, true) do + handle_response(response) + end + + defp handle_response(error, true) do + handle_response(error) + end end diff --git a/lib/nebulex/adapters/cachex/custom_actions/count_all.ex b/lib/nebulex/adapters/cachex/custom_actions/count_all.ex new file mode 100644 index 0000000..28ab361 --- /dev/null +++ b/lib/nebulex/adapters/cachex/custom_actions/count_all.ex @@ -0,0 +1,27 @@ +defmodule Nebulex.Adapters.Cachex.CustomActions.CountAll do + @moduledoc """ + Nebulex `count_all`. + """ + + import Cachex.Spec + import Nebulex.Adapters.Cachex.EtsUtils + + alias Cachex.Options + + @doc false + def execute(cache(name: name), keys, opts) do + chunk_size = Options.get(opts, :max_entries, &is_positive_integer/1, 25) + + count = + ets_select_keys( + keys, + chunk_size, + 0, + &new_match_spec/1, + &:ets.select_count(name, &1), + &(&1 + &2) + ) + + {:ok, count} + end +end diff --git a/lib/nebulex/adapters/cachex/custom_actions/delete_all.ex b/lib/nebulex/adapters/cachex/custom_actions/delete_all.ex new file mode 100644 index 0000000..27a2b45 --- /dev/null +++ b/lib/nebulex/adapters/cachex/custom_actions/delete_all.ex @@ -0,0 +1,27 @@ +defmodule Nebulex.Adapters.Cachex.CustomActions.DeleteAll do + @moduledoc """ + Nebulex `delete_all`. + """ + + import Cachex.Spec + import Nebulex.Adapters.Cachex.EtsUtils + + alias Cachex.Options + + @doc false + def execute(cache(name: name), keys, opts) do + chunk_size = Options.get(opts, :max_entries, &is_positive_integer/1, 25) + + count = + ets_select_keys( + keys, + chunk_size, + 0, + &new_match_spec/1, + &:ets.select_delete(name, &1), + &(&1 + &2) + ) + + {:ok, count} + end +end diff --git a/lib/nebulex/adapters/cachex/custom_actions/fetch.ex b/lib/nebulex/adapters/cachex/custom_actions/fetch.ex new file mode 100644 index 0000000..f9d9ee9 --- /dev/null +++ b/lib/nebulex/adapters/cachex/custom_actions/fetch.ex @@ -0,0 +1,21 @@ +defmodule Nebulex.Adapters.Cachex.CustomActions.Fetch do + @moduledoc """ + Custom `fetch`. + """ + + import Cachex.Spec + import Nebulex.Utils, only: [wrap_error: 2] + + alias Cachex.Actions + + @doc false + def execute(cache() = cache, key, _opts) do + case Actions.read(cache, key) do + entry(value: value) -> + {:ok, value} + + nil -> + wrap_error Nebulex.KeyError, key: key, cache: cache, reason: :not_found + end + end +end diff --git a/lib/nebulex/adapters/cachex/custom_actions/get_all.ex b/lib/nebulex/adapters/cachex/custom_actions/get_all.ex new file mode 100644 index 0000000..160b769 --- /dev/null +++ b/lib/nebulex/adapters/cachex/custom_actions/get_all.ex @@ -0,0 +1,32 @@ +defmodule Nebulex.Adapters.Cachex.CustomActions.GetAll do + @moduledoc """ + Nebulex `get_all`. + """ + + import Cachex.Spec + import Nebulex.Adapters.Cachex.EtsUtils + + alias Cachex.Options + + @doc false + def execute(cache(name: name), keys, select, opts) do + chunk_size = Options.get(opts, :max_entries, &is_positive_integer/1, 25) + + ets_select_keys( + keys, + chunk_size, + [], + &new_match_spec(&1, match_return(select)), + &:ets.select(name, &1), + &Kernel.++/2 + ) + end + + defp match_return(select) do + case select do + :key -> :"$1" + :value -> :"$2" + {:key, :value} -> {{:"$1", :"$2"}} + end + end +end diff --git a/lib/nebulex/adapters/cachex/custom_actions/put_new.ex b/lib/nebulex/adapters/cachex/custom_actions/put_new.ex new file mode 100644 index 0000000..6a66e50 --- /dev/null +++ b/lib/nebulex/adapters/cachex/custom_actions/put_new.ex @@ -0,0 +1,23 @@ +defmodule Nebulex.Adapters.Cachex.CustomActions.PutNew do + @moduledoc """ + Custom `put_new`. + """ + + import Cachex.Spec + + alias Cachex.Options + alias Cachex.Services.Janitor + alias Cachex.Services.Locksmith + + @doc false + def execute(cache(name: name) = cache, key, value, options) do + expiration = Options.get(options, :expire, &is_integer/1) + expiration = Janitor.expiration(cache, expiration) + + record = entry_now(key: key, expiration: expiration, value: value) + + Locksmith.write(cache, [key], fn -> + {:ok, :ets.insert_new(name, record)} + end) + end +end diff --git a/lib/nebulex/adapters/cachex/custom_actions/put_new_all.ex b/lib/nebulex/adapters/cachex/custom_actions/put_new_all.ex new file mode 100644 index 0000000..7740f39 --- /dev/null +++ b/lib/nebulex/adapters/cachex/custom_actions/put_new_all.ex @@ -0,0 +1,38 @@ +defmodule Nebulex.Adapters.Cachex.CustomActions.PutNewAll do + @moduledoc """ + Custom `put_new_all`. + """ + + import Cachex.Error + import Cachex.Spec + + alias Cachex.Options + alias Cachex.Services.Janitor + alias Cachex.Services.Locksmith + + @doc false + def execute(cache(name: name) = cache, pairs, options) do + expiration = Options.get(options, :expire, &is_integer/1) + expiration = Janitor.expiration(cache, expiration) + + with {:ok, keys, entries} <- map_entries(expiration, pairs, [], []) do + Locksmith.write(cache, keys, fn -> + {:ok, :ets.insert_new(name, entries)} + end) + end + end + + defp map_entries(exp, [{key, value} | pairs], keys, entries) do + entry = entry_now(key: key, expiration: exp, value: value) + + map_entries(exp, pairs, [key | keys], [entry | entries]) + end + + defp map_entries(_exp, [], keys, entries) do + {:ok, keys, entries} + end + + defp map_entries(_exp, _inv, _keys, _entries) do + error(:invalid_pairs) + end +end diff --git a/lib/nebulex/adapters/cachex/ets_utils.ex b/lib/nebulex/adapters/cachex/ets_utils.ex new file mode 100644 index 0000000..b091df6 --- /dev/null +++ b/lib/nebulex/adapters/cachex/ets_utils.ex @@ -0,0 +1,113 @@ +defmodule Nebulex.Adapters.Cachex.EtsUtils do + @moduledoc """ + ETS utilities to support extra commands. + + > Taken from `Nebulex.Adapters.Local`. + """ + + import Cachex.Spec + + alias Cachex.Query + + ## API + + @doc false + def ets_select_keys(keys, chunk_size, acc, ms_fun, chunk_fun, after_fun) + + def ets_select_keys([k], chunk_size, acc, ms_fun, chunk_fun, after_fun) do + k = if is_tuple(k), do: tuple_to_match_spec(k), else: k + + ets_select_keys( + [], + 2, + chunk_size, + match_key(k), + acc, + ms_fun, + chunk_fun, + after_fun + ) + end + + def ets_select_keys([k1, k2 | keys], chunk_size, acc, ms_fun, chunk_fun, after_fun) do + k1 = if is_tuple(k1), do: tuple_to_match_spec(k1), else: k1 + k2 = if is_tuple(k2), do: tuple_to_match_spec(k2), else: k2 + + ets_select_keys( + keys, + 2, + chunk_size, + {:orelse, match_key(k1), match_key(k2)}, + acc, + ms_fun, + chunk_fun, + after_fun + ) + end + + def ets_select_keys([], _count, _chunk_size, chunk_acc, acc, ms_fun, chunk_fun, after_fun) do + chunk_acc + |> ms_fun.() + |> chunk_fun.() + |> after_fun.(acc) + end + + def ets_select_keys(keys, count, chunk_size, chunk_acc, acc, ms_fun, chunk_fun, after_fun) + when count >= chunk_size do + acc = + chunk_acc + |> ms_fun.() + |> chunk_fun.() + |> after_fun.(acc) + + ets_select_keys(keys, chunk_size, acc, ms_fun, chunk_fun, after_fun) + end + + def ets_select_keys([k | keys], count, chunk_size, chunk_acc, acc, ms_fun, chunk_fun, after_fun) do + k = if is_tuple(k), do: tuple_to_match_spec(k), else: k + + ets_select_keys( + keys, + count + 1, + chunk_size, + {:orelse, chunk_acc, match_key(k)}, + acc, + ms_fun, + chunk_fun, + after_fun + ) + end + + @doc false + def new_match_spec(conds, return \\ true) do + [ + { + entry(key: :"$1", value: :"$2", modified: :"$3", expiration: :"$4"), + [conds], + [return] + } + ] + end + + ## Private functions + + defp tuple_to_match_spec(data) do + data + |> :erlang.tuple_to_list() + |> tuple_to_match_spec([]) + end + + defp tuple_to_match_spec([], acc) do + {acc |> Enum.reverse() |> :erlang.list_to_tuple()} + end + + defp tuple_to_match_spec([e | tail], acc) do + e = if is_tuple(e), do: tuple_to_match_spec(e), else: e + + tuple_to_match_spec(tail, [e | acc]) + end + + defp match_key(k) do + Query.unexpired({:"=:=", :"$1", k}) + end +end diff --git a/lib/nebulex/adapters/cachex/router.ex b/lib/nebulex/adapters/cachex/router.ex new file mode 100644 index 0000000..e7122a5 --- /dev/null +++ b/lib/nebulex/adapters/cachex/router.ex @@ -0,0 +1,21 @@ +defmodule Nebulex.Adapters.Cachex.Router do + @moduledoc false + + import Nebulex.Utils + + alias Cachex.Router, as: CachexRouter + alias Cachex.Services.Overseer + + ## API + + @doc """ + Helper function to route custom actions. + """ + def route(name, {action, _args} = call) do + Overseer.with(name, fn cachex_cache -> + module = camelize_and_concat([Nebulex.Adapters.Cachex.CustomActions, action]) + + CachexRouter.route(cachex_cache, module, call) + end) + end +end diff --git a/mix.exs b/mix.exs index 5cfd178..5fd349e 100644 --- a/mix.exs +++ b/mix.exs @@ -1,17 +1,15 @@ defmodule NebulexAdaptersCachex.MixProject do use Mix.Project - @source_url "https://github.com/cabol/nebulex_adapters_cachex" - @version "2.1.1" - @nbx_tag "2.5.2" - @nbx_vsn "2.5" + @source_url "https://github.com/nebulex-project/nebulex_cachex" + @version "3.0.0-dev" + # @nbx_vsn "3.0.0" def project do [ app: :nebulex_adapters_cachex, version: @version, - elixir: "~> 1.9", - elixirc_paths: elixirc_paths(Mix.env()), + elixir: "~> 1.14", aliases: aliases(), deps: deps(), @@ -41,40 +39,35 @@ defmodule NebulexAdaptersCachex.MixProject do ] end - defp elixirc_paths(:test), do: ["lib", "test/support"] - defp elixirc_paths(_), do: ["lib"] - - def application do - [] - end - defp deps do [ nebulex_dep(), - {:cachex, "~> 3.6"}, + {:nimble_options, "~> 0.5 or ~> 1.0"}, + {:cachex, "~> 4.0"}, {:telemetry, "~> 0.4 or ~> 1.0", optional: true}, # Test & Code Analysis - {:excoveralls, "~> 0.17", only: :test}, + {:excoveralls, "~> 0.18", only: :test}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false}, - {:stream_data, "~> 0.6", only: [:dev, :test]}, + {:mimic, "~> 1.7", only: :test}, + {:stream_data, "~> 1.1", only: [:dev, :test]}, # Benchmark Test - {:benchee, "~> 1.1", only: :test}, - {:benchee_html, "~> 1.0", only: :test}, + {:benchee, "~> 1.3", only: [:dev, :test]}, + {:benchee_html, "~> 1.0", only: [:dev, :test]}, # Docs - {:ex_doc, "~> 0.30", only: [:dev, :test], runtime: false} + {:ex_doc, "~> 0.36", only: [:dev, :test], runtime: false} ] end defp nebulex_dep do if path = System.get_env("NEBULEX_PATH") do - {:nebulex, "~> #{@nbx_tag}", path: path} + {:nebulex, path: path, override: true} else - {:nebulex, "~> #{@nbx_vsn}"} + {:nebulex, github: "cabol/nebulex", branch: "v3.0.0-dev"} end end @@ -82,7 +75,7 @@ defmodule NebulexAdaptersCachex.MixProject do [ "nbx.setup": [ "cmd rm -rf nebulex", - "cmd git clone --depth 1 --branch v#{@nbx_tag} https://github.com/cabol/nebulex" + "cmd git clone --depth 1 --branch v3.0.0-dev https://github.com/cabol/nebulex" ], check: [ "compile --warnings-as-errors", @@ -98,7 +91,10 @@ defmodule NebulexAdaptersCachex.MixProject do defp package do [ name: :nebulex_adapters_cachex, - maintainers: ["Carlos Bolanos"], + maintainers: [ + "Carlos Bolanos", + "Felipe Ripoll" + ], licenses: ["MIT"], links: %{"GitHub" => @source_url} ] diff --git a/mix.lock b/mix.lock index 04b4eed..e5c667c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,29 +1,33 @@ %{ - "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, - "benchee_html": {:hex, :benchee_html, "1.0.0", "5b4d24effebd060f466fb460ec06576e7b34a00fc26b234fe4f12c4f05c95947", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:benchee_json, "~> 1.0", [hex: :benchee_json, repo: "hexpm", optional: false]}], "hexpm", "5280af9aac432ff5ca4216d03e8a93f32209510e925b60e7f27c33796f69e699"}, + "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, + "benchee_html": {:hex, :benchee_html, "1.0.1", "1e247c0886c3fdb0d3f4b184b653a8d6fb96e4ad0d0389267fe4f36968772e24", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:benchee_json, "~> 1.0", [hex: :benchee_json, repo: "hexpm", optional: false]}], "hexpm", "b00a181af7152431901e08f3fc9f7197ed43ff50421a8347b0c80bf45d5b3fef"}, "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "cachex": {:hex, :cachex, "4.0.3", "95e88c3ef4d37990948eaecccefe40b4ce4a778e0d7ade29081e6b7a89309ee2", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d5d632da7f162f8a190f1c39b712c0ebc9cf0007c4e2029d44eddc8041b52d55"}, + "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, - "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, - "excoveralls": {:hex, :excoveralls, "0.17.1", "83fa7906ef23aa7fc8ad7ee469c357a63b1b3d55dd701ff5b9ce1f72442b2874", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "95bc6fda953e84c60f14da4a198880336205464e75383ec0f570180567985ae0"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "ex_doc": {:hex, :ex_doc, "0.37.0", "970f92b39e62c460aa8a367508e938f5e4da6e2ff3eaed3f8530b25870f45471", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "b0ee7f17373948e0cf471e59c3a0ee42f3bd1171c67d91eb3626456ef9c6202c"}, + "ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"}, + "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "ham": {:hex, :ham, "0.3.0", "7cd031b4a55fba219c11553e7b13ba73bd86eab4034518445eff1e038cb9a44d", [:mix], [], "hexpm", "7d6c6b73d7a6a83233876cc1b06a4d9b5de05562b228effda4532f9a49852bf6"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, - "nebulex": {:hex, :nebulex, "2.5.2", "2d358813ccb2eeea525e3a29c270ad123d3337e97ed9159d9113cf128108bd4c", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "61a122302cf42fa61eca22515b1df21aaaa1b98cf462f6dd0998de9797aaf1c7"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, - "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "mimic": {:hex, :mimic, "1.11.0", "49b126687520b6e179acab305068ad7d72bfea8abe94908a6c0c8ca0a5b7bdc7", [:mix], [{:ham, "~> 0.2", [hex: :ham, repo: "hexpm", optional: false]}], "hexpm", "8b16b1809ca947cffbaede146cd42da8c1c326af67a84b59b01c204d54e4f1a2"}, + "nebulex": {:git, "https://github.com/cabol/nebulex.git", "69162fd6d27e9a217a519a568e324265e89c470f", [branch: "v3.0.0-dev"]}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, } diff --git a/test/nebulex/adapters/cachex/cachex_error_test.exs b/test/nebulex/adapters/cachex/cachex_error_test.exs new file mode 100644 index 0000000..7366174 --- /dev/null +++ b/test/nebulex/adapters/cachex/cachex_error_test.exs @@ -0,0 +1,35 @@ +defmodule Nebulex.Adapters.Cachex.CachexErrorTest do + use ExUnit.Case, async: true + use Mimic + + import Nebulex.CacheCase, only: [setup_with_dynamic_cache: 2] + import Nebulex.Utils, only: [wrap_error: 2] + + alias Nebulex.Adapter + alias Nebulex.Adapters.Cachex.TestCache.Local, as: Cache + + @cache_name __MODULE__.Cachex + + setup_with_dynamic_cache Cache, @cache_name + + describe "cachex error" do + test "info!", %{cache: cache, name: name} do + Cachex + |> expect(:stats, fn _ -> + wrap_error Nebulex.Error, reason: :error + end) + |> allow(self(), cache_pid(name)) + + assert_raise Nebulex.Error, ~r"command failed with reason: :error", fn -> + cache.info!() + end + end + end + + defp cache_pid(cache) do + cache + |> Adapter.lookup_meta() + |> Map.fetch!(:cachex_name) + |> Process.whereis() + end +end diff --git a/test/nebulex/adapters/cachex/info_test.exs b/test/nebulex/adapters/cachex/info_test.exs new file mode 100644 index 0000000..66999ea --- /dev/null +++ b/test/nebulex/adapters/cachex/info_test.exs @@ -0,0 +1,60 @@ +defmodule Nebulex.Adapters.Cachex.InfoTest do + use ExUnit.Case, async: true + + import Nebulex.CacheCase + + alias Nebulex.Adapter + + defmodule Cache do + @moduledoc false + use Nebulex.Cache, + otp_app: :nebulex_adapters_cachex, + adapter: Nebulex.Adapters.Cachex + end + + setup_with_cache Cache, stats: true + + describe "info" do + test "ok: returns all" do + assert Cache.info!() == %{server: server_info(), stats: cachex_stats()} + end + + test "ok: returns item's info" do + assert Cache.info!(:server) == server_info() + assert Cache.info!(:stats) == cachex_stats() + end + + test "ok: returns multiple items info" do + assert Cache.info!([:server]) == %{server: server_info()} + assert Cache.info!([:server, :stats]) == %{server: server_info(), stats: cachex_stats()} + end + + test "error: raises an exception because the requested item doesn't exist" do + for spec <- [:unknown, [:unknown, :unknown]] do + assert_raise ArgumentError, ~r"invalid information specification key :unknown", fn -> + Cache.info!(spec) + end + end + end + end + + ## Private functions + + defp server_info do + adapter_meta = Adapter.lookup_meta(Cache) + + %{ + nbx_version: Nebulex.vsn(), + cache_module: adapter_meta[:cache], + cache_adapter: adapter_meta[:adapter], + cache_name: adapter_meta[:name], + cache_pid: adapter_meta[:pid] + } + end + + defp cachex_stats do + Cache.cache_name() + |> Cachex.stats!() + |> Map.drop([:calls, :operations]) + end +end diff --git a/test/nebulex/adapters/cachex/local_error_test.exs b/test/nebulex/adapters/cachex/local_error_test.exs new file mode 100644 index 0000000..e02133a --- /dev/null +++ b/test/nebulex/adapters/cachex/local_error_test.exs @@ -0,0 +1,35 @@ +defmodule Nebulex.Adapters.Cachex.LocalErrorTest do + use ExUnit.Case, async: true + use Mimic + + # Inherit error tests + use Nebulex.Cache.KVErrorTest + use Nebulex.Cache.KVExpirationErrorTest + + import Nebulex.CacheCase, only: [setup_with_dynamic_cache: 2] + import Nebulex.Utils, only: [wrap_error: 2] + + alias Nebulex.Adapters.Cachex.TestCache.Local, as: Cache + + @cache_name __MODULE__.Cachex + + setup do + Cachex.Router + |> stub(:route, fn _, _, _ -> {:error, :error} end) + + Nebulex.Adapters.Cachex.Router + |> stub(:route, fn _, _ -> wrap_error Nebulex.Error, reason: :error end) + + :ok + end + + setup_with_dynamic_cache Cache, @cache_name + + describe "put!" do + test "raises an error", %{cache: cache} do + assert_raise Nebulex.Error, ~r"command failed with reason: :error", fn -> + cache.put!(:error, "error") + end + end + end +end diff --git a/test/nebulex/adapters/cachex/local_test.exs b/test/nebulex/adapters/cachex/local_test.exs new file mode 100644 index 0000000..916cb55 --- /dev/null +++ b/test/nebulex/adapters/cachex/local_test.exs @@ -0,0 +1,26 @@ +defmodule Nebulex.Adapters.Cachex.LocalTest do + use ExUnit.Case, async: true + + # Inherit tests + use Nebulex.CacheTestCase, except: [Nebulex.Cache.TransactionTest] + + import Nebulex.CacheCase, only: [setup_with_dynamic_cache: 3] + + alias Nebulex.Adapters.Cachex.TestCache.Local, as: Cache + + setup_with_dynamic_cache Cache, __MODULE__, hooks: [] + + describe "kv api" do + test "put_new_all! raises an error (invalid entries)", %{cache: cache} do + assert_raise Nebulex.Error, ~r"command failed with reason: :invalid_pairs", fn -> + cache.put_new_all!(:invalid) + end + end + end + + describe "queryable api" do + test "delete_all :expired", %{cache: cache} do + assert cache.delete_all!(query: :expired) == 0 + end + end +end diff --git a/test/nebulex/adapters/cachex/stats_test.exs b/test/nebulex/adapters/cachex/stats_test.exs new file mode 100644 index 0000000..73815b8 --- /dev/null +++ b/test/nebulex/adapters/cachex/stats_test.exs @@ -0,0 +1,116 @@ +defmodule Nebulex.Adapters.Cachex.StatsTest do + use ExUnit.Case, async: true + use Mimic + + defmodule Cache do + @moduledoc false + use Nebulex.Cache, + otp_app: :nebulex_adapters_cachex, + adapter: Nebulex.Adapters.Cachex + end + + import Nebulex.CacheCase + + ## Tests + + describe "stats/0" do + setup_with_cache Cache, stats: true + + test "hits and misses" do + :ok = Cache.put_all!(a: 1, b: 2) + + assert Cache.get!(:a) == 1 + assert Cache.has_key?(:a) + assert Cache.ttl!(:b) == :infinity + refute Cache.get!(:c) + refute Cache.get!(:d) + + assert Cache.get_all!(in: [:a, :b, :c, :d]) |> Map.new() == %{a: 1, b: 2} + + assert Cache.info!(:stats) == cachex_stats() + end + + test "writes and updates" do + assert Cache.put_all!(a: 1, b: 2) == :ok + assert Cache.put_all(%{a: 1, b: 2}) == :ok + refute Cache.put_new_all!(a: 1, b: 2) + assert Cache.put_new_all!(c: 3, d: 4, e: 3) + assert Cache.put!(1, 1) == :ok + refute Cache.put_new!(1, 2) + refute Cache.replace!(2, 2) + assert Cache.put_new!(2, 2) + assert Cache.replace!(2, 22) + assert Cache.incr!(:counter) == 1 + assert Cache.incr!(:counter) == 2 + refute Cache.expire!(:f, 1000) + assert Cache.expire!(:a, 1000) + refute Cache.touch!(:f) + assert Cache.touch!(:b) + + _ = t_sleep(1100) + + refute Cache.get!(:a) + + wait_until(fn -> + assert Cache.info!(:stats) == cachex_stats() + end) + end + + test "deletions" do + entries = for x <- 1..10, do: {x, x} + :ok = Cache.put_all!(entries) + + assert Cache.delete!(1) == :ok + assert Cache.take!(2) == 2 + + assert_raise Nebulex.KeyError, fn -> + Cache.take!(20) + end + + assert Cache.info!(:stats) == cachex_stats() + + assert Cache.delete_all!() == 8 + + assert Cache.info!(:stats) |> Map.update!(:calls, &Map.delete(&1, :stats)) == cachex_stats() + end + + test "expirations" do + :ok = Cache.put_all!(a: 1, b: 2) + :ok = Cache.put_all!([c: 3, d: 4], ttl: 1000) + + assert Cache.get_all!(in: [:a, :b, :c, :d]) |> Map.new() == %{a: 1, b: 2, c: 3, d: 4} + + _ = t_sleep(1100) + + # The `get_all` doesn't trigger the expiration + assert Cache.get_all!(in: [:a, :b, :c, :d]) |> Map.new() == %{a: 1, b: 2} + + # The `get` will trigger the expiration + refute Cache.get!(:c) + refute Cache.get!(:d) + + wait_until(fn -> + assert Cache.info!(:stats) == cachex_stats() + end) + end + end + + describe "disabled stats:" do + setup_with_cache Cache, stats: false + + test "c:Nebulex.Cache.stats/1 raises an exception when stats are not enabled" do + assert_raise Nebulex.Error, "command failed with reason: :stats_disabled", fn -> + Cache.info!(:stats) + end + end + end + + ## Private functions + + defp cachex_stats(ops \\ 1) do + Cache.cache_name() + |> Cachex.stats!() + |> Map.update!(:operations, &(&1 - ops)) + |> Map.update!(:calls, &Map.delete(&1, :stats)) + end +end diff --git a/test/nebulex/adapters/cachex/transaction_test.exs b/test/nebulex/adapters/cachex/transaction_test.exs new file mode 100644 index 0000000..f31ca9d --- /dev/null +++ b/test/nebulex/adapters/cachex/transaction_test.exs @@ -0,0 +1,96 @@ +defmodule Nebulex.Adapters.Cachex.TransactionTest do + use ExUnit.Case, async: true + + import Nebulex.CacheCase, only: [setup_with_cache: 1] + + defmodule Cache do + @moduledoc false + use Nebulex.Cache, + otp_app: :nebulex_adapters_cachex, + adapter: Nebulex.Adapters.Cachex + end + + setup_with_cache Cache + + describe "transaction" do + test "ok: single transaction", %{cache: cache} do + assert cache.transaction(fn -> + :ok = cache.put!(1, 11) + + 11 = cache.fetch!(1) + + :ok = cache.delete!(1) + + cache.get!(1) + end) == {:ok, nil} + end + + test "ok: nested transaction", %{cache: cache} do + assert cache.transaction( + fn -> + cache.transaction( + fn -> + :ok = cache.put!(1, 11) + + 11 = cache.fetch!(1) + + :ok = cache.delete!(1) + + cache.get!(1) + end, + keys: [2] + ) + end, + keys: [1] + ) == {:ok, {:ok, nil}} + end + + test "ok: single transaction with read and write operations", %{cache: cache} do + assert cache.put(:test, ["old value"]) == :ok + assert cache.fetch!(:test) == ["old value"] + + assert cache.transaction( + fn -> + ["old value"] = value = cache.fetch!(:test) + + :ok = cache.put!(:test, ["new value" | value]) + + cache.fetch!(:test) + end, + keys: [:test] + ) == {:ok, ["new value", "old value"]} + + assert cache.fetch!(:test) == ["new value", "old value"] + end + + test "error: internal error", %{cache: cache} do + assert cache.transaction(fn -> + :ok = cache.put!(1, 11) + + 11 = cache.fetch!(1) + + :ok = cache.delete!(1) + + :ok = cache.get(1) + end) == + {:error, + %Nebulex.Error{ + reason: "no match of right hand side value: {:ok, nil}", + module: Nebulex.Error, + metadata: [] + }} + end + end + + describe "in_transaction?" do + test "returns true if calling process is already within a transaction", %{cache: cache} do + assert cache.in_transaction?() == {:ok, false} + + cache.transaction(fn -> + :ok = cache.put(1, 11) + + assert cache.in_transaction?() == {:ok, true} + end) + end + end +end diff --git a/test/nebulex_adapters_cachex/local_test.exs b/test/nebulex_adapters_cachex/local_test.exs deleted file mode 100644 index 0d2461c..0000000 --- a/test/nebulex_adapters_cachex/local_test.exs +++ /dev/null @@ -1,26 +0,0 @@ -defmodule NebulexAdaptersCachex.LocalTest do - use ExUnit.Case, async: true - use Nebulex.Adapters.CachexTest - - import Nebulex.CacheCase - - alias NebulexAdaptersCachex.TestCache.Local, as: Cache - - setup_with_dynamic_cache(Cache, :local_with_cachex) - - describe "count_all/2 error:" do - test "unsupported query", %{cache: cache} do - assert_raise Nebulex.QueryError, ~r"unsupported count_all in query", fn -> - cache.count_all(:unexpired) - end - end - end - - describe "delete_all/2 error:" do - test "unsupported query", %{cache: cache} do - assert_raise Nebulex.QueryError, ~r"unsupported delete_all in query", fn -> - cache.delete_all(:unexpired) - end - end - end -end diff --git a/test/nebulex_adapters_cachex/multilevel_test.exs b/test/nebulex_adapters_cachex/multilevel_test.exs deleted file mode 100644 index aa56689..0000000 --- a/test/nebulex_adapters_cachex/multilevel_test.exs +++ /dev/null @@ -1,34 +0,0 @@ -defmodule NebulexAdaptersCachex.MultilevelTest do - use ExUnit.Case, async: true - use Nebulex.NodeCase - use Nebulex.MultilevelTest - use Nebulex.Cache.QueryableTest - use Nebulex.Cache.TransactionTest - - import Nebulex.CacheCase - - alias NebulexAdaptersCachex.TestCache.Multilevel - alias NebulexAdaptersCachex.TestCache.Multilevel.{L1, L2, L3} - - @gc_interval :timer.hours(1) - - @levels [ - { - L1, - name: :multilevel_inclusive_l1, gc_interval: @gc_interval - }, - { - L2, - name: :multilevel_inclusive_l2, primary: [gc_interval: @gc_interval] - }, - { - L3, - name: :multilevel_inclusive_l3, primary: [gc_interval: @gc_interval] - } - ] - - setup_with_dynamic_cache(Multilevel, :multilevel_inclusive, - model: :inclusive, - levels: @levels - ) -end diff --git a/test/nebulex_adapters_cachex/partitioned_test.exs b/test/nebulex_adapters_cachex/partitioned_test.exs deleted file mode 100644 index 645d877..0000000 --- a/test/nebulex_adapters_cachex/partitioned_test.exs +++ /dev/null @@ -1,41 +0,0 @@ -defmodule NebulexAdaptersCachex.PartitionedTest do - use Nebulex.NodeCase - use Nebulex.Adapters.CachexTest - - alias NebulexAdaptersCachex.TestCache.Partitioned - - @primary :"primary@127.0.0.1" - - setup do - cluster = - :lists.usort([ - @primary - | Application.get_env(:nebulex_adapters_cachex, :nodes, []) - ]) - - node_pid_list = - start_caches( - [node() | Node.list()], - [{Partitioned, []}] - ) - - on_exit(fn -> - :ok = Process.sleep(100) - stop_caches(node_pid_list) - end) - - {:ok, cache: Partitioned, name: Partitioned, cluster: cluster} - end - - describe "partitioned cache" do - test "get_and_update" do - assert Partitioned.get_and_update(1, &Partitioned.get_and_update_fun/1) == {nil, 1} - assert Partitioned.get_and_update(1, &Partitioned.get_and_update_fun/1) == {1, 2} - assert Partitioned.get_and_update(1, &Partitioned.get_and_update_fun/1) == {2, 4} - - assert_raise ArgumentError, fn -> - Partitioned.get_and_update(1, &Partitioned.get_and_update_bad_fun/1) - end - end - end -end diff --git a/test/nebulex_adapters_cachex/replicated_test.exs b/test/nebulex_adapters_cachex/replicated_test.exs deleted file mode 100644 index 3b7a33e..0000000 --- a/test/nebulex_adapters_cachex/replicated_test.exs +++ /dev/null @@ -1,17 +0,0 @@ -defmodule NebulexAdaptersCachex.ReplicatedTest do - use Nebulex.NodeCase - use Nebulex.Adapters.CachexTest - - alias NebulexAdaptersCachex.TestCache.Replicated - - setup do - node_pid_list = start_caches([node() | Node.list()], [{Replicated, []}]) - - on_exit(fn -> - :ok = Process.sleep(100) - stop_caches(node_pid_list) - end) - - {:ok, cache: Replicated, name: Replicated} - end -end diff --git a/test/nebulex_adapters_cachex/stats_test.exs b/test/nebulex_adapters_cachex/stats_test.exs deleted file mode 100644 index 4d4d8e4..0000000 --- a/test/nebulex_adapters_cachex/stats_test.exs +++ /dev/null @@ -1,135 +0,0 @@ -defmodule NebulexAdaptersCachex.StatsTest do - use ExUnit.Case, async: true - - import Nebulex.CacheCase - - defmodule Cache do - use Nebulex.Cache, - otp_app: :nebulex, - adapter: Nebulex.Adapters.Multilevel - - defmodule L1 do - use Nebulex.Cache, - otp_app: :nebulex, - adapter: Nebulex.Adapters.Cachex - end - - defmodule L2 do - use Nebulex.Cache, - otp_app: :nebulex, - adapter: Nebulex.Adapters.Replicated, - primary_storage_adapter: Nebulex.Adapters.Cachex - end - - defmodule L3 do - use Nebulex.Cache, - otp_app: :nebulex, - adapter: Nebulex.Adapters.Partitioned, - primary_storage_adapter: Nebulex.Adapters.Cachex - end - end - - @config [ - model: :inclusive, - levels: [ - {Cache.L1, []}, - {Cache.L2, []}, - {Cache.L3, []} - ] - ] - - describe "stats/0" do - setup_with_cache(Cache, [stats: true] ++ @config) - - test "hits and misses" do - :ok = Cache.put_all(a: 1, b: 2) - - assert Cache.get(:a) == 1 - assert Cache.has_key?(:a) - assert Cache.ttl(:b) == :infinity - refute Cache.get(:c) - refute Cache.get(:d) - - assert_stats_measurements(Cache, - l1: [hits: 3, misses: 2, writes: 2], - l2: [hits: 0, misses: 2, writes: 2], - l3: [hits: 0, misses: 2, writes: 2] - ) - end - - test "writes and updates" do - assert Cache.put_all(a: 1, b: 2) == :ok - refute Cache.put_new_all(a: 1, b: 2) - assert Cache.put_new_all(c: 3, d: 4, e: 3) - assert Cache.put(1, 1) == :ok - refute Cache.put_new(1, 2) - refute Cache.replace(2, 2) - assert Cache.put_new(2, 2) - assert Cache.replace(2, 22) - assert Cache.incr(:counter) == 1 - assert Cache.incr(:counter) == 2 - refute Cache.expire(:f, 1000) - assert Cache.expire(:a, 1000) - refute Cache.touch(:f) - assert Cache.touch(:b) - - :ok = Process.sleep(1100) - refute Cache.get(:a) - - assert_stats_measurements(Cache, - l1: [expirations: 1, misses: 5, writes: 8, updates: 4], - l2: [expirations: 1, misses: 5, writes: 8, updates: 4], - l3: [expirations: 1, misses: 5, writes: 8, updates: 4] - ) - end - - test "evictions" do - entries = for x <- 1..10, do: {x, x} - :ok = Cache.put_all(entries) - - assert Cache.delete(1) == :ok - assert Cache.take(2) == 2 - refute Cache.take(20) - - assert_stats_measurements(Cache, - l1: [evictions: 2, misses: 1, writes: 10], - l2: [evictions: 2, misses: 1, writes: 10], - l3: [evictions: 2, misses: 1, writes: 10] - ) - - assert Cache.delete_all() == 24 - - assert_stats_measurements(Cache, - l1: [evictions: 10, misses: 1, writes: 10], - l2: [evictions: 10, misses: 1, writes: 10], - l3: [evictions: 10, misses: 1, writes: 10] - ) - end - - test "expirations" do - :ok = Cache.put_all(a: 1, b: 2) - :ok = Cache.put_all([c: 3, d: 4], ttl: 1000) - - assert Cache.get_all([:a, :b, :c, :d]) == %{a: 1, b: 2, c: 3, d: 4} - - :ok = Process.sleep(1100) - assert Cache.get_all([:a, :b, :c, :d]) == %{a: 1, b: 2} - - assert_stats_measurements(Cache, - l1: [evictions: 2, expirations: 2, hits: 6, misses: 2, writes: 4], - l2: [evictions: 2, expirations: 2, hits: 0, misses: 2, writes: 4], - l3: [evictions: 2, expirations: 2, hits: 0, misses: 2, writes: 4] - ) - end - end - - ## Helpers - - defp assert_stats_measurements(cache, levels) do - measurements = cache.stats().measurements - - for {level, stats} <- levels, {stat, expected} <- stats do - assert get_in(measurements, [level, stat]) == expected - end - end -end diff --git a/test/support/test_cache.ex b/test/support/test_cache.ex deleted file mode 100644 index 5b82a03..0000000 --- a/test/support/test_cache.ex +++ /dev/null @@ -1,75 +0,0 @@ -defmodule NebulexAdaptersCachex.TestCache do - @moduledoc false - - defmodule Common do - @moduledoc false - - defmacro __using__(_opts) do - quote do - def get_and_update_fun(nil), do: {nil, 1} - def get_and_update_fun(current) when is_integer(current), do: {current, current * 2} - - def get_and_update_bad_fun(_), do: :other - end - end - end - - defmodule Local do - @moduledoc false - use Nebulex.Cache, - otp_app: :nebulex_adapters_cachex, - adapter: Nebulex.Adapters.Cachex - - use NebulexAdaptersCachex.TestCache.Common - end - - defmodule Partitioned do - @moduledoc false - use Nebulex.Cache, - otp_app: :nebulex_adapters_cachex, - adapter: Nebulex.Adapters.Partitioned, - primary_storage_adapter: Nebulex.Adapters.Cachex - - use NebulexAdaptersCachex.TestCache.Common - end - - defmodule Replicated do - @moduledoc false - use Nebulex.Cache, - otp_app: :nebulex_adapters_cachex, - adapter: Nebulex.Adapters.Replicated, - primary_storage_adapter: Nebulex.Adapters.Cachex - - use NebulexAdaptersCachex.TestCache.Common - end - - defmodule Multilevel do - @moduledoc false - use Nebulex.Cache, - otp_app: :nebulex_adapters_cachex, - adapter: Nebulex.Adapters.Multilevel - - defmodule L1 do - @moduledoc false - use Nebulex.Cache, - otp_app: :nebulex_adapters_cachex, - adapter: Nebulex.Adapters.Cachex - end - - defmodule L2 do - @moduledoc false - use Nebulex.Cache, - otp_app: :nebulex_adapters_cachex, - adapter: Nebulex.Adapters.Replicated, - primary_storage_adapter: Nebulex.Adapters.Cachex - end - - defmodule L3 do - @moduledoc false - use Nebulex.Cache, - otp_app: :nebulex_adapters_cachex, - adapter: Nebulex.Adapters.Partitioned, - primary_storage_adapter: Nebulex.Adapters.Cachex - end - end -end diff --git a/test/support/test_cache.exs b/test/support/test_cache.exs new file mode 100644 index 0000000..08be267 --- /dev/null +++ b/test/support/test_cache.exs @@ -0,0 +1,25 @@ +defmodule Nebulex.Adapters.Cachex.TestCache do + @moduledoc false + + defmodule Common do + @moduledoc false + + defmacro __using__(_opts) do + quote do + def get_and_update_fun(nil), do: {nil, 1} + def get_and_update_fun(current) when is_integer(current), do: {current, current * 2} + + def get_and_update_bad_fun(_), do: :other + end + end + end + + defmodule Local do + @moduledoc false + use Nebulex.Cache, + otp_app: :nebulex_adapters_cachex, + adapter: Nebulex.Adapters.Cachex + + use Nebulex.Adapters.Cachex.TestCache.Common + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index ac2a992..f4e1424 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,16 +1,16 @@ -# Start Telemetry -Application.start(:telemetry) - -# Set nodes -nodes = [:"node1@127.0.0.1", :"node2@127.0.0.1", :"node3@127.0.0.1"] -:ok = Application.put_env(:nebulex_adapters_cachex, :nodes, nodes) +# Mocks +[ + Nebulex.Adapters.Cachex.Router, + Cachex.Router, + Cachex +] +|> Enum.each(&Mimic.copy/1) # Nebulex dependency path nbx_dep_path = Mix.Project.deps_paths()[:nebulex] -for file <- File.ls!("#{nbx_dep_path}/test/support") -- ["test_cache.ex"] do - Code.require_file("#{nbx_dep_path}/test/support/" <> file, __DIR__) -end +Code.require_file("#{nbx_dep_path}/test/support/fake_adapter.exs", __DIR__) +Code.require_file("#{nbx_dep_path}/test/support/cache_case.exs", __DIR__) for file <- File.ls!("#{nbx_dep_path}/test/shared/cache") do Code.require_file("#{nbx_dep_path}/test/shared/cache/" <> file, __DIR__) @@ -24,4 +24,10 @@ for file <- File.ls!("test/shared"), not File.dir?("test/shared/" <> file) do Code.require_file("./shared/" <> file, __DIR__) end +Code.require_file("support/test_cache.exs", __DIR__) + +# Start Telemetry +_ = Application.start(:telemetry) + +# Start ExUnit ExUnit.start()