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