defmodule SnmpKit.SnmpLib.MIB.ComprehensiveMibTest do @moduledoc """ Comprehensive test suite for all MIB fixtures to ensure 100% compatibility across working, broken, and DOCSIS MIB categories. """ use ExUnit.Case, async: true alias SnmpKit.SnmpLib.MIB.Parser @moduletag :mib @test_dirs [ {"working", "test/fixtures/mibs/working"}, {"broken", "test/fixtures/mibs/broken"}, {"docsis", "test/fixtures/mibs/docsis"} ] describe "MIB compatibility tests" do for {dir_name, dir_path} <- @test_dirs do test "#{dir_name} MIBs parse successfully" do if Code.ensure_loaded?(:yecc) and function_exported?(:yecc, :file, 1) do dir_path = Path.join(File.cwd!(), unquote(dir_path)) case File.ls(dir_path) do {:ok, files} -> mib_files = filter_mib_files(files) assert mib_files != [], "No MIB files found in #{unquote(dir_name)} directory" results = test_mib_files(dir_path, mib_files) successful = Enum.count(results, fn {status, _} -> status == :ok end) failed = Enum.filter(results, fn {status, _} -> status == :error end) # Log results for visibility IO.puts("\n#{String.upcase(unquote(dir_name))} MIBs: #{successful}/#{Enum.count(mib_files)} successful") if failed != [] do IO.puts("Failed files:") for {:error, {file, reason}} <- failed do reason_str = case reason do {line, module, message} when is_integer(line) and is_atom(module) -> "Line #{line}: #{message}" reason when is_binary(reason) -> reason _ -> inspect(reason) end IO.puts(" - #{file}: #{reason_str}") end end # All files should tokenize successfully assert successful == length(mib_files), "#{length(failed)} files failed to tokenize in #{unquote(dir_name)} directory" {:error, _reason} -> # Fixture directory doesn't exist — skip rather than fail :ok end else # yecc module not available - MIB parsing disabled, test passes refute false end end end end describe "performance benchmarks" do test "lexer performance meets minimum thresholds" do # Test with a sample from each directory type (use files that actually parse) test_cases = [ # 500 definitions/sec minimum {"working", "test/fixtures/mibs/working/IF-MIB.mib", 500}, {"working", "test/fixtures/mibs/working/HOST-RESOURCES-MIB.mib", 500}, {"docsis", "test/fixtures/mibs/docsis/DOCS-CABLE-DEVICE-MIB", 500} ] for {type, file_path, min_rate} <- test_cases do full_path = Path.join(File.cwd!(), file_path) if File.exists?(full_path) do content = File.read!(full_path) # Warm up and verify file parses successfully case Parser.parse(content) do {:ok, _} -> # Performance test {time_us, {:ok, mib}} = :timer.tc(fn -> Parser.parse(content) end) # Calculate a rate based on definitions instead of tokens definitions_count = case mib do %{definitions: defs} when is_list(defs) -> length(defs) _ -> 1 end rate = definitions_count / time_us * 1_000_000 assert rate >= min_rate, "#{type} MIB performance too slow: #{Float.round(rate)} definitions/sec < #{min_rate}" {:error, _reason} -> # Skip performance test for files that don't parse IO.puts("Skipping performance test for #{file_path} - parsing failed") end end end end end describe "memory efficiency tests" do @tag :memory test "tokenization does not leak memory" do # Test with a medium-sized file repeatedly file_path = Path.join(File.cwd!(), "test/fixtures/mibs/working/IF-MIB.mib") if File.exists?(file_path) do content = File.read!(file_path) # Get baseline memory :erlang.garbage_collect() initial_memory = :erlang.memory(:total) # Run parsing multiple times for _ <- 1..100 do {:ok, _mib} = Parser.parse(content) end # Force garbage collection and check memory :erlang.garbage_collect() final_memory = :erlang.memory(:total) memory_increase = final_memory - initial_memory memory_increase_mb = memory_increase / 1_024 / 1_024 # Should not leak significant memory (allow 10MB tolerance) assert memory_increase_mb < 10, "Memory leak detected: #{Float.round(memory_increase_mb, 2)}MB increase" end end end # Helper functions defp filter_mib_files(files) do files |> Enum.filter(fn file -> String.ends_with?(file, [".mib", ".MIB"]) or (not String.contains?(file, ".") and not String.starts_with?(file, "download")) end) |> Enum.sort() end defp test_mib_files(dir_path, mib_files) do Enum.map(mib_files, fn file -> file_path = Path.join(dir_path, file) test_single_mib_file(file_path, file) end) end defp test_single_mib_file(file_path, file_name) do with {:ok, content} <- File.read(file_path), {:ok, mib} when is_map(mib) <- Parser.parse(content) do definitions_count = get_definitions_count(mib) {:ok, {file_name, definitions_count}} else {:error, reason} when is_binary(reason) -> {:error, {file_name, "File read error: #{reason}"}} {:error, reason} -> {:error, {file_name, reason}} end end defp get_definitions_count(%{definitions: defs}) when is_list(defs), do: length(defs) defp get_definitions_count(_), do: 1 end