You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

891 lines
41 KiB

пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 13 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 13 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 13 година
пре 13 година
пре 13 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 13 година
пре 12 година
пре 14 година
пре 14 година
пре 13 година
пре 13 година
пре 14 година
пре 13 година
пре 14 година
пре 13 година
пре 14 година
пре 13 година
пре 14 година
пре 13 година
пре 14 година
пре 13 година
пре 14 година
пре 13 година
пре 14 година
пре 14 година
пре 13 година
пре 13 година
пре 13 година
пре 13 година
пре 13 година
пре 13 година
пре 12 година
пре 12 година
  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)
  58. catch
  59. _What:_Why ->
  60. erlang:error(badarg, [Fmt, Args])
  61. end.
  62. %% @doc Returns an flattened list containing the ASCII representation of the given
  63. %% term.
  64. -spec fprint(term(), pos_integer()) -> string().
  65. fprint(Term, Max) ->
  66. fprint(Term, Max, []).
  67. %% @doc Returns an flattened list containing the ASCII representation of the given
  68. %% term.
  69. -spec fprint(term(), pos_integer(), options()) -> string().
  70. fprint(T, Max, Options) ->
  71. {L, _} = print(T, Max, prepare_options(Options, #print_options{})),
  72. lists:flatten(L).
  73. %% @doc Same as print, but never crashes.
  74. %%
  75. %% This is a tradeoff. Print might conceivably crash if it's asked to
  76. %% print something it doesn't understand, for example some new data
  77. %% type in a future version of Erlang. If print crashes, we fall back
  78. %% to io_lib to format the term, but then the formatting is
  79. %% depth-limited instead of length limited, so you might run out
  80. %% memory printing it. Out of the frying pan and into the fire.
  81. %%
  82. -spec safe(term(), pos_integer()) -> {string(), pos_integer()} | {string()}.
  83. safe(What, Len) ->
  84. case catch print(What, Len) of
  85. {L, Used} when is_list(L) -> {L, Used};
  86. _ -> {"unable to print" ++ io_lib:write(What, 99)}
  87. end.
  88. %% @doc Returns {List, Length}
  89. -spec print(term(), pos_integer()) -> {iolist(), pos_integer()}.
  90. print(Term, Max) ->
  91. print(Term, Max, []).
  92. %% @doc Returns {List, Length}
  93. -spec print(term(), pos_integer(), options() | #print_options{}) -> {iolist(), pos_integer()}.
  94. print(Term, Max, Options) when is_list(Options) ->
  95. %% need to convert the proplist to a record
  96. print(Term, Max, prepare_options(Options, #print_options{}));
  97. print(Term, _Max, #print_options{force_strings=true}) when not is_list(Term), not is_binary(Term), not is_atom(Term) ->
  98. erlang:error(badarg);
  99. print(_, Max, _Options) when Max < 0 -> {"...", 3};
  100. print(_, _, #print_options{depth=0}) -> {"...", 3};
  101. %% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need
  102. %% to be truncated. This isn't strictly true, someone could make an
  103. %% arbitrarily long bignum. Let's assume that won't happen unless someone
  104. %% is being malicious.
  105. %%
  106. print(Atom, _Max, #print_options{force_strings=NoQuote}) when is_atom(Atom) ->
  107. L = atom_to_list(Atom),
  108. R = case atom_needs_quoting_start(L) andalso not NoQuote of
  109. true -> lists:flatten([$', L, $']);
  110. false -> L
  111. end,
  112. {R, length(R)};
  113. print(<<>>, _Max, #print_options{depth=1}) ->
  114. {"<<>>", 4};
  115. print(Bin, _Max, #print_options{depth=1}) when is_binary(Bin) ->
  116. {"<<...>>", 7};
  117. print(<<>>, _Max, Options) ->
  118. case Options#print_options.force_strings of
  119. true ->
  120. {"", 0};
  121. false ->
  122. {"<<>>", 4}
  123. end;
  124. print(Binary, 0, _Options) when is_bitstring(Binary) ->
  125. {"<<..>>", 6};
  126. print(Bin, Max, _Options) when is_binary(Bin), Max < 2 ->
  127. {"<<...>>", 7};
  128. print(Binary, Max, Options) when is_binary(Binary) ->
  129. B = binary_to_list(Binary, 1, lists:min([Max, byte_size(Binary)])),
  130. {Res, Length} = case Options#print_options.lists_as_strings orelse
  131. Options#print_options.force_strings of
  132. true ->
  133. Depth = Options#print_options.depth,
  134. MaxSize = (Depth - 1) * 4,
  135. %% check if we need to truncate based on depth
  136. In = case Depth > -1 andalso MaxSize < length(B) andalso
  137. not Options#print_options.force_strings of
  138. true ->
  139. string:substr(B, 1, MaxSize);
  140. false -> B
  141. end,
  142. MaxLen = case Options#print_options.force_strings of
  143. true ->
  144. Max;
  145. false ->
  146. %% make room for the leading doublequote
  147. Max - 1
  148. end,
  149. try alist(In, MaxLen, Options) of
  150. {L0, Len0} ->
  151. case Options#print_options.force_strings of
  152. false ->
  153. case B /= In of
  154. true ->
  155. {[$", L0, "..."], Len0+4};
  156. false ->
  157. {[$"|L0], Len0+1}
  158. end;
  159. true ->
  160. {L0, Len0}
  161. end
  162. catch
  163. throw:{unprintable, C} ->
  164. Index = string:chr(In, C),
  165. case Index > 1 andalso Options#print_options.depth =< Index andalso
  166. Options#print_options.depth > -1 andalso
  167. not Options#print_options.force_strings of
  168. true ->
  169. %% print first Index-1 characters followed by ...
  170. {L0, Len0} = alist_start(string:substr(In, 1, Index - 1), Max - 1, Options),
  171. {L0++"...", Len0+3};
  172. false ->
  173. list_body(In, Max-4, dec_depth(Options), true)
  174. end
  175. end;
  176. _ ->
  177. list_body(B, Max-4, dec_depth(Options), true)
  178. end,
  179. case Options#print_options.force_strings of
  180. true ->
  181. {Res, Length};
  182. _ ->
  183. {["<<", Res, ">>"], Length+4}
  184. end;
  185. %% bitstrings are binary's evil brother who doesn't end on an 8 bit boundary.
  186. %% This makes printing them extremely annoying, so list_body/list_bodyc has
  187. %% some magic for dealing with the output of bitstring_to_list, which returns
  188. %% a list of integers (as expected) but with a trailing binary that represents
  189. %% the remaining bits.
  190. print({inline_bitstring, B}, _Max, _Options) when is_bitstring(B) ->
  191. Size = bit_size(B),
  192. <<Value:Size>> = B,
  193. ValueStr = integer_to_list(Value),
  194. SizeStr = integer_to_list(Size),
  195. {[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +1};
  196. print(BitString, Max, Options) when is_bitstring(BitString) ->
  197. BL = case byte_size(BitString) > Max of
  198. true ->
  199. binary_to_list(BitString, 1, Max);
  200. _ ->
  201. R = erlang:bitstring_to_list(BitString),
  202. {Bytes, [Bits]} = lists:splitwith(fun erlang:is_integer/1, R),
  203. %% tag the trailing bits with a special tuple we catch when
  204. %% list_body calls print again
  205. Bytes ++ [{inline_bitstring, Bits}]
  206. end,
  207. {X, Len0} = list_body(BL, Max - 4, dec_depth(Options), true),
  208. {["<<", X, ">>"], Len0 + 4};
  209. print(Float, _Max, _Options) when is_float(Float) ->
  210. %% use the same function io_lib:format uses to print floats
  211. %% float_to_list is way too verbose.
  212. L = io_lib_format:fwrite_g(Float),
  213. {L, length(L)};
  214. print(Fun, Max, _Options) when is_function(Fun) ->
  215. L = erlang:fun_to_list(Fun),
  216. case length(L) > Max of
  217. true ->
  218. S = erlang:max(5, Max),
  219. Res = string:substr(L, 1, S) ++ "..>",
  220. {Res, length(Res)};
  221. _ ->
  222. {L, length(L)}
  223. end;
  224. print(Integer, _Max, _Options) when is_integer(Integer) ->
  225. L = integer_to_list(Integer),
  226. {L, length(L)};
  227. print(Pid, _Max, _Options) when is_pid(Pid) ->
  228. L = pid_to_list(Pid),
  229. {L, length(L)};
  230. print(Ref, _Max, _Options) when is_reference(Ref) ->
  231. L = erlang:ref_to_list(Ref),
  232. {L, length(L)};
  233. print(Port, _Max, _Options) when is_port(Port) ->
  234. L = erlang:port_to_list(Port),
  235. {L, length(L)};
  236. print({'$lager_record', Name, Fields}, Max, Options) ->
  237. Leader = "#" ++ atom_to_list(Name) ++ "{",
  238. {RC, Len} = record_fields(Fields, Max - length(Leader) + 1, dec_depth(Options)),
  239. {[Leader, RC, "}"], Len + length(Leader) + 1};
  240. print(Tuple, Max, Options) when is_tuple(Tuple) ->
  241. {TC, Len} = tuple_contents(Tuple, Max-2, Options),
  242. {[${, TC, $}], Len + 2};
  243. print(List, Max, Options) when is_list(List) ->
  244. case Options#print_options.lists_as_strings orelse
  245. Options#print_options.force_strings of
  246. true ->
  247. alist_start(List, Max, dec_depth(Options));
  248. _ ->
  249. {R, Len} = list_body(List, Max - 2, dec_depth(Options), false),
  250. {[$[, R, $]], Len + 2}
  251. end;
  252. print(Map, Max, Options) ->
  253. case erlang:is_builtin(erlang, is_map, 1) andalso erlang:is_map(Map) of
  254. true ->
  255. {MapBody, Len} = map_body(Map, Max - 3, dec_depth(Options)),
  256. {[$#, ${, MapBody, $}], Len + 3};
  257. false ->
  258. error(badarg, [Map, Max, Options])
  259. end.
  260. %% Returns {List, Length}
  261. tuple_contents(Tuple, Max, Options) ->
  262. L = tuple_to_list(Tuple),
  263. list_body(L, Max, dec_depth(Options), true).
  264. %% Format the inside of a list, i.e. do not add a leading [ or trailing ].
  265. %% Returns {List, Length}
  266. list_body([], _Max, _Options, _Tuple) -> {[], 0};
  267. list_body(_, Max, _Options, _Tuple) when Max < 4 -> {"...", 3};
  268. list_body(_, _Max, #print_options{depth=0}, _Tuple) -> {"...", 3};
  269. list_body([H], Max, Options=#print_options{depth=1}, _Tuple) ->
  270. print(H, Max, Options);
  271. list_body([H|_], Max, Options=#print_options{depth=1}, Tuple) ->
  272. {List, Len} = print(H, Max-4, Options),
  273. Sep = case Tuple of
  274. true -> $,;
  275. false -> $|
  276. end,
  277. {[List ++ [Sep | "..."]], Len + 4};
  278. list_body([H|T], Max, Options, Tuple) ->
  279. {List, Len} = print(H, Max, Options),
  280. {Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple),
  281. {[List|Final], FLen + Len};
  282. list_body(X, Max, Options, _Tuple) -> %% improper list
  283. {List, Len} = print(X, Max - 1, Options),
  284. {[$|,List], Len + 1}.
  285. list_bodyc([], _Max, _Options, _Tuple) -> {[], 0};
  286. list_bodyc(_, Max, _Options, _Tuple) when Max < 5 -> {",...", 4};
  287. list_bodyc(_, _Max, #print_options{depth=1}, true) -> {",...", 4};
  288. list_bodyc(_, _Max, #print_options{depth=1}, false) -> {"|...", 4};
  289. list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) ->
  290. {List, Len} = print(H, Max, dec_depth(Options)),
  291. {Final, FLen} = list_bodyc(T, Max - Len - 1, dec_depth(Options), Tuple),
  292. Sep = case Depth == 1 andalso not Tuple of
  293. true -> $|;
  294. _ -> $,
  295. end,
  296. {[Sep, List|Final], FLen + Len + 1};
  297. list_bodyc(X, Max, Options, _Tuple) -> %% improper list
  298. {List, Len} = print(X, Max - 1, Options),
  299. {[$|,List], Len + 1}.
  300. map_body(Map, Max, #print_options{depth=Depth}) when Max < 4; Depth =:= 0 ->
  301. case erlang:map_size(Map) of
  302. 0 -> {[], 0};
  303. _ -> {"...", 3}
  304. end;
  305. map_body(Map, Max, Options) ->
  306. case maps:to_list(Map) of
  307. [] ->
  308. {[], 0};
  309. [{Key, Value} | Rest] ->
  310. {KeyStr, KeyLen} = print(Key, Max - 4, Options),
  311. DiffLen = KeyLen + 4,
  312. {ValueStr, ValueLen} = print(Value, Max - DiffLen, Options),
  313. DiffLen2 = DiffLen + ValueLen,
  314. {Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)),
  315. {[KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen}
  316. end.
  317. map_bodyc([], _Max, _Options) ->
  318. {[], 0};
  319. map_bodyc(_Rest, Max,#print_options{depth=Depth}) when Max < 5; Depth =:= 0 ->
  320. {",...", 4};
  321. map_bodyc([{Key, Value} | Rest], Max, Options) ->
  322. {KeyStr, KeyLen} = print(Key, Max - 5, Options),
  323. DiffLen = KeyLen + 5,
  324. {ValueStr, ValueLen} = print(Value, Max - DiffLen, Options),
  325. DiffLen2 = DiffLen + ValueLen,
  326. {Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)),
  327. {[$,, KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen}.
  328. %% The head of a list we hope is ascii. Examples:
  329. %%
  330. %% [65,66,67] -> "ABC"
  331. %% [65,0,67] -> "A"[0,67]
  332. %% [0,65,66] -> [0,65,66]
  333. %% [65,b,66] -> "A"[b,66]
  334. %%
  335. alist_start([], _Max, #print_options{force_strings=true}) -> {"", 0};
  336. alist_start([], _Max, _Options) -> {"[]", 2};
  337. alist_start(_, Max, _Options) when Max < 4 -> {"...", 3};
  338. alist_start(_, _Max, #print_options{depth=0}) -> {"[...]", 5};
  339. alist_start(L, Max, #print_options{force_strings=true} = Options) ->
  340. alist(L, Max, Options);
  341. %alist_start([H|_T], _Max, #print_options{depth=1}) when is_integer(H) -> {[$[, H, $|, $., $., $., $]], 7};
  342. alist_start([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
  343. try alist([H|T], Max -1, Options) of
  344. {L, Len} ->
  345. {[$"|L], Len + 1}
  346. catch
  347. throw:{unprintable, _} ->
  348. {R, Len} = list_body([H|T], Max-2, Options, false),
  349. {[$[, R, $]], Len + 2}
  350. end;
  351. alist_start([H|T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable
  352. try alist([H|T], Max -1, Options) of
  353. {L, Len} ->
  354. {[$"|L], Len + 1}
  355. catch
  356. throw:{unprintable, _} ->
  357. {R, Len} = list_body([H|T], Max-2, Options, false),
  358. {[$[, R, $]], Len + 2}
  359. end;
  360. alist_start([H|T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H=:= $\f; H=:= $\b ->
  361. try alist([H|T], Max -1, Options) of
  362. {L, Len} ->
  363. {[$"|L], Len + 1}
  364. catch
  365. throw:{unprintable, _} ->
  366. {R, Len} = list_body([H|T], Max-2, Options, false),
  367. {[$[, R, $]], Len + 2}
  368. end;
  369. alist_start(L, Max, Options) ->
  370. {R, Len} = list_body(L, Max-2, Options, false),
  371. {[$[, R, $]], Len + 2}.
  372. alist([], _Max, #print_options{force_strings=true}) -> {"", 0};
  373. alist([], _Max, _Options) -> {"\"", 1};
  374. alist(_, Max, #print_options{force_strings=true}) when Max < 4 -> {"...", 3};
  375. alist(_, Max, #print_options{force_strings=false}) when Max < 5 -> {"...\"", 4};
  376. alist([H|T], Max, Options = #print_options{force_strings=false,lists_as_strings=true}) when H =:= $"; H =:= $\\ ->
  377. %% preserve escaping around quotes
  378. {L, Len} = alist(T, Max-1, Options),
  379. {[$\\,H|L], Len + 2};
  380. alist([H|T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable
  381. {L, Len} = alist(T, Max-1, Options),
  382. {[H|L], Len + 1};
  383. alist([H|T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable
  384. {L, Len} = alist(T, Max-1, Options),
  385. {[H|L], Len + 1};
  386. alist([H|T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H=:= $\f; H=:= $\b ->
  387. {L, Len} = alist(T, Max-1, Options),
  388. case Options#print_options.force_strings of
  389. true ->
  390. {[H|L], Len + 1};
  391. _ ->
  392. {[escape(H)|L], Len + 1}
  393. end;
  394. alist([H|T], Max, #print_options{force_strings=true} = Options) when is_integer(H) ->
  395. {L, Len} = alist(T, Max-1, Options),
  396. {[H|L], Len + 1};
  397. alist([H|T], Max, Options = #print_options{force_strings=true}) when is_binary(H); is_list(H) ->
  398. {List, Len} = print(H, Max, Options),
  399. case (Max - Len) =< 0 of
  400. true ->
  401. %% no more room to print anything
  402. {List, Len};
  403. false ->
  404. %% no need to decrement depth, as we're in printable string mode
  405. {Final, FLen} = alist(T, Max - Len, Options),
  406. {[List|Final], FLen+Len}
  407. end;
  408. alist(_, _, #print_options{force_strings=true}) ->
  409. erlang:error(badarg);
  410. alist([H|_L], _Max, _Options) ->
  411. throw({unprintable, H});
  412. alist(H, _Max, _Options) ->
  413. %% improper list
  414. throw({unprintable, H}).
  415. %% is the first character in the atom alphabetic & lowercase?
  416. atom_needs_quoting_start([H|T]) when H >= $a, H =< $z ->
  417. atom_needs_quoting(T);
  418. atom_needs_quoting_start(_) ->
  419. true.
  420. atom_needs_quoting([]) ->
  421. false;
  422. atom_needs_quoting([H|T]) when (H >= $a andalso H =< $z);
  423. (H >= $A andalso H =< $Z);
  424. (H >= $0 andalso H =< $9);
  425. H == $@; H == $_ ->
  426. atom_needs_quoting(T);
  427. atom_needs_quoting(_) ->
  428. true.
  429. -spec prepare_options(options(), #print_options{}) -> #print_options{}.
  430. prepare_options([], Options) ->
  431. Options;
  432. prepare_options([{depth, Depth}|T], Options) when is_integer(Depth) ->
  433. prepare_options(T, Options#print_options{depth=Depth});
  434. prepare_options([{lists_as_strings, Bool}|T], Options) when is_boolean(Bool) ->
  435. prepare_options(T, Options#print_options{lists_as_strings = Bool});
  436. prepare_options([{force_strings, Bool}|T], Options) when is_boolean(Bool) ->
  437. prepare_options(T, Options#print_options{force_strings = Bool}).
  438. dec_depth(#print_options{depth=Depth} = Options) when Depth > 0 ->
  439. Options#print_options{depth=Depth-1};
  440. dec_depth(Options) ->
  441. Options.
  442. escape($\t) -> "\\t";
  443. escape($\n) -> "\\n";
  444. escape($\r) -> "\\r";
  445. escape($\e) -> "\\e";
  446. escape($\f) -> "\\f";
  447. escape($\b) -> "\\b";
  448. escape($\v) -> "\\v".
  449. record_fields([], _, _) ->
  450. {"", 0};
  451. record_fields(_, Max, #print_options{depth=D}) when Max < 4; D == 0 ->
  452. {"...", 3};
  453. record_fields([{Field, Value}|T], Max, Options) ->
  454. {ExtraChars, Terminator} = case T of
  455. [] ->
  456. {1, []};
  457. _ ->
  458. {2, ","}
  459. end,
  460. {FieldStr, FieldLen} = print(Field, Max - ExtraChars, Options),
  461. {ValueStr, ValueLen} = print(Value, Max - (FieldLen + ExtraChars), Options),
  462. {Final, FLen} = record_fields(T, Max - (FieldLen + ValueLen + ExtraChars), dec_depth(Options)),
  463. {[FieldStr++"="++ValueStr++Terminator|Final], FLen + FieldLen + ValueLen + ExtraChars}.
  464. -ifdef(TEST).
  465. %%--------------------
  466. %% The start of a test suite. So far, it only checks for not crashing.
  467. -spec test() -> ok.
  468. test() ->
  469. test(trunc_io, print).
  470. -spec test(atom(), atom()) -> ok.
  471. test(Mod, Func) ->
  472. Simple_items = [atom, 1234, 1234.0, {tuple}, [], [list], "string", self(),
  473. <<1,2,3>>, make_ref(), fun() -> ok end],
  474. F = fun(A) ->
  475. Mod:Func(A, 100),
  476. Mod:Func(A, 2),
  477. Mod:Func(A, 20)
  478. end,
  479. G = fun(A) ->
  480. case catch F(A) of
  481. {'EXIT', _} -> exit({failed, A});
  482. _ -> ok
  483. end
  484. end,
  485. lists:foreach(G, Simple_items),
  486. Tuples = [ {1,2,3,a,b,c}, {"abc", def, 1234},
  487. {{{{a},b,c,{d},e}},f}],
  488. Lists = [ [1,2,3,4,5,6,7], lists:seq(1,1000),
  489. [{a}, {a,b}, {a, [b,c]}, "def"], [a|b], [$a|$b] ],
  490. lists:foreach(G, Tuples),
  491. lists:foreach(G, Lists).
  492. -spec perf() -> ok.
  493. perf() ->
  494. {New, _} = timer:tc(trunc_io, perf, [trunc_io, print, 1000]),
  495. {Old, _} = timer:tc(trunc_io, perf, [io_lib, write, 1000]),
  496. io:fwrite("New code took ~p us, old code ~p\n", [New, Old]).
  497. -spec perf(atom(), atom(), integer()) -> done.
  498. perf(M, F, Reps) when Reps > 0 ->
  499. test(M,F),
  500. perf(M,F,Reps-1);
  501. perf(_,_,_) ->
  502. done.
  503. %% Performance test. Needs a particularly large term I saved as a binary...
  504. -spec perf1() -> {non_neg_integer(), non_neg_integer()}.
  505. perf1() ->
  506. {ok, Bin} = file:read_file("bin"),
  507. A = binary_to_term(Bin),
  508. {N, _} = timer:tc(trunc_io, print, [A, 1500]),
  509. {M, _} = timer:tc(io_lib, write, [A]),
  510. {N, M}.
  511. format_test() ->
  512. %% simple format strings
  513. ?assertEqual("foobar", lists:flatten(format("~s", [["foo", $b, $a, $r]], 50))),
  514. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~p", [["foo", $b, $a, $r]], 50))),
  515. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~P", [["foo", $b, $a, $r], 10], 50))),
  516. ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~w", [["foo", $b, $a, $r]], 50))),
  517. %% complex ones
  518. ?assertEqual(" foobar", lists:flatten(format("~10s", [["foo", $b, $a, $r]], 50))),
  519. ?assertEqual("f", lists:flatten(format("~1s", [["foo", $b, $a, $r]], 50))),
  520. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22p", [["foo", $b, $a, $r]], 50))),
  521. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22P", [["foo", $b, $a, $r], 10], 50))),
  522. ?assertEqual("**********", lists:flatten(format("~10W", [["foo", $b, $a, $r], 10], 50))),
  523. ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~25W", [["foo", $b, $a, $r], 10], 50))),
  524. % Note these next two diverge from io_lib:format; the field width is
  525. % ignored, when it should be used as max line length.
  526. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10p", [["foo", $b, $a, $r]], 50))),
  527. ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10P", [["foo", $b, $a, $r], 10], 50))),
  528. ok.
  529. atom_quoting_test() ->
  530. ?assertEqual("hello", lists:flatten(format("~p", [hello], 50))),
  531. ?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))),
  532. ?assertEqual("'Hello world'", lists:flatten(format("~p", ['Hello world'], 50))),
  533. ?assertEqual("hello_world", lists:flatten(format("~p", ['hello_world'], 50))),
  534. ?assertEqual("'node@127.0.0.1'", lists:flatten(format("~p", ['node@127.0.0.1'], 50))),
  535. ?assertEqual("node@nohost", lists:flatten(format("~p", [node@nohost], 50))),
  536. ?assertEqual("abc123", lists:flatten(format("~p", [abc123], 50))),
  537. ok.
  538. sane_float_printing_test() ->
  539. ?assertEqual("1.0", lists:flatten(format("~p", [1.0], 50))),
  540. ?assertEqual("1.23456789", lists:flatten(format("~p", [1.23456789], 50))),
  541. ?assertEqual("1.23456789", lists:flatten(format("~p", [1.234567890], 50))),
  542. ?assertEqual("0.3333333333333333", lists:flatten(format("~p", [1/3], 50))),
  543. ?assertEqual("0.1234567", lists:flatten(format("~p", [0.1234567], 50))),
  544. ok.
  545. float_inside_list_test() ->
  546. ?assertEqual("[97,38.233913133184835,99]", lists:flatten(format("~p", [[$a, 38.233913133184835, $c]], 50))),
  547. ?assertError(badarg, lists:flatten(format("~s", [[$a, 38.233913133184835, $c]], 50))),
  548. ok.
  549. quote_strip_test() ->
  550. ?assertEqual("\"hello\"", lists:flatten(format("~p", ["hello"], 50))),
  551. ?assertEqual("hello", lists:flatten(format("~s", ["hello"], 50))),
  552. ?assertEqual("hello", lists:flatten(format("~s", [hello], 50))),
  553. ?assertEqual("hello", lists:flatten(format("~p", [hello], 50))),
  554. ?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))),
  555. ?assertEqual("hello world", lists:flatten(format("~s", ['hello world'], 50))),
  556. ok.
  557. binary_printing_test() ->
  558. ?assertEqual("<<>>", lists:flatten(format("~p", [<<>>], 50))),
  559. ?assertEqual("", lists:flatten(format("~s", [<<>>], 50))),
  560. ?assertEqual("<<..>>", lists:flatten(format("~p", [<<"hi">>], 0))),
  561. ?assertEqual("<<...>>", lists:flatten(format("~p", [<<"hi">>], 1))),
  562. ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<$h, $e, $l, $l, $o>>], 50))),
  563. ?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<"hello">>], 50))),
  564. ?assertEqual("<<104,101,108,108,111>>", lists:flatten(format("~w", [<<"hello">>], 50))),
  565. ?assertEqual("<<1,2,3,4>>", lists:flatten(format("~p", [<<1, 2, 3, 4>>], 50))),
  566. ?assertEqual([1,2,3,4], lists:flatten(format("~s", [<<1, 2, 3, 4>>], 50))),
  567. ?assertEqual("hello", lists:flatten(format("~s", [<<"hello">>], 50))),
  568. ?assertEqual("hello\nworld", lists:flatten(format("~s", [<<"hello\nworld">>], 50))),
  569. ?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))),
  570. ?assertEqual("<<\"\\\"hello world\\\"\">>", lists:flatten(format("~p", [<<"\"hello world\"">>], 50))),
  571. ?assertEqual("<<\"hello\\\\world\">>", lists:flatten(format("~p", [<<"hello\\world">>], 50))),
  572. ?assertEqual("<<\"hello\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\world">>], 50))),
  573. ?assertEqual("<<\"hello\\\\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\\world">>], 50))),
  574. ?assertEqual("<<\"hello\\bworld\">>", lists:flatten(format("~p", [<<"hello\bworld">>], 50))),
  575. ?assertEqual("<<\"hello\\tworld\">>", lists:flatten(format("~p", [<<"hello\tworld">>], 50))),
  576. ?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))),
  577. ?assertEqual("<<\"hello\\rworld\">>", lists:flatten(format("~p", [<<"hello\rworld">>], 50))),
  578. ?assertEqual("<<\"hello\\eworld\">>", lists:flatten(format("~p", [<<"hello\eworld">>], 50))),
  579. ?assertEqual("<<\"hello\\fworld\">>", lists:flatten(format("~p", [<<"hello\fworld">>], 50))),
  580. ?assertEqual("<<\"hello\\vworld\">>", lists:flatten(format("~p", [<<"hello\vworld">>], 50))),
  581. ?assertEqual(" hello", lists:flatten(format("~10s", [<<"hello">>], 50))),
  582. ?assertEqual("[a]", lists:flatten(format("~s", [<<"[a]">>], 50))),
  583. ?assertEqual("[a]", lists:flatten(format("~s", [[<<"[a]">>]], 50))),
  584. ok.
  585. bitstring_printing_test() ->
  586. ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
  587. [<<1, 2, 3, 1:7>>], 100))),
  588. ?assertEqual("<<1:7>>", lists:flatten(format("~p",
  589. [<<1:7>>], 100))),
  590. ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
  591. [<<1, 2, 3, 1:7>>], 12))),
  592. ?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p",
  593. [<<1, 2, 3, 1:7>>], 13))),
  594. ?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p",
  595. [<<1, 2, 3, 1:7>>], 14))),
  596. ?assertEqual("<<..>>", lists:flatten(format("~p", [<<1:7>>], 0))),
  597. ?assertEqual("<<...>>", lists:flatten(format("~p", [<<1:7>>], 1))),
  598. ?assertEqual("[<<1>>,<<2>>]", lists:flatten(format("~p", [[<<1>>, <<2>>]],
  599. 100))),
  600. ?assertEqual("{<<1:7>>}", lists:flatten(format("~p", [{<<1:7>>}], 50))),
  601. ok.
  602. list_printing_test() ->
  603. ?assertEqual("[]", lists:flatten(format("~p", [[]], 50))),
  604. ?assertEqual("[]", lists:flatten(format("~w", [[]], 50))),
  605. ?assertEqual("", lists:flatten(format("~s", [[]], 50))),
  606. ?assertEqual("...", lists:flatten(format("~s", [[]], -1))),
  607. ?assertEqual("[[]]", lists:flatten(format("~p", [[[]]], 50))),
  608. ?assertEqual("[13,11,10,8,5,4]", lists:flatten(format("~p", [[13,11,10,8,5,4]], 50))),
  609. ?assertEqual("\"\\rabc\"", lists:flatten(format("~p", [[13,$a, $b, $c]], 50))),
  610. ?assertEqual("[1,2,3|4]", lists:flatten(format("~p", [[1, 2, 3|4]], 50))),
  611. ?assertEqual("[...]", lists:flatten(format("~p", [[1, 2, 3,4]], 4))),
  612. ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 6))),
  613. ?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 7))),
  614. ?assertEqual("[1,2,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 8))),
  615. ?assertEqual("[1|4]", lists:flatten(format("~p", [[1|4]], 50))),
  616. ?assertEqual("[1]", lists:flatten(format("~p", [[1]], 50))),
  617. ?assertError(badarg, lists:flatten(format("~s", [[1|4]], 50))),
  618. ?assertEqual("\"hello...\"", lists:flatten(format("~p", ["hello world"], 10))),
  619. ?assertEqual("hello w...", lists:flatten(format("~s", ["hello world"], 10))),
  620. ?assertEqual("hello world\r\n", lists:flatten(format("~s", ["hello world\r\n"], 50))),
  621. ?assertEqual("\rhello world\r\n", lists:flatten(format("~s", ["\rhello world\r\n"], 50))),
  622. ?assertEqual("\"\\rhello world\\r\\n\"", lists:flatten(format("~p", ["\rhello world\r\n"], 50))),
  623. ?assertEqual("[13,104,101,108,108,111,32,119,111,114,108,100,13,10]", lists:flatten(format("~w", ["\rhello world\r\n"], 60))),
  624. ?assertEqual("...", lists:flatten(format("~s", ["\rhello world\r\n"], 3))),
  625. ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
  626. lists:flatten(format("~p", [
  627. [22835963083295358096932575511191922182123945984,
  628. 22835963083295358096932575511191922182123945984]], 9))),
  629. ?assertEqual("[22835963083295358096932575511191922182123945984,...]",
  630. lists:flatten(format("~p", [
  631. [22835963083295358096932575511191922182123945984,
  632. 22835963083295358096932575511191922182123945984]], 53))),
  633. %%improper list
  634. ?assertEqual("[1,2,3|4]", lists:flatten(format("~P", [[1|[2|[3|4]]], 5], 50))),
  635. ?assertEqual("[1|1]", lists:flatten(format("~P", [[1|1], 5], 50))),
  636. ?assertEqual("[9|9]", lists:flatten(format("~p", [[9|9]], 50))),
  637. ok.
  638. iolist_printing_test() ->
  639. ?assertEqual("iolist: HelloIamaniolist",
  640. lists:flatten(format("iolist: ~s", [[$H, $e, $l, $l, $o, "I", ["am", [<<"an">>], [$i, $o, $l, $i, $s, $t]]]], 1000))),
  641. ?assertEqual("123...",
  642. lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 6))),
  643. ?assertEqual("123456...",
  644. lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 9))),
  645. ?assertEqual("123456789H...",
  646. lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 13))),
  647. ?assertEqual("123456789HellIamaniolist",
  648. lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 30))),
  649. ok.
  650. tuple_printing_test() ->
  651. ?assertEqual("{}", lists:flatten(format("~p", [{}], 50))),
  652. ?assertEqual("{}", lists:flatten(format("~w", [{}], 50))),
  653. ?assertError(badarg, lists:flatten(format("~s", [{}], 50))),
  654. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 1))),
  655. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 2))),
  656. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 3))),
  657. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 4))),
  658. ?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 5))),
  659. ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 6))),
  660. ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 7))),
  661. ?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo,bar}], 9))),
  662. ?assertEqual("{foo,bar}", lists:flatten(format("~p", [{foo,bar}], 10))),
  663. ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
  664. lists:flatten(format("~w", [
  665. {22835963083295358096932575511191922182123945984,
  666. 22835963083295358096932575511191922182123945984}], 10))),
  667. ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
  668. lists:flatten(format("~w", [
  669. {22835963083295358096932575511191922182123945984,
  670. bar}], 10))),
  671. ?assertEqual("{22835963083295358096932575511191922182123945984,...}",
  672. lists:flatten(format("~w", [
  673. {22835963083295358096932575511191922182123945984,
  674. 22835963083295358096932575511191922182123945984}], 53))),
  675. ok.
  676. map_printing_test() ->
  677. case erlang:is_builtin(erlang, is_map, 1) of
  678. true ->
  679. ?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 50))),
  680. ?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 3))),
  681. ?assertEqual("#{}", lists:flatten(format("~w", [maps:new()], 50))),
  682. ?assertError(badarg, lists:flatten(format("~s", [maps:new()], 50))),
  683. ?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 1))),
  684. ?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 6))),
  685. ?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 7))),
  686. ?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 9))),
  687. ?assertEqual("#{bar => foo}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 10))),
  688. ?assertEqual("#{bar => ...,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 9))),
  689. ?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 10))),
  690. ?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 17))),
  691. ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 18))),
  692. ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 19))),
  693. ?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 20))),
  694. ?assertEqual("#{bar => foo,foo => bar}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 21))),
  695. ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}",
  696. lists:flatten(format("~w", [
  697. maps:from_list([{22835963083295358096932575511191922182123945984,
  698. 22835963083295358096932575511191922182123945984}])], 10))),
  699. ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}",
  700. lists:flatten(format("~w", [
  701. maps:from_list([{22835963083295358096932575511191922182123945984,
  702. bar}])], 10))),
  703. ?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}",
  704. lists:flatten(format("~w", [
  705. maps:from_list([{22835963083295358096932575511191922182123945984,
  706. bar}])], 53))),
  707. ?assertEqual("#{22835963083295358096932575511191922182123945984 => bar}",
  708. lists:flatten(format("~w", [
  709. maps:from_list([{22835963083295358096932575511191922182123945984,
  710. bar}])], 54))),
  711. ok;
  712. false ->
  713. ok
  714. end.
  715. unicode_test() ->
  716. ?assertEqual([231,167,129], lists:flatten(format("~s", [<<231,167,129>>], 50))),
  717. ?assertEqual([31169], lists:flatten(format("~ts", [<<231,167,129>>], 50))),
  718. ok.
  719. depth_limit_test() ->
  720. ?assertEqual("{...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 1], 50))),
  721. ?assertEqual("{a,...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 2], 50))),
  722. ?assertEqual("{a,[...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 3], 50))),
  723. ?assertEqual("{a,[b|...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 4], 50))),
  724. ?assertEqual("{a,[b,[...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 5], 50))),
  725. ?assertEqual("{a,[b,[c|...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 6], 50))),
  726. ?assertEqual("{a,[b,[c,[...]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 7], 50))),
  727. ?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 8], 50))),
  728. ?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 9], 50))),
  729. ?assertEqual("{a,{...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 3], 50))),
  730. ?assertEqual("{a,{b,...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 4], 50))),
  731. ?assertEqual("{a,{b,{...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 5], 50))),
  732. ?assertEqual("{a,{b,{c,...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 6], 50))),
  733. ?assertEqual("{a,{b,{c,{...}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 7], 50))),
  734. ?assertEqual("{a,{b,{c,{d}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 8], 50))),
  735. case erlang:is_builtin(erlang, is_map, 1) of
  736. true ->
  737. ?assertEqual("#{a => #{...}}",
  738. lists:flatten(format("~P",
  739. [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 2], 50))),
  740. ?assertEqual("#{a => #{b => #{...}}}",
  741. lists:flatten(format("~P",
  742. [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 3], 50))),
  743. ?assertEqual("#{a => #{b => #{c => d}}}",
  744. lists:flatten(format("~P",
  745. [maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 4], 50))),
  746. ?assertEqual("#{}", lists:flatten(format("~P", [maps:new(), 1], 50))),
  747. ?assertEqual("#{...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 1], 50))),
  748. ?assertEqual("#{1 => 1,...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 2], 50))),
  749. ?assertEqual("#{1 => 1,2 => 2,...}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 3], 50))),
  750. ?assertEqual("#{1 => 1,2 => 2,3 => 3}", lists:flatten(format("~P", [maps:from_list([{1,1}, {2,2}, {3,3}]), 4], 50))),
  751. ok;
  752. false ->
  753. ok
  754. end,
  755. ?assertEqual("{\"a\",[...]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 3], 50))),
  756. ?assertEqual("{\"a\",[\"b\",[[...]|...]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 6], 50))),
  757. ?assertEqual("{\"a\",[\"b\",[\"c\",[\"d\"]]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 9], 50))),
  758. ?assertEqual("[...]", lists:flatten(format("~P", [[1, 2, 3], 1], 50))),
  759. ?assertEqual("[1|...]", lists:flatten(format("~P", [[1, 2, 3], 2], 50))),
  760. ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, 3], 3], 50))),
  761. ?assertEqual("[1,2,3]", lists:flatten(format("~P", [[1, 2, 3], 4], 50))),
  762. ?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))),
  763. ?assertEqual("{1,2,...}", lists:flatten(format("~P", [{1, 2, 3}, 3], 50))),
  764. ?assertEqual("{1,2,3}", lists:flatten(format("~P", [{1, 2, 3}, 4], 50))),
  765. ?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))),
  766. ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, <<3>>], 3], 50))),
  767. ?assertEqual("[1,2,<<...>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 4], 50))),
  768. ?assertEqual("[1,2,<<3>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 5], 50))),
  769. ?assertEqual("<<...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 1], 50))),
  770. ?assertEqual("<<0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 2], 50))),
  771. ?assertEqual("<<0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 3], 50))),
  772. ?assertEqual("<<0,0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 4], 50))),
  773. ?assertEqual("<<0,0,0,0>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 5], 50))),
  774. %% this is a seriously weird edge case
  775. ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 2], 50))),
  776. ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 3], 50))),
  777. ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 4], 50))),
  778. ?assertEqual("<<32,32,32,0>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 5], 50))),
  779. ?assertEqual("<<32,32,32,0>>", lists:flatten(format("~p", [<<32, 32, 32, 0>>], 50))),
  780. %% depth limiting for some reason works in 4 byte chunks on printable binaries?
  781. ?assertEqual("<<\"hell\"...>>", lists:flatten(format("~P", [<<"hello world">>, 2], 50))),
  782. ?assertEqual("<<\"abcd\"...>>", lists:flatten(format("~P", [<<$a, $b, $c, $d, $e, 0>>, 2], 50))),
  783. %% I don't even know...
  784. ?assertEqual("<<>>", lists:flatten(format("~P", [<<>>, 1], 50))),
  785. ?assertEqual("<<>>", lists:flatten(format("~W", [<<>>, 1], 50))),
  786. ?assertEqual("{abc,<<\"abc\\\"\">>}", lists:flatten(format("~P", [{abc,<<"abc\"">>}, 4], 50))),
  787. ok.
  788. print_terms_without_format_string_test() ->
  789. ?assertError(badarg, format({hello, world}, [], 50)),
  790. ?assertError(badarg, format([{google, bomb}], [], 50)),
  791. ?assertError(badarg, format([$h,$e,$l,$l,$o, 3594], [], 50)),
  792. ?assertEqual("helloworld", lists:flatten(format([$h,$e,$l,$l,$o, "world"], [], 50))),
  793. ?assertEqual("hello", lists:flatten(format(<<"hello">>, [], 50))),
  794. ?assertEqual("hello", lists:flatten(format('hello', [], 50))),
  795. ?assertError(badarg, format(<<1, 2, 3, 1:7>>, [], 100)),
  796. ?assertError(badarg, format(65535, [], 50)),
  797. ok.
  798. improper_io_list_test() ->
  799. ?assertEqual(">hello", lists:flatten(format('~s', [[$>|<<"hello">>]], 50))),
  800. ?assertEqual(">hello", lists:flatten(format('~ts', [[$>|<<"hello">>]], 50))),
  801. ?assertEqual("helloworld", lists:flatten(format('~ts', [[<<"hello">>|<<"world">>]], 50))),
  802. ok.
  803. -endif.