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

582 行
24 KiB

  1. %% ``The contents of this file are subject to the Erlang Public License,
  2. %% Version 1.1, (the "License"); you may not use this file except in
  3. %% compliance with the License. You should have received a copy of the
  4. %% Erlang Public License along with your Erlang distribution. If not, it can be
  5. %% retrieved via the world wide web at http://www.erlang.org/.
  6. %%
  7. %% Software distributed under the License is distributed on an "AS IS"
  8. %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  9. %% the License for the specific language governing rights and limitations
  10. %% under the License.
  11. %%
  12. %% The Initial Developer of the Original Code is Corelatus AB.
  13. %% Portions created by Corelatus are Copyright 2003, Corelatus
  14. %% AB. All Rights Reserved.''
  15. %%
  16. %% @doc Module to print out terms for logging. Limits by length rather than depth.
  17. %%
  18. %% The resulting string may be slightly larger than the limit; the intention
  19. %% is to provide predictable CPU and memory consumption for formatting
  20. %% terms, not produce precise string lengths.
  21. %%
  22. %% Typical use:
  23. %%
  24. %% trunc_io:print(Term, 500).
  25. %%
  26. %% Source license: Erlang Public License.
  27. %% Original author: Matthias Lang, <tt>matthias@corelatus.se</tt>
  28. %%
  29. %% Various changes to this module, most notably the format/3 implementation
  30. %% were added by Andrew Thompson `<andrew@basho.com>'. The module has been renamed
  31. %% to avoid conflicts with the vanilla module.
  32. -module(lager_trunc_io).
  33. -author('matthias@corelatus.se').
  34. %% And thanks to Chris Newcombe for a bug fix
  35. -export([format/3, format/4, print/2, print/3, fprint/2, fprint/3, safe/2]). % interface functions
  36. -version("$Id: trunc_io.erl,v 1.11 2009-02-23 12:01:06 matthias Exp $").
  37. -ifdef(TEST).
  38. -export([perf/0, perf/3, perf1/0, test/0, test/2]). % testing functions
  39. -include_lib("eunit/include/eunit.hrl").
  40. -endif.
  41. -type option() :: {'depth', integer()}
  42. | {'lists_as_strings', boolean()}
  43. | {'force_strings', boolean()}.
  44. -type options() :: [option()].
  45. -record(print_options, {
  46. %% negative depth means no depth limiting
  47. depth = -1 :: integer(),
  48. %% whether to print lists as strings, if possible
  49. lists_as_strings = true :: boolean(),
  50. %% force strings, or binaries to be printed as a string,
  51. %% even if they're not printable
  52. force_strings = false :: boolean()
  53. }).
  54. format(Fmt, Args, Max) ->
  55. format(Fmt, Args, Max, []).
  56. format(Fmt, Args, Max, Options) ->
  57. try lager_format:format(Fmt, Args, Max, Options) of
  58. Result -> Result
  59. catch
  60. _:_ ->
  61. erlang:error(badarg, [Fmt, Args])
  62. end.
  63. %% @doc Returns an flattened list containing the ASCII representation of the given
  64. %% term.
  65. -spec fprint(term(), pos_integer()) -> string().
  66. fprint(Term, Max) ->
  67. fprint(Term, Max, []).
  68. %% @doc Returns an flattened list containing the ASCII representation of the given
  69. %% term.
  70. -spec fprint(term(), pos_integer(), options()) -> string().
  71. fprint(T, Max, Options) ->
  72. {L, _} = print(T, Max, prepare_options(Options, #print_options{})),
  73. lists:flatten(L).
  74. %% @doc Same as print, but never crashes.
  75. %%
  76. %% This is a tradeoff. Print might conceivably crash if it's asked to
  77. %% print something it doesn't understand, for example some new data
  78. %% type in a future version of Erlang. If print crashes, we fall back
  79. %% to io_lib to format the term, but then the formatting is
  80. %% depth-limited instead of length limited, so you might run out
  81. %% memory printing it. Out of the frying pan and into the fire.
  82. %%
  83. -spec safe(term(), pos_integer()) -> {string(), pos_integer()} | {string()}.
  84. safe(What, Len) ->
  85. case catch print(What, Len) of
  86. {L, Used} when is_list(L) -> {L, Used};
  87. _ -> {"unable to print" ++ io_lib:write(What, 99)}
  88. end.
  89. %% @doc Returns {List, Length}
  90. -spec print(term(), pos_integer()) -> {iolist(), pos_integer()}.
  91. print(Term, Max) ->
  92. print(Term, Max, []).
  93. %% @doc Returns {List, Length}
  94. -spec print(term(), pos_integer(), options() | #print_options{}) -> {iolist(), pos_integer()}.
  95. print(Term, Max, Options) when is_list(Options) ->
  96. %% need to convert the proplist to a record
  97. print(Term, Max, prepare_options(Options, #print_options{}));
  98. print(Term, _Max, #print_options{force_strings=true}) when not is_list(Term), not is_binary(Term), not is_atom(Term) ->
  99. erlang:error(badarg);
  100. print(_, Max, _Options) when Max < 0 -> {"...", 3};
  101. print(_, _, #print_options{depth=0}) -> {"...", 3};
  102. print(Tuple, Max, Options) when is_tuple(Tuple) ->
  103. {TC, Len} = tuple_contents(Tuple, Max-2, Options),
  104. {[${, TC, $}], Len + 2};
  105. %% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need
  106. %% to be truncated. This isn't strictly true, someone could make an
  107. %% arbitrarily long bignum. Let's assume that won't happen unless someone
  108. %% is being malicious.
  109. %%
  110. print(Atom, _Max, #print_options{force_strings=NoQuote}) when is_atom(Atom) ->
  111. L = atom_to_list(Atom),
  112. R = case atom_needs_quoting_start(L) andalso not NoQuote of
  113. true -> lists:flatten([$', L, $']);
  114. false -> L
  115. end,
  116. {R, length(R)};
  117. print(<<>>, _Max, _Options) ->
  118. {"<<>>", 4};
  119. print(Binary, 0, _Options) when is_bitstring(Binary) ->
  120. {"<<..>>", 6};
  121. print(Binary, Max, Options) when is_binary(Binary) ->
  122. B = binary_to_list(Binary, 1, lists:min([Max, byte_size(Binary)])),
  123. {L, Len} = case Options#print_options.lists_as_strings orelse
  124. Options#print_options.force_strings of
  125. true ->
  126. alist_start(B, Max-4, Options);
  127. _ ->
  128. list_body(B, Max-4, Options, false)
  129. end,
  130. {Res, Length} = case L of
  131. [91, X, 93] ->
  132. {X, Len - 2};
  133. X ->
  134. {X, Len}
  135. end,
  136. case Options#print_options.force_strings of
  137. true ->
  138. {Res, Length};
  139. _ ->
  140. {["<<", Res, ">>"], Length+4}
  141. end;
  142. %% bitstrings are binary's evil brother who doesn't end on an 8 bit boundary.
  143. %% This makes printing them extremely annoying, so list_body/list_bodyc has
  144. %% some magic for dealing with the output of bitstring_to_list, which returns
  145. %% a list of integers (as expected) but with a trailing binary that represents
  146. %% the remaining bits.
  147. print(BitString, Max, Options) when is_bitstring(BitString) ->
  148. case byte_size(BitString) > Max of
  149. true ->
  150. BL = binary_to_list(BitString, 1, Max);
  151. _ ->
  152. BL = erlang:bitstring_to_list(BitString)
  153. end,
  154. {X, Len0} = list_body(BL, Max - 4, Options, false),
  155. {["<<", X, ">>"], Len0 + 4};
  156. print(Float, _Max, _Options) when is_float(Float) ->
  157. %% use the same function io_lib:format uses to print floats
  158. %% float_to_list is way too verbose.
  159. L = io_lib_format:fwrite_g(Float),
  160. {L, length(L)};
  161. print(Fun, Max, _Options) when is_function(Fun) ->
  162. L = erlang:fun_to_list(Fun),
  163. case length(L) > Max of
  164. true ->
  165. S = erlang:max(5, Max),
  166. Res = string:substr(L, 1, S) ++ "..>",
  167. {Res, length(Res)};
  168. _ ->
  169. {L, length(L)}
  170. end;
  171. print(Integer, _Max, _Options) when is_integer(Integer) ->
  172. L = integer_to_list(Integer),
  173. {L, length(L)};
  174. print(Pid, _Max, _Options) when is_pid(Pid) ->
  175. L = pid_to_list(Pid),
  176. {L, length(L)};
  177. print(Ref, _Max, _Options) when is_reference(Ref) ->
  178. L = erlang:ref_to_list(Ref),
  179. {L, length(L)};
  180. print(Port, _Max, _Options) when is_port(Port) ->
  181. L = erlang:port_to_list(Port),
  182. {L, length(L)};
  183. print(List, Max, Options) when is_list(List) ->
  184. case Options#print_options.lists_as_strings orelse
  185. Options#print_options.force_strings of
  186. true ->
  187. alist_start(List, Max, dec_depth(Options));
  188. _ ->
  189. {R, Len} = list_body(List, Max - 2, dec_depth(Options), false),
  190. {[$[, R, $]], Len + 2}
  191. end.
  192. %% Returns {List, Length}
  193. tuple_contents(Tuple, Max, Options) ->
  194. L = tuple_to_list(Tuple),
  195. list_body(L, Max, dec_depth(Options), true).
  196. %% Format the inside of a list, i.e. do not add a leading [ or trailing ].
  197. %% Returns {List, Length}
  198. list_body([], _Max, _Options, _Tuple) -> {[], 0};
  199. list_body(_, Max, _Options, _Tuple) when Max < 4 -> {"...", 3};
  200. list_body(_, _Max, #print_options{depth=0}, _Tuple) -> {"...", 3};
  201. list_body([B], _Max, _Options, _Tuple) when is_bitstring(B), not is_binary(B) ->
  202. Size = bit_size(B),
  203. <<Value:Size>> = B,
  204. ValueStr = integer_to_list(Value),
  205. SizeStr = integer_to_list(Size),
  206. {[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +1};
  207. list_body([H|T], Max, Options, Tuple) ->
  208. {List, Len} = print(H, Max, Options),
  209. {Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple),
  210. {[List|Final], FLen + Len};
  211. list_body(X, Max, Options, _Tuple) -> %% improper list
  212. {List, Len} = print(X, Max - 1, Options),
  213. {[$|,List], Len + 1}.
  214. list_bodyc([], _Max, _Options, _Tuple) -> {[], 0};
  215. list_bodyc(_, Max, _Options, _Tuple) when Max < 5 -> {",...", 4};
  216. list_bodyc([B], _Max, _Options, _Tuple) when is_bitstring(B), not is_binary(B) ->
  217. Size = bit_size(B),
  218. <<Value:Size>> = B,
  219. ValueStr = integer_to_list(Value),
  220. SizeStr = integer_to_list(Size),
  221. {[$, , ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +2};
  222. list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) ->
  223. {List, Len} = print(H, Max, dec_depth(Options)),
  224. {Final, FLen} = list_bodyc(T, Max - Len - 1, Options, Tuple),
  225. Sep = case Depth == 1 andalso not Tuple of
  226. true -> $|;
  227. _ -> $,
  228. end,
  229. {[Sep, List|Final], FLen + Len + 1};
  230. list_bodyc(X, Max, Options, _Tuple) -> %% improper list
  231. {List, Len} = print(X, Max - 1, Options),
  232. {[$|,List], Len + 1}.
  233. %% The head of a list we hope is ascii. Examples:
  234. %%
  235. %% [65,66,67] -> "ABC"
  236. %% [65,0,67] -> "A"[0,67]
  237. %% [0,65,66] -> [0,65,66]
  238. %% [65,b,66] -> "A"[b,66]
  239. %%
  240. alist_start([], _Max, #print_options{force_strings=true}) -> {"", 0};
  241. alist_start([], _Max, _Options) -> {"[]", 2};
  242. alist_start(_, Max, _Options) when Max < 4 -> {"...", 3};
  243. alist_start(_, _Max, #print_options{depth=0}) -> {"[...]", 5};
  244. alist_start(L, Max, #print_options{force_strings=true} = Options) ->
  245. alist(L, Max, Options);
  246. alist_start([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
  247. try alist([H|T], Max -1, Options) of
  248. {L, Len} ->
  249. {[$"|L], Len + 1}
  250. catch
  251. throw:unprintable ->
  252. {R, Len} = list_body([H|T], Max-2, Options, false),
  253. {[$[, R, $]], Len + 2}
  254. end;
  255. alist_start([H|T], Max, Options) when H =:= 9; H =:= 10; H =:= 13 ->
  256. try alist([H|T], Max -1, Options) of
  257. {L, Len} ->
  258. {[$"|L], Len + 1}
  259. catch
  260. throw:unprintable ->
  261. {R, Len} = list_body([H|T], Max-2, Options, false),
  262. {[$[, R, $]], Len + 2}
  263. end;
  264. alist_start(L, Max, Options) ->
  265. {R, Len} = list_body(L, Max-2, Options, false),
  266. {[$[, R, $]], Len + 2}.
  267. alist([], _Max, #print_options{force_strings=true}) -> {"", 0};
  268. alist([], _Max, _Options) -> {"\"", 1};
  269. alist(_, Max, #print_options{force_strings=true}) when Max < 4 -> {"...", 3};
  270. alist(_, Max, #print_options{force_strings=false}) when Max < 5 -> {"...\"", 4};
  271. alist([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
  272. {L, Len} = alist(T, Max-1, Options),
  273. {[H|L], Len + 1};
  274. alist([H|T], Max, Options) when H =:= 9; H =:= 10; H =:= 13 ->
  275. {L, Len} = alist(T, Max-1, Options),
  276. case Options#print_options.force_strings of
  277. true ->
  278. {[H|L], Len + 1};
  279. _ ->
  280. {[escape(H)|L], Len + 1}
  281. end;
  282. alist([H|T], Max, #print_options{force_strings=true} = Options) when is_integer(H) ->
  283. {L, Len} = alist(T, Max-1, Options),
  284. {[H|L], Len + 1};
  285. alist(_, _, #print_options{force_strings=true}) ->
  286. erlang:error(badarg);
  287. alist(_L, _Max, _Options) ->
  288. throw(unprintable).
  289. %% is the first character in the atom alphabetic & lowercase?
  290. atom_needs_quoting_start([H|T]) when H >= $a, H =< $z ->
  291. atom_needs_quoting(T);
  292. atom_needs_quoting_start(_) ->
  293. true.
  294. atom_needs_quoting([]) ->
  295. false;
  296. atom_needs_quoting([H|T]) when (H >= $a andalso H =< $z);
  297. (H >= $A andalso H =< $Z);
  298. (H >= $0 andalso H =< $9);
  299. H == $@; H == $_ ->
  300. atom_needs_quoting(T);
  301. atom_needs_quoting(_) ->
  302. true.
  303. -spec prepare_options(options(), #print_options{}) -> #print_options{}.
  304. prepare_options([], Options) ->
  305. Options;
  306. prepare_options([{depth, Depth}|T], Options) when is_integer(Depth) ->
  307. prepare_options(T, Options#print_options{depth=Depth});
  308. prepare_options([{lists_as_strings, Bool}|T], Options) when is_boolean(Bool) ->
  309. prepare_options(T, Options#print_options{lists_as_strings = Bool});
  310. prepare_options([{force_strings, Bool}|T], Options) when is_boolean(Bool) ->
  311. prepare_options(T, Options#print_options{force_strings = Bool}).
  312. dec_depth(#print_options{depth=Depth} = Options) when Depth > 0 ->
  313. Options#print_options{depth=Depth-1};
  314. dec_depth(Options) ->
  315. Options.
  316. escape(9) -> "\\t";
  317. escape(10) -> "\\n";
  318. escape(13) -> "\\r".
  319. -ifdef(TEST).
  320. %%--------------------
  321. %% The start of a test suite. So far, it only checks for not crashing.
  322. -spec test() -> ok.
  323. test() ->
  324. test(trunc_io, print).
  325. -spec test(atom(), atom()) -> ok.
  326. test(Mod, Func) ->
  327. Simple_items = [atom, 1234, 1234.0, {tuple}, [], [list], "string", self(),
  328. <<1,2,3>>, make_ref(), fun() -> ok end],
  329. F = fun(A) ->
  330. Mod:Func(A, 100),
  331. Mod:Func(A, 2),
  332. Mod:Func(A, 20)
  333. end,
  334. G = fun(A) ->
  335. case catch F(A) of
  336. {'EXIT', _} -> exit({failed, A});
  337. _ -> ok
  338. end
  339. end,
  340. lists:foreach(G, Simple_items),
  341. Tuples = [ {1,2,3,a,b,c}, {"abc", def, 1234},
  342. {{{{a},b,c,{d},e}},f}],
  343. Lists = [ [1,2,3,4,5,6,7], lists:seq(1,1000),
  344. [{a}, {a,b}, {a, [b,c]}, "def"], [a|b], [$a|$b] ],
  345. lists:foreach(G, Tuples),
  346. lists:foreach(G, Lists).
  347. -spec perf() -> ok.
  348. perf() ->
  349. {New, _} = timer:tc(trunc_io, perf, [trunc_io, print, 1000]),
  350. {Old, _} = timer:tc(trunc_io, perf, [io_lib, write, 1000]),
  351. io:fwrite("New code took ~p us, old code ~p\n", [New, Old]).
  352. -spec perf(atom(), atom(), integer()) -> done.
  353. perf(M, F, Reps) when Reps > 0 ->
  354. test(M,F),
  355. perf(M,F,Reps-1);
  356. perf(_,_,_) ->
  357. done.
  358. %% Performance test. Needs a particularly large term I saved as a binary...
  359. -spec perf1() -> {non_neg_integer(), non_neg_integer()}.
  360. perf1() ->
  361. {ok, Bin} = file:read_file("bin"),
  362. A = binary_to_term(Bin),
  363. {N, _} = timer:tc(trunc_io, print, [A, 1500]),
  364. {M, _} = timer:tc(io_lib, write, [A]),
  365. {N, M}.
  366. format_test() ->
  367. %% simple format strings
  368. ?assertEqual("foobar", lists:flatten(format("~s", [["foo", $b, $a, $r]], 50))),
  369. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~p", [["foo", $b, $a, $r]], 50))),
  370. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~P", [["foo", $b, $a, $r], 10], 50))),
  371. ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~w", [["foo", $b, $a, $r]], 50))),
  372. %% complex ones
  373. ?assertEqual(" foobar", lists:flatten(format("~10s", [["foo", $b, $a, $r]], 50))),
  374. ?assertEqual(" [\"foo\",98,97,114]", lists:flatten(format("~22p", [["foo", $b, $a, $r]], 50))),
  375. ?assertEqual(" [\"foo\",98,97,114]", lists:flatten(format("~22P", [["foo", $b, $a, $r], 10], 50))),
  376. ?assertEqual("**********", lists:flatten(format("~10W", [["foo", $b, $a, $r], 10], 50))),
  377. ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~25W", [["foo", $b, $a, $r], 10], 50))),
  378. ok.
  379. atom_quoting_test() ->
  380. ?assertEqual("hello", lists:flatten(format("~p", [hello], 50))),
  381. ?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))),
  382. ?assertEqual("'Hello world'", lists:flatten(format("~p", ['Hello world'], 50))),
  383. ?assertEqual("hello_world", lists:flatten(format("~p", ['hello_world'], 50))),
  384. ?assertEqual("'node@127.0.0.1'", lists:flatten(format("~p", ['node@127.0.0.1'], 50))),
  385. ?assertEqual("node@nohost", lists:flatten(format("~p", [node@nohost], 50))),
  386. ?assertEqual("abc123", lists:flatten(format("~p", [abc123], 50))),
  387. ok.
  388. sane_float_printing_test() ->
  389. ?assertEqual("1.0", lists:flatten(format("~p", [1.0], 50))),
  390. ?assertEqual("1.23456789", lists:flatten(format("~p", [1.23456789], 50))),
  391. ?assertEqual("1.23456789", lists:flatten(format("~p", [1.234567890], 50))),
  392. ?assertEqual("0.3333333333333333", lists:flatten(format("~p", [1/3], 50))),
  393. ?assertEqual("0.1234567", lists:flatten(format("~p", [0.1234567], 50))),
  394. ok.
  395. float_inside_list_test() ->
  396. ?assertEqual("[97,38.233913133184835,99]", lists:flatten(format("~p", [[$a, 38.233913133184835, $c]], 50))),
  397. ?assertError(badarg, lists:flatten(format("~s", [[$a, 38.233913133184835, $c]], 50))),
  398. ok.
  399. quote_strip_test() ->
  400. ?assertEqual("\"hello\"", lists:flatten(format("~p", ["hello"], 50))),
  401. ?assertEqual("hello", lists:flatten(format("~s", ["hello"], 50))),
  402. ?assertEqual("hello", lists:flatten(format("~s", [hello], 50))),
  403. ?assertEqual("hello", lists:flatten(format("~p", [hello], 50))),
  404. ?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))),
  405. ?assertEqual("hello world", lists:flatten(format("~s", ['hello world'], 50))),
  406. ok.
  407. binary_printing_test() ->
  408. ?assertEqual("<<>>", lists:flatten(format("~p", [<<>>], 50))),
  409. ?assertEqual("<<..>>", lists:flatten(format("~p", [<<"hi">>], 0))),
  410. ?assertEqual("<<...>>", lists:flatten(format("~p", [<<"hi">>], 1))),
  411. ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<$h, $e, $l, $l, $o>>], 50))),
  412. ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<"hello">>], 50))),
  413. ?assertEqual("<<104,101,108,108,111>>", lists:flatten(format("~w", [<<"hello">>], 50))),
  414. ?assertEqual("<<1,2,3,4>>", lists:flatten(format("~p", [<<1, 2, 3, 4>>], 50))),
  415. ?assertEqual([1,2,3,4], lists:flatten(format("~s", [<<1, 2, 3, 4>>], 50))),
  416. ?assertEqual("hello", lists:flatten(format("~s", [<<"hello">>], 50))),
  417. ?assertEqual("hello\nworld", lists:flatten(format("~s", [<<"hello\nworld">>], 50))),
  418. ?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))),
  419. ?assertEqual(" hello", lists:flatten(format("~10s", [<<"hello">>], 50))),
  420. ok.
  421. bitstring_printing_test() ->
  422. ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
  423. [<<1, 2, 3, 1:7>>], 100))),
  424. ?assertEqual("<<1:7>>", lists:flatten(format("~p",
  425. [<<1:7>>], 100))),
  426. ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
  427. [<<1, 2, 3, 1:7>>], 12))),
  428. ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
  429. [<<1, 2, 3, 1:7>>], 13))),
  430. ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
  431. [<<1, 2, 3, 1:7>>], 14))),
  432. ?assertEqual("<<..>>", lists:flatten(format("~p", [<<1:7>>], 0))),
  433. ?assertEqual("<<...>>", lists:flatten(format("~p", [<<1:7>>], 1))),
  434. ?assertEqual("[<<1>>,<<2>>]", lists:flatten(format("~p", [[<<1>>, <<2>>]],
  435. 100))),
  436. ok.
  437. list_printing_test() ->
  438. ?assertEqual("[]", lists:flatten(format("~p", [[]], 50))),
  439. ?assertEqual("[]", lists:flatten(format("~w", [[]], 50))),
  440. ?assertEqual("", lists:flatten(format("~s", [[]], 50))),
  441. ?assertEqual("...", lists:flatten(format("~s", [[]], -1))),
  442. ?assertEqual("[[]]", lists:flatten(format("~p", [[[]]], 50))),
  443. ?assertEqual("[13,11,10,8,5,4]", lists:flatten(format("~p", [[13,11,10,8,5,4]], 50))),
  444. ?assertEqual("\"\\rabc\"", lists:flatten(format("~p", [[13,$a, $b, $c]], 50))),
  445. ?assertEqual("[1,2,3|4]", lists:flatten(format("~p", [[1, 2, 3|4]], 50))),
  446. ?assertEqual("[...]", lists:flatten(format("~p", [[1, 2, 3,4]], 4))),
  447. ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 6))),
  448. ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 7))),
  449. ?assertEqual("[1,2,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 8))),
  450. ?assertEqual("[1|4]", lists:flatten(format("~p", [[1|4]], 50))),
  451. ?assertEqual("[1]", lists:flatten(format("~p", [[1]], 50))),
  452. ?assertError(badarg, lists:flatten(format("~s", [[1|4]], 50))),
  453. ?assertEqual("\"hello...\"", lists:flatten(format("~p", ["hello world"], 10))),
  454. ?assertEqual("hello w...", lists:flatten(format("~s", ["hello world"], 10))),
  455. ?assertEqual("hello world\r\n", lists:flatten(format("~s", ["hello world\r\n"], 50))),
  456. ?assertEqual("\rhello world\r\n", lists:flatten(format("~s", ["\rhello world\r\n"], 50))),
  457. ?assertEqual("\"\\rhello world\\r\\n\"", lists:flatten(format("~p", ["\rhello world\r\n"], 50))),
  458. ?assertEqual("[13,104,101,108,108,111,32,119,111,114,108,100,13,10]", lists:flatten(format("~w", ["\rhello world\r\n"], 60))),
  459. ?assertEqual("...", lists:flatten(format("~s", ["\rhello world\r\n"], 3))),
  460. ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
  461. lists:flatten(format("~p", [
  462. [22835963083295358096932575511191922182123945984,
  463. 22835963083295358096932575511191922182123945984]], 9))),
  464. ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
  465. lists:flatten(format("~p", [
  466. [22835963083295358096932575511191922182123945984,
  467. 22835963083295358096932575511191922182123945984]], 53))),
  468. ok.
  469. tuple_printing_test() ->
  470. ?assertEqual("{}", lists:flatten(format("~p", [{}], 50))),
  471. ?assertEqual("{}", lists:flatten(format("~w", [{}], 50))),
  472. ?assertError(badarg, lists:flatten(format("~s", [{}], 50))),
  473. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 1))),
  474. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 2))),
  475. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 3))),
  476. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 4))),
  477. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 5))),
  478. ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 6))),
  479. ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 7))),
  480. ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 9))),
  481. ?assertEqual("{foo,bar}", lists:flatten(format("~p", [{foo,bar}], 10))),
  482. ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
  483. lists:flatten(format("~w", [
  484. {22835963083295358096932575511191922182123945984,
  485. 22835963083295358096932575511191922182123945984}], 10))),
  486. ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
  487. lists:flatten(format("~w", [
  488. {22835963083295358096932575511191922182123945984,
  489. bar}], 10))),
  490. ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
  491. lists:flatten(format("~w", [
  492. {22835963083295358096932575511191922182123945984,
  493. 22835963083295358096932575511191922182123945984}], 53))),
  494. ok.
  495. unicode_test() ->
  496. ?assertEqual([231,167,129], lists:flatten(format("~s", [<<231,167,129>>], 50))),
  497. ?assertEqual([31169], lists:flatten(format("~ts", [<<231,167,129>>], 50))),
  498. ok.
  499. depth_limit_test() ->
  500. ?assertEqual("{...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 1], 50))),
  501. ?assertEqual("{a,...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 2], 50))),
  502. ?assertEqual("{a,[...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 3], 50))),
  503. ?assertEqual("{a,[b|...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 4], 50))),
  504. ?assertEqual("{a,[b,[...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 5], 50))),
  505. ?assertEqual("{a,[b,[c|...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 6], 50))),
  506. ?assertEqual("{a,[b,[c,[...]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 7], 50))),
  507. ?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 8], 50))),
  508. ?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 9], 50))),
  509. ?assertEqual("{a,{...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 3], 50))),
  510. ?assertEqual("{a,{b,...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 4], 50))),
  511. ?assertEqual("{a,{b,{...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 5], 50))),
  512. ?assertEqual("{a,{b,{c,...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 6], 50))),
  513. ?assertEqual("{a,{b,{c,{...}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 7], 50))),
  514. ?assertEqual("{a,{b,{c,{d}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 8], 50))),
  515. ?assertEqual("{\"a\",[...]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 3], 50))),
  516. ?assertEqual("{\"a\",[\"b\",[[...]|...]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 6], 50))),
  517. ?assertEqual("{\"a\",[\"b\",[\"c\",[\"d\"]]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 9], 50))),
  518. ok.
  519. -endif.