emqx.eunit.ex 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. defmodule Mix.Tasks.Emqx.Eunit do
  2. use Mix.Task
  3. alias Mix.Tasks.Emqx.Ct, as: ECt
  4. # todo: invoke the equivalent of `make merge-config` as a requirement...
  5. @requirements ["compile", "loadpaths"]
  6. @impl true
  7. def run(args) do
  8. Mix.debug(true)
  9. IO.inspect(args)
  10. Enum.each([:common_test, :eunit, :mnesia], &ECt.add_to_path_and_cache/1)
  11. ECt.ensure_whole_emqx_project_is_loaded()
  12. ECt.unload_emqx_applications!()
  13. {_, 0} = System.cmd("epmd", ["-daemon"])
  14. node_name = :"test@127.0.0.1"
  15. :net_kernel.start([node_name, :longnames])
  16. # unmangle PROFILE env because some places (`:emqx_conf.resolve_schema_module`) expect
  17. # the version without the `-test` suffix.
  18. System.fetch_env!("PROFILE")
  19. |> String.replace_suffix("-test", "")
  20. |> then(& System.put_env("PROFILE", &1))
  21. :emqx_common_test_helpers.clear_screen()
  22. args
  23. |> parse_args!()
  24. |> discover_tests()
  25. |> :eunit.test(
  26. verbose: true,
  27. print_depth: 100
  28. )
  29. |> case do
  30. :ok -> :ok
  31. :error -> Mix.raise("errors found in tests")
  32. end
  33. end
  34. defp add_to_path_and_cache(lib_name) do
  35. :code.lib_dir()
  36. |> Path.join("#{lib_name}-*")
  37. |> Path.wildcard()
  38. |> hd()
  39. |> Path.join("ebin")
  40. |> to_charlist()
  41. |> :code.add_path(:cache)
  42. end
  43. defp parse_args!(args) do
  44. {opts, _rest} = OptionParser.parse!(
  45. args,
  46. strict: [
  47. cases: :string,
  48. modules: :string,
  49. ]
  50. )
  51. cases =
  52. opts
  53. |> get_name_list(:cases)
  54. |> Enum.flat_map(&resolve_test_fns!/1)
  55. modules =
  56. opts
  57. |> get_name_list(:modules)
  58. |> Enum.map(&String.to_atom/1)
  59. %{
  60. cases: cases,
  61. modules: modules,
  62. }
  63. end
  64. defp get_name_list(opts, key) do
  65. opts
  66. |> Keyword.get(key, "")
  67. |> String.split(",", trim: true)
  68. end
  69. defp resolve_test_fns!(mod_fn_str) do
  70. {mod, fun} = case String.split(mod_fn_str, ":") do
  71. [mod, fun] ->
  72. {String.to_atom(mod), String.to_atom(fun)}
  73. _ ->
  74. Mix.raise("Bad test case spec; must of `MOD:FUN` form. Got: #{mod_fn_str}`")
  75. end
  76. if not has_test_case?(mod, fun) do
  77. Mix.raise("Module #{mod} does not export test case #{fun}")
  78. end
  79. if to_string(fun) =~ ~r/_test_$/ do
  80. apply(mod, fun, [])
  81. else
  82. [Function.capture(mod, fun, 0)]
  83. end
  84. end
  85. defp has_test_case?(mod, fun) do
  86. try do
  87. mod.module_info(:functions)
  88. |> Enum.find(& &1 == {fun, 0})
  89. |> then(& !! &1)
  90. rescue
  91. UndefinedFunctionError -> false
  92. end
  93. end
  94. defp discover_tests(%{cases: [], modules: []} = _opts) do
  95. Mix.Dep.Umbrella.cached()
  96. |> Enum.map(& {:application, &1.app})
  97. end
  98. defp discover_tests(%{cases: cases, modules: modules}) do
  99. Enum.concat(
  100. [
  101. cases,
  102. Enum.map(modules, & {:module, &1})
  103. ]
  104. )
  105. end
  106. end