Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

891 Zeilen
41 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)
  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.