MIX_ENV=test环境下无法启动Elixir桌面应用问题排查
问题描述
背景
我有一个Phoenix伞形应用,MIX_ENV=prod环境下能正常运行,但在MIX_ENV=dev执行mix test时会报错。
报错信息
18:39:42.101 [error] ** wx object server {:local, WebInterface} terminating ** Last message in was {:"$gen_cast", {:show, &WebInterface.Endpoint.url/0}} ** When Server state == %Desktop.Window{ module: nil, taskbar: nil, frame: {:wx_ref, 35, :wxFrame, []}, notifications: %{}, webview: {:wx_ref, 49, :wxWebView, []}, home_url: &WebInterface.Endpoint.url/0, last_url: nil, title: "Market Manager", rebuild: 0, rebuild_timer: {:interval, #Reference<0.2679771817.2635857922.255366>} } ** Reason for termination == ** {:badarg, [ {:erlang, :binary_to_existing_atom, ["Elixir.WebInterface.Endpoint.HTTP", :utf8], [error_info: %{module: :erl_erts_errors}]}, {:elixir_aliases, :safe_concat, 1, [file: ~c"src/elixir_aliases.erl", line: 139]}, {WebInterface.Endpoint, :url, 0, [file: ~c"lib/web_interface/endpoint.ex", line: 4]}, {Desktop.Window, :prepare_url, 1, [file: ~c"lib/desktop/window.ex", line: 666]}, {Desktop.Window, :handle_cast, 2, [file: ~c"lib/desktop/window.ex", line: 604]}, {:wx_object, :handle_msg, 5, [file: ~c"wx_object.erl", line: 493]}, {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 241]} ]}
问题代码片段
问题出在application.ex的启动代码中:
@impl true def start(_type, _args) do children = [ Telemetry, {Phoenix.PubSub, name: PubSub}, Endpoint, Manager, %{ id: Desktop.Window, start: {Desktop.Window, :start_link, [ [ app: :web_interface, id: WebInterface, title: "Market Manager", size: WindowUtils.calculate_window_size(0.6, 0.8), menubar: MenuBar, icon: "static/images/resized_logo_5_32x32.png", url: &WebInterface.Endpoint.url/0 # 疑似问题行 ] ]}, restart: :transient, shutdown: 5_000 } ] opts = [strategy: :one_for_one, name: WebInterface.Supervisor] Supervisor.start_link(children, opts) end
已尝试的配置修改
修改了config/test.exs:
import Config config :web_interface, WebInterface.Endpoint, http: [ip: {127, 0, 0, 1}, port: 4002], secret_key_base: "some_key", server: true, check_origin: false
生产环境config/prod.exs配置:
config :web_interface, WebInterface.Endpoint, http: [ip: {127, 0, 0, 1}, port: 80], cache_static_manifest: "priv/static/cache_manifest.json", secret_key_base: "some_key", check_origin: false, server: true
疑问
- 问题产生的原因是什么?
- 如何解决这个问题?
问题解答
原因分析
报错栈里的{:erlang, :binary_to_existing_atom, ["Elixir.WebInterface.Endpoint.HTTP", :utf8]}是核心问题:WebInterface.Endpoint.url/0生成URL时,会尝试将HTTP适配器名称转为已存在的原子,但测试环境下Phoenix的HTTP适配器进程还没完全初始化,对应的原子Elixir.WebInterface.Endpoint.HTTP尚未创建,导致binary_to_existing_atom调用失败抛出badarg错误。
另外,测试环境完全不需要启动Desktop.Window这类GUI组件,强行启动不仅会引发初始化顺序问题,还会额外消耗资源。
解决方案
推荐两种可行方式:
方案一:根据环境条件启动Desktop.Window
修改application.ex的子进程列表,仅在非测试环境加载GUI组件:
@impl true def start(_type, _args) do children = [ Telemetry, {Phoenix.PubSub, name: PubSub}, Endpoint, Manager # 仅在非测试环境添加Desktop.Window | if Mix.env() != :test do [ %{ id: Desktop.Window, start: {Desktop.Window, :start_link, [ [ app: :web_interface, id: WebInterface, title: "Market Manager", size: WindowUtils.calculate_window_size(0.6, 0.8), menubar: MenuBar, icon: "static/images/resized_logo_5_32x32.png", url: &WebInterface.Endpoint.url/0 ] ]}, restart: :transient, shutdown: 5_000 } ] else [] end ] opts = [strategy: :one_for_one, name: WebInterface.Supervisor] Supervisor.start_link(children, opts) end
方案二:确保Endpoint完全初始化后再启动Desktop.Window
如果测试环境确实需要GUI组件,可调整Supervisor启动逻辑保证依赖顺序:
- 给Desktop.Window添加依赖声明,确保它在Endpoint启动完成后再启动:
%{ id: Desktop.Window, start: {Desktop.Window, :start_link, [...]}, restart: :transient, shutdown: 5_000, dependencies: [Endpoint] # 添加依赖关系 }
- 或者将Supervisor策略改为
:rest_for_one,保证前面的进程启动成功后再启动后续进程:
opts = [strategy: :rest_for_one, name: WebInterface.Supervisor]
优先推荐方案一,测试环境无需加载GUI组件,既能避免初始化问题,还能加快测试启动速度。
内容的提问来源于stack exchange,提问作者Flame_Phoenix
相关产品推荐
相关产品推荐

