您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

577 行
26 KiB

14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
  1. %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
  2. %%
  3. %% This file is provided to you under the Apache License,
  4. %% Version 2.0 (the "License"); you may not use this file
  5. %% except in compliance with the License. You may obtain
  6. %% a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing,
  11. %% software distributed under the License is distributed on an
  12. %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. %% KIND, either express or implied. See the License for the
  14. %% specific language governing permissions and limitations
  15. %% under the License.
  16. -module(lager_test_backend).
  17. -include("lager.hrl").
  18. -behaviour(gen_event).
  19. -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2,
  20. code_change/3]).
  21. -record(state, {level, buffer, ignored}).
  22. -compile([{parse_transform, lager_transform}]).
  23. -ifdef(TEST).
  24. -include_lib("eunit/include/eunit.hrl").
  25. -endif.
  26. init(Level) ->
  27. {ok, #state{level=lager_util:level_to_num(Level), buffer=[], ignored=[]}}.
  28. handle_call(count, #state{buffer=Buffer} = State) ->
  29. {ok, length(Buffer), State};
  30. handle_call(count_ignored, #state{ignored=Ignored} = State) ->
  31. {ok, length(Ignored), State};
  32. handle_call(flush, State) ->
  33. {ok, ok, State#state{buffer=[], ignored=[]}};
  34. handle_call(pop, #state{buffer=Buffer} = State) ->
  35. case Buffer of
  36. [] ->
  37. {ok, undefined, State};
  38. [H|T] ->
  39. {ok, H, State#state{buffer=T}}
  40. end;
  41. handle_call(get_loglevel, #state{level=Level} = State) ->
  42. {ok, Level, State};
  43. handle_call({set_loglevel, Level}, State) ->
  44. {ok, ok, State#state{level=lager_util:level_to_num(Level)}};
  45. handle_call(_Request, State) ->
  46. {ok, ok, State}.
  47. handle_event({log, Level, Time, Message}, #state{level=LogLevel,
  48. buffer=Buffer} = State) when Level =< LogLevel ->
  49. {ok, State#state{buffer=Buffer ++ [{Level, Time, Message}]}};
  50. handle_event({log, _Level, _Time, _Message}, #state{ignored=Ignored} = State) ->
  51. {ok, State#state{ignored=Ignored ++ [ignored]}};
  52. handle_event(_Event, State) ->
  53. {ok, State}.
  54. handle_info(_Info, State) ->
  55. {ok, State}.
  56. terminate(_Reason, _State) ->
  57. ok.
  58. code_change(_OldVsn, State, _Extra) ->
  59. {ok, State}.
  60. -ifdef(TEST).
  61. pop() ->
  62. gen_event:call(lager_event, ?MODULE, pop).
  63. count() ->
  64. gen_event:call(lager_event, ?MODULE, count).
  65. count_ignored() ->
  66. gen_event:call(lager_event, ?MODULE, count_ignored).
  67. lager_test_() ->
  68. {foreach,
  69. fun setup/0,
  70. fun cleanup/1,
  71. [
  72. {"observe that there is nothing up my sleeve",
  73. fun() ->
  74. ?assertEqual(undefined, pop()),
  75. ?assertEqual(0, count())
  76. end
  77. },
  78. {"logging works",
  79. fun() ->
  80. lager:warning("test message"),
  81. ?assertEqual(1, count()),
  82. {Level, _Time, Message} = pop(),
  83. ?assertMatch(Level, lager_util:level_to_num(warning)),
  84. [LevelStr, _LocStr, MsgStr] = re:split(Message, " ", [{return, list}, {parts, 3}]),
  85. ?assertEqual("[warning]", LevelStr),
  86. ?assertEqual("test message", MsgStr),
  87. ok
  88. end
  89. },
  90. {"logging with arguments works",
  91. fun() ->
  92. lager:warning("test message ~p", [self()]),
  93. ?assertEqual(1, count()),
  94. {Level, _Time, Message} = pop(),
  95. ?assertMatch(Level, lager_util:level_to_num(warning)),
  96. [LevelStr, _LocStr, MsgStr] = re:split(Message, " ", [{return, list}, {parts, 3}]),
  97. ?assertEqual("[warning]", LevelStr),
  98. ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), MsgStr),
  99. ok
  100. end
  101. },
  102. {"logging works from inside a begin/end block",
  103. fun() ->
  104. ?assertEqual(0, count()),
  105. begin
  106. lager:warning("test message 2")
  107. end,
  108. ?assertEqual(1, count()),
  109. ok
  110. end
  111. },
  112. {"logging works from inside a list comprehension",
  113. fun() ->
  114. ?assertEqual(0, count()),
  115. [lager:warning("test message") || _N <- lists:seq(1, 10)],
  116. ?assertEqual(10, count()),
  117. ok
  118. end
  119. },
  120. {"logging works from a begin/end block inside a list comprehension",
  121. fun() ->
  122. ?assertEqual(0, count()),
  123. [ begin lager:warning("test message") end || _N <- lists:seq(1, 10)],
  124. ?assertEqual(10, count()),
  125. ok
  126. end
  127. },
  128. {"logging works from a nested list comprehension",
  129. fun() ->
  130. ?assertEqual(0, count()),
  131. [ [lager:warning("test message") || _N <- lists:seq(1, 10)] ||
  132. _I <- lists:seq(1, 10)],
  133. ?assertEqual(100, count()),
  134. ok
  135. end
  136. },
  137. {"log messages below the threshold are ignored",
  138. fun() ->
  139. ?assertEqual(0, count()),
  140. lager:debug("this message will be ignored"),
  141. ?assertEqual(0, count()),
  142. ?assertEqual(0, count_ignored()),
  143. lager_mochiglobal:put(loglevel, ?DEBUG),
  144. lager:debug("this message should be ignored"),
  145. ?assertEqual(0, count()),
  146. ?assertEqual(1, count_ignored()),
  147. lager:set_loglevel(?MODULE, debug),
  148. lager:debug("this message should be logged"),
  149. ?assertEqual(1, count()),
  150. ?assertEqual(1, count_ignored()),
  151. ?assertEqual(debug, lager:get_loglevel(?MODULE)),
  152. ok
  153. end
  154. }
  155. ]
  156. }.
  157. setup() ->
  158. error_logger:tty(false),
  159. application:load(lager),
  160. application:set_env(lager, handlers, [{?MODULE, info}]),
  161. application:set_env(lager, error_logger_redirect, false),
  162. application:start(lager),
  163. gen_event:call(lager_event, ?MODULE, flush).
  164. cleanup(_) ->
  165. application:stop(lager),
  166. application:unload(lager),
  167. error_logger:tty(true).
  168. crash(Type) ->
  169. spawn(fun() -> gen_server:call(crash, Type) end),
  170. timer:sleep(100).
  171. error_logger_redirect_crash_test_() ->
  172. {foreach,
  173. fun() ->
  174. error_logger:tty(false),
  175. application:load(lager),
  176. application:set_env(lager, error_logger_redirect, true),
  177. application:set_env(lager, handlers, [{?MODULE, error}]),
  178. application:start(lager),
  179. crash:start()
  180. end,
  181. fun(_) ->
  182. application:stop(lager),
  183. application:unload(lager),
  184. case whereis(crash) of
  185. undefined -> ok;
  186. Pid -> exit(Pid, kill)
  187. end,
  188. error_logger:tty(true)
  189. end,
  190. [
  191. {"again, there is nothing up my sleeve",
  192. fun() ->
  193. ?assertEqual(undefined, pop()),
  194. ?assertEqual(0, count())
  195. end
  196. },
  197. {"bad return value",
  198. fun() ->
  199. Pid = whereis(crash),
  200. crash(bad_return),
  201. {_, _, Msg} = pop(),
  202. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad return value: bleh", [Pid])),
  203. ?assertEqual(Expected, lists:flatten(Msg))
  204. end
  205. },
  206. {"case clause",
  207. fun() ->
  208. Pid = whereis(crash),
  209. crash(case_clause),
  210. {_, _, Msg} = pop(),
  211. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no case clause matching {} in crash:handle_call/3", [Pid])),
  212. ?assertEqual(Expected, lists:flatten(Msg))
  213. end
  214. },
  215. {"function clause",
  216. fun() ->
  217. Pid = whereis(crash),
  218. crash(function_clause),
  219. {_, _, Msg} = pop(),
  220. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no function clause matching crash:function({})", [Pid])),
  221. ?assertEqual(Expected, lists:flatten(Msg))
  222. end
  223. },
  224. {"if clause",
  225. fun() ->
  226. Pid = whereis(crash),
  227. crash(if_clause),
  228. {_, _, Msg} = pop(),
  229. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no true branch found while evaluating if expression in crash:handle_call/3", [Pid])),
  230. ?assertEqual(Expected, lists:flatten(Msg))
  231. end
  232. },
  233. {"try clause",
  234. fun() ->
  235. Pid = whereis(crash),
  236. crash(try_clause),
  237. {_, _, Msg} = pop(),
  238. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no try clause matching [] in crash:handle_call/3", [Pid])),
  239. ?assertEqual(Expected, lists:flatten(Msg))
  240. end
  241. },
  242. {"undefined function",
  243. fun() ->
  244. Pid = whereis(crash),
  245. crash(undef),
  246. {_, _, Msg} = pop(),
  247. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: call to undefined function crash:booger/0 from crash:handle_call/3", [Pid])),
  248. ?assertEqual(Expected, lists:flatten(Msg))
  249. end
  250. },
  251. {"bad math",
  252. fun() ->
  253. Pid = whereis(crash),
  254. crash(badarith),
  255. {_, _, Msg} = pop(),
  256. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad arithmetic expression in crash:handle_call/3", [Pid])),
  257. ?assertEqual(Expected, lists:flatten(Msg))
  258. end
  259. },
  260. {"bad match",
  261. fun() ->
  262. Pid = whereis(crash),
  263. crash(badmatch),
  264. {_, _, Msg} = pop(),
  265. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no match of right hand value {} in crash:handle_call/3", [Pid])),
  266. ?assertEqual(Expected, lists:flatten(Msg))
  267. end
  268. },
  269. {"bad arity",
  270. fun() ->
  271. Pid = whereis(crash),
  272. crash(badarity),
  273. {_, _, Msg} = pop(),
  274. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: fun called with wrong arity of 1 instead of 3 in crash:handle_call/3", [Pid])),
  275. ?assertEqual(Expected, lists:flatten(Msg))
  276. end
  277. },
  278. {"bad arg1",
  279. fun() ->
  280. Pid = whereis(crash),
  281. crash(badarg1),
  282. {_, _, Msg} = pop(),
  283. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad argument in crash:handle_call/3", [Pid])),
  284. ?assertEqual(Expected, lists:flatten(Msg))
  285. end
  286. },
  287. {"bad arg2",
  288. fun() ->
  289. Pid = whereis(crash),
  290. crash(badarg2),
  291. {_, _, Msg} = pop(),
  292. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([[102,111,111],bar]) in crash:handle_call/3", [Pid])),
  293. ?assertEqual(Expected, lists:flatten(Msg))
  294. end
  295. },
  296. {"noproc",
  297. fun() ->
  298. Pid = whereis(crash),
  299. crash(noproc),
  300. {_, _, Msg} = pop(),
  301. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)", [Pid])),
  302. ?assertEqual(Expected, lists:flatten(Msg))
  303. end
  304. },
  305. {"badfun",
  306. fun() ->
  307. Pid = whereis(crash),
  308. crash(badfun),
  309. {_, _, Msg} = pop(),
  310. Expected = lists:flatten(io_lib:format("[error] ~w gen_server crash terminated with reason: bad function booger in crash:handle_call/3", [Pid])),
  311. ?assertEqual(Expected, lists:flatten(Msg))
  312. end
  313. }
  314. ]
  315. }.
  316. error_logger_redirect_test_() ->
  317. {foreach,
  318. fun() ->
  319. error_logger:tty(false),
  320. application:load(lager),
  321. application:set_env(lager, error_logger_redirect, true),
  322. application:set_env(lager, handlers, [{?MODULE, info}]),
  323. application:start(lager),
  324. timer:sleep(100),
  325. gen_event:call(lager_event, ?MODULE, flush)
  326. end,
  327. fun(_) ->
  328. application:stop(lager),
  329. application:unload(lager),
  330. error_logger:tty(true)
  331. end,
  332. [
  333. {"error reports are printed",
  334. fun() ->
  335. error_logger:error_report([{this, is}, a, {silly, format}]),
  336. timer:sleep(100),
  337. {_, _, Msg} = pop(),
  338. Expected = lists:flatten(io_lib:format("[error] ~w this: is, a, silly: format", [self()])),
  339. ?assertEqual(Expected, lists:flatten(Msg))
  340. end
  341. },
  342. {"string error reports are printed",
  343. fun() ->
  344. error_logger:error_report("this is less silly"),
  345. timer:sleep(100),
  346. {_, _, Msg} = pop(),
  347. Expected = lists:flatten(io_lib:format("[error] ~w this is less silly", [self()])),
  348. ?assertEqual(Expected, lists:flatten(Msg))
  349. end
  350. },
  351. {"error messages are printed",
  352. fun() ->
  353. error_logger:error_msg("doom, doom has come upon you all"),
  354. timer:sleep(100),
  355. {_, _, Msg} = pop(),
  356. Expected = lists:flatten(io_lib:format("[error] ~w doom, doom has come upon you all", [self()])),
  357. ?assertEqual(Expected, lists:flatten(Msg))
  358. end
  359. },
  360. {"info reports are printed",
  361. fun() ->
  362. error_logger:info_report([{this, is}, a, {silly, format}]),
  363. timer:sleep(100),
  364. {_, _, Msg} = pop(),
  365. Expected = lists:flatten(io_lib:format("[info] ~w this: is, a, silly: format", [self()])),
  366. ?assertEqual(Expected, lists:flatten(Msg))
  367. end
  368. },
  369. {"single term info reports are printed",
  370. fun() ->
  371. error_logger:info_report({foolish, bees}),
  372. timer:sleep(100),
  373. {_, _, Msg} = pop(),
  374. Expected = lists:flatten(io_lib:format("[info] ~w {foolish,bees}", [self()])),
  375. ?assertEqual(Expected, lists:flatten(Msg))
  376. end
  377. },
  378. {"single term error reports are printed",
  379. fun() ->
  380. error_logger:error_report({foolish, bees}),
  381. timer:sleep(100),
  382. {_, _, Msg} = pop(),
  383. Expected = lists:flatten(io_lib:format("[error] ~w {foolish,bees}", [self()])),
  384. ?assertEqual(Expected, lists:flatten(Msg))
  385. end
  386. },
  387. {"string info reports are printed",
  388. fun() ->
  389. error_logger:info_report("this is less silly"),
  390. timer:sleep(100),
  391. {_, _, Msg} = pop(),
  392. Expected = lists:flatten(io_lib:format("[info] ~w this is less silly", [self()])),
  393. ?assertEqual(Expected, lists:flatten(Msg))
  394. end
  395. },
  396. {"info messages are printed",
  397. fun() ->
  398. error_logger:info_msg("doom, doom has come upon you all"),
  399. timer:sleep(100),
  400. {_, _, Msg} = pop(),
  401. Expected = lists:flatten(io_lib:format("[info] ~w doom, doom has come upon you all", [self()])),
  402. ?assertEqual(Expected, lists:flatten(Msg))
  403. end
  404. },
  405. {"warning messages are printed at the correct level",
  406. fun() ->
  407. error_logger:warning_msg("doom, doom has come upon you all"),
  408. Map = error_logger:warning_map(),
  409. timer:sleep(100),
  410. {_, _, Msg} = pop(),
  411. Expected = lists:flatten(io_lib:format("[~w] ~w doom, doom has come upon you all", [Map, self()])),
  412. ?assertEqual(Expected, lists:flatten(Msg))
  413. end
  414. },
  415. {"warning reports are printed at the correct level",
  416. fun() ->
  417. error_logger:warning_report([{i, like}, pie]),
  418. Map = error_logger:warning_map(),
  419. timer:sleep(100),
  420. {_, _, Msg} = pop(),
  421. Expected = lists:flatten(io_lib:format("[~w] ~w i: like, pie", [Map, self()])),
  422. ?assertEqual(Expected, lists:flatten(Msg))
  423. end
  424. },
  425. {"single term warning reports are printed at the correct level",
  426. fun() ->
  427. error_logger:warning_report({foolish, bees}),
  428. Map = error_logger:warning_map(),
  429. timer:sleep(100),
  430. {_, _, Msg} = pop(),
  431. Expected = lists:flatten(io_lib:format("[~w] ~w {foolish,bees}", [Map, self()])),
  432. ?assertEqual(Expected, lists:flatten(Msg))
  433. end
  434. },
  435. {"application stop reports",
  436. fun() ->
  437. error_logger:info_report([{application, foo}, {exited, quittin_time}, {type, lazy}]),
  438. timer:sleep(100),
  439. {_, _, Msg} = pop(),
  440. Expected = lists:flatten(io_lib:format("[info] ~w Application foo exited with reason: quittin_time", [self()])),
  441. ?assertEqual(Expected, lists:flatten(Msg))
  442. end
  443. },
  444. {"supervisor reports",
  445. fun() ->
  446. error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]),
  447. timer:sleep(100),
  448. {_, _, Msg} = pop(),
  449. Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason fired in context france", [self()])),
  450. ?assertEqual(Expected, lists:flatten(Msg))
  451. end
  452. },
  453. {"supervisor_bridge reports",
  454. fun() ->
  455. error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{mod, mini_steve}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]),
  456. timer:sleep(100),
  457. {_, _, Msg} = pop(),
  458. Expected = lists:flatten(io_lib:format("[error] ~w Supervisor steve had child at module mini_steve at bleh exit with reason fired in context france", [self()])),
  459. ?assertEqual(Expected, lists:flatten(Msg))
  460. end
  461. },
  462. {"application progress report",
  463. fun() ->
  464. error_logger:info_report(progress, [{application, foo}, {started_at, node()}]),
  465. timer:sleep(100),
  466. {_, _, Msg} = pop(),
  467. Expected = lists:flatten(io_lib:format("[info] ~w Application foo started on node ~w", [self(), node()])),
  468. ?assertEqual(Expected, lists:flatten(Msg))
  469. end
  470. },
  471. {"supervisor progress report",
  472. fun() ->
  473. lager:set_loglevel(?MODULE, debug),
  474. error_logger:info_report(progress, [{supervisor, {local, foo}}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]),
  475. timer:sleep(100),
  476. {_, _, Msg} = pop(),
  477. Expected = lists:flatten(io_lib:format("[debug] ~w Supervisor foo started foo:bar/1 at pid baz", [self()])),
  478. ?assertEqual(Expected, lists:flatten(Msg))
  479. end
  480. },
  481. {"crash report for emfile",
  482. fun() ->
  483. error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, {emfile, [{stack, trace, 1}]}, []}}], []]),
  484. timer:sleep(100),
  485. {_, _, Msg} = pop(),
  486. Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: maximum number of file descriptors exhausted, check ulimit -n", [self(), self()])),
  487. ?assertEqual(Expected, lists:flatten(Msg))
  488. end
  489. },
  490. {"crash report for system process limit",
  491. fun() ->
  492. error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, {system_limit, [{erlang, spawn, 1}]}, []}}], []]),
  493. timer:sleep(100),
  494. {_, _, Msg} = pop(),
  495. Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self(), self()])),
  496. ?assertEqual(Expected, lists:flatten(Msg))
  497. end
  498. },
  499. {"crash report for system process limit2",
  500. fun() ->
  501. error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, {system_limit, [{erlang, spawn_opt, 1}]}, []}}], []]),
  502. timer:sleep(100),
  503. {_, _, Msg} = pop(),
  504. Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self(), self()])),
  505. ?assertEqual(Expected, lists:flatten(Msg))
  506. end
  507. },
  508. {"crash report for system port limit",
  509. fun() ->
  510. error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, {system_limit, [{erlang, open_port, 1}]}, []}}], []]),
  511. timer:sleep(100),
  512. {_, _, Msg} = pop(),
  513. Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ports exceeded", [self(), self()])),
  514. ?assertEqual(Expected, lists:flatten(Msg))
  515. end
  516. },
  517. {"crash report for system port limit",
  518. fun() ->
  519. error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, {system_limit, [{erlang, list_to_atom, 1}]}, []}}], []]),
  520. timer:sleep(100),
  521. {_, _, Msg} = pop(),
  522. Expected = lists:flatten(io_lib:format("[error] ~w CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: tried to create an atom larger than 255, or maximum atom count exceeded", [self(), self()])),
  523. ?assertEqual(Expected, lists:flatten(Msg))
  524. end
  525. },
  526. {"messages should not be generated if they don't satisfy the threshold",
  527. fun() ->
  528. lager:set_loglevel(?MODULE, error),
  529. error_logger:info_report([hello, world]),
  530. timer:sleep(100),
  531. ?assertEqual(0, count()),
  532. ?assertEqual(0, count_ignored()),
  533. lager:set_loglevel(?MODULE, info),
  534. error_logger:info_report([hello, world]),
  535. timer:sleep(100),
  536. ?assertEqual(1, count()),
  537. ?assertEqual(0, count_ignored()),
  538. lager:set_loglevel(?MODULE, error),
  539. lager_mochiglobal:put(loglevel, ?DEBUG),
  540. error_logger:info_report([hello, world]),
  541. timer:sleep(100),
  542. ?assertEqual(1, count()),
  543. ?assertEqual(1, count_ignored())
  544. end
  545. }
  546. ]
  547. }.
  548. -endif.