88 lines
3.2 KiB
Elixir
88 lines
3.2 KiB
Elixir
defmodule Aprsme.RegexCacheTest do
|
|
# async: false — the cache is a singleton GenServer owning a named ETS table
|
|
# shared across the whole application. Parallel tests would see each other.
|
|
use ExUnit.Case, async: false
|
|
|
|
alias Aprsme.RegexCache
|
|
|
|
# Each test gets a unique pattern to avoid cross-test pollution of the
|
|
# shared cache (we can't safely clear it without racing other callers).
|
|
defp unique_pattern(tag) do
|
|
suffix = System.unique_integer([:positive, :monotonic])
|
|
"#{tag}_#{suffix}"
|
|
end
|
|
|
|
describe "get_or_compile/1" do
|
|
test "compiles and returns a Regex on first call" do
|
|
pattern = unique_pattern("hello")
|
|
assert {:ok, regex} = RegexCache.get_or_compile(pattern)
|
|
assert %Regex{} = regex
|
|
assert Regex.match?(regex, pattern)
|
|
end
|
|
|
|
test "returns the same compiled regex on subsequent calls" do
|
|
pattern = unique_pattern("same")
|
|
assert {:ok, r1} = RegexCache.get_or_compile(pattern)
|
|
assert {:ok, r2} = RegexCache.get_or_compile(pattern)
|
|
assert r1 == r2
|
|
end
|
|
|
|
test "returns {:error, _} for invalid patterns" do
|
|
assert {:error, _reason} = RegexCache.get_or_compile("[")
|
|
assert {:error, _reason} = RegexCache.get_or_compile("(unbalanced")
|
|
end
|
|
|
|
test "does not cache failed compilations" do
|
|
assert {:error, _} = RegexCache.get_or_compile("[bad")
|
|
# Still an error on retry — error path didn't poison the cache with nil.
|
|
assert {:error, _} = RegexCache.get_or_compile("[bad")
|
|
end
|
|
|
|
test "distinct patterns produce distinct regexes" do
|
|
p1 = unique_pattern("a")
|
|
p2 = unique_pattern("b")
|
|
assert {:ok, r1} = RegexCache.get_or_compile(p1)
|
|
assert {:ok, r2} = RegexCache.get_or_compile(p2)
|
|
refute r1 == r2
|
|
end
|
|
|
|
test "compiled regex actually matches as expected" do
|
|
pattern = unique_pattern("match") <> "\\d+"
|
|
assert {:ok, regex} = RegexCache.get_or_compile(pattern)
|
|
|
|
base = String.trim_trailing(pattern, "\\d+")
|
|
|
|
assert Regex.match?(regex, "#{base}123")
|
|
refute Regex.match?(regex, "nope")
|
|
end
|
|
|
|
test "caches many different patterns without losing recent hits" do
|
|
# Exercise the handle_call path for cache hits + misses across many patterns.
|
|
patterns = for i <- 1..50, do: "bulk_pattern_#{System.unique_integer()}_#{i}"
|
|
|
|
for p <- patterns do
|
|
assert {:ok, %Regex{}} = RegexCache.get_or_compile(p)
|
|
end
|
|
|
|
# All patterns should still be retrievable.
|
|
for p <- patterns do
|
|
assert {:ok, %Regex{}} = RegexCache.get_or_compile(p)
|
|
end
|
|
end
|
|
|
|
test "exercises LRU eviction when cache grows past the max-size threshold" do
|
|
# Generate enough patterns to push past @max_cache_size (1000).
|
|
# Existing cache entries count too, so overshoot by ~150 to be safe.
|
|
patterns = for i <- 1..1_200, do: "lru_pattern_#{System.unique_integer()}_#{i}"
|
|
|
|
for p <- patterns do
|
|
assert {:ok, %Regex{}} = RegexCache.get_or_compile(p)
|
|
end
|
|
|
|
# The first few patterns should likely be evicted, but still compile fine
|
|
# on re-request (no caching contract violation).
|
|
[first | _] = patterns
|
|
assert {:ok, %Regex{}} = RegexCache.get_or_compile(first)
|
|
end
|
|
end
|
|
end
|