1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21-module(send_term_SUITE).
22
23-export([all/0, suite/0, basic/1]).
24
25-export([generate_external_terms_files/1]).
26
27-include_lib("common_test/include/ct.hrl").
28
29suite() ->
30    [{ct_hooks,[ts_install_cth]},
31     {timetrap, {minutes, 3}}].
32
33all() ->
34    [basic].
35
36basic(Config) when is_list(Config) ->
37    Drv = "send_term_drv",
38    P = start_driver(Config, Drv),
39
40    [] = term(P, 0),
41    Self = self(),
42    {blurf,42,[],[-42,{}|"abc"++P],"kalle",3.1416,Self,#{}} = term(P, 1),
43
44    Map41 = maps:from_list([{blurf, 42},
45			    {[], [-42,{}|"abc"++P]},
46			    {"kalle", 3.1416},
47			    {Self, #{}}]),
48    Map41 = term(P, 41),
49
50    Map42 = maps:from_list([{42, []},
51			    {[-42,{}|"abc"++P], "kalle"},
52			    {3.1416, Self},
53			    {#{}, blurf}]),
54    Map42 = term(P, 42),
55    Deep = lists:seq(0, 199),
56    Deep = term(P, 2),
57    {B1,B2} = term(P, 3),
58    B1 = list_to_binary(lists:seq(0, 255)),
59    B2 = list_to_binary(lists:seq(23, 255-17)),
60
61    %% Pid sending. We need another process.
62    Child = spawn_link(fun() ->
63				     erlang:port_command(P, [4])
64			     end),
65    {Self,Child} = receive_any(),
66
67    %% ERL_DRV_EXT2TERM
68    ExpectExt2Term = expected_ext2term_drv(proplists:get_value(data_dir, Config)),
69    ExpectExt2Term = term(P, 5),
70
71    %% ERL_DRV_INT, ERL_DRV_UINT
72    case erlang:system_info({wordsize, external}) of
73	      4 ->
74		  {-1, 4294967295} = term(P, 6);
75	      8 ->
76		  {-1, 18446744073709551615} = term(P, 6)
77	  end,
78
79    %% ERL_DRV_BUF2BINARY
80    ExpectedBinTup = {<<>>,
81			    <<>>,
82			    list_to_binary(lists:duplicate(17,17)),
83			    list_to_binary(lists:duplicate(1024,17))},
84    ExpectedBinTup = term(P, 7),
85
86    %% single terms
87    Singles = [{[], 8}, % ERL_DRV_NIL
88               {'', 9}, % ERL_DRV_ATOM
89               {an_atom, 10}, % ERL_DRV_ATOM
90               {-4711, 11}, % ERL_DRV_INT
91               {4711, 12}, % ERL_DRV_UINT
92               {P, 13}, % ERL_DRV_PORT
93               {<<>>, 14}, % ERL_DRV_BINARY
94               {<<"hejsan">>, 15}, % ERL_DRV_BINARY
95               {<<>>, 16}, % ERL_DRV_BUF2BINARY
96               {<<>>, 17}, % ERL_DRV_BUF2BINARY
97               {<<"hoppsan">>, 18}, % ERL_DRV_BUF2BINARY
98               {"", 19}, % ERL_DRV_STRING
99               {"", 20}, % ERL_DRV_STRING
100               {"hippsan", 21}, % ERL_DRV_STRING
101               {{}, 22}, % ERL_DRV_TUPLE
102               {[], 23}, % ERL_DRV_LIST
103               {Self, 24}, % ERL_DRV_PID
104               {[], 25}, % ERL_DRV_STRING_CONS
105               {[], 27}, % ERL_DRV_EXT2TERM
106               {18446744073709551615, 28}, % ERL_DRV_UINT64
107               {20233590931456, 29}, % ERL_DRV_UINT64
108               {4711, 30}, % ERL_DRV_UINT64
109               {0, 31}, % ERL_DRV_UINT64
110               {9223372036854775807, 32}, % ERL_DRV_INT64
111               {20233590931456, 33}, % ERL_DRV_INT64
112               {4711, 34}, % ERL_DRV_INT64
113               {0, 35}, % ERL_DRV_INT64
114               {-1, 36}, % ERL_DRV_INT64
115               {-4711, 37}, % ERL_DRV_INT64
116               {-20233590931456, 38}, % ERL_DRV_INT64
117               {-9223372036854775808, 39},
118	       {#{}, 40}], % ERL_DRV_MAP
119    {Terms, Ops} = lists:unzip(Singles),
120    Terms = term(P,Ops),
121
122    AFloat = term(P, 26), % ERL_DRV_FLOAT
123    true = AFloat < 0.001,
124    true = AFloat > -0.001,
125
126    %% Failure cases.
127    [] = term(P, 127),
128    receive
129	      Any ->
130		  ct:fail("Unexpected: ~p\n", [Any])
131	  after 0 ->
132		  ok
133	  end,
134
135    ok = chk_temp_alloc(),
136
137    %% In a private heap system, verify that there are no binaries
138    %% left for the process.
139    erlang:garbage_collect(),		%Get rid of binaries.
140    case erlang:system_info(heap_type) of
141	private ->
142	    {binary,[]} = process_info(self(), binary);
143	_ -> ok
144    end,
145
146    stop_driver(P, Drv),
147    ok.
148
149term(P, Op) ->
150    erlang:port_command(P, [Op]),
151    receive_any().
152
153receive_any() ->
154    receive
155	Any -> Any
156    end.
157
158chk_temp_alloc() ->
159    %% Verify that we haven't got any outstanding temp_alloc allocations.
160    case erts_debug:alloc_blocks_size(temp_alloc) of
161        undefined -> ok;
162        0 -> ok
163    end.
164
165%% Start/stop drivers.
166start_driver(Config, Name) ->
167    Path = proplists:get_value(data_dir, Config),
168    erl_ddll:start(),
169    ok = load_driver(Path, Name),
170    open_port({spawn, Name}, []).
171
172load_driver(Dir, Driver) ->
173    case erl_ddll:load_driver(Dir, Driver) of
174	ok -> ok;
175	{error, Error} = Res ->
176	    io:format("~s\n", [erl_ddll:format_error(Error)]),
177	    Res
178    end.
179
180stop_driver(Port, Name) ->
181    true = erlang:port_close(Port),
182    receive
183	{Port,Message} ->
184	    ct:fail({strange_message_from_port,Message})
185    after 0 ->
186	    ok
187    end,
188
189    %% Unload the driver.
190    ok = erl_ddll:unload_driver(Name),
191    ok = erl_ddll:stop().
192
193get_external_terms(DataDir) ->
194    {ok, Bin} = file:read_file([DataDir, "ext_terms.bin"]),
195    binary_to_term(Bin).
196
197expected_ext2term_drv(DataDir) ->
198    make_expected_ext2term_drv(get_external_terms(DataDir)).
199
200make_expected_ext2term_drv([]) ->
201    [];
202make_expected_ext2term_drv([T|Ts]) ->
203    [{T, T} | make_expected_ext2term_drv(Ts)].
204
205%%
206%% Generation of send_term_SUITE_data/ext_terms.h and
207%% send_term_SUITE_data/ext_terms.bin
208%%
209%% These files should normally not need to be regenerated,
210%% but we may want that if we introduce new types or make
211%% backward incompatible changes to the external format.
212%%
213
214generate_external_terms_files(BaseDir) ->
215    {ok,Node} = slave:start(hostname(), a_node),
216    RPid = rpc:call(Node, erlang, self, []),
217    true = is_pid(RPid),
218    RRef = rpc:call(Node, erlang, make_ref, []),
219    true = is_reference(RRef),
220    RPort = hd(rpc:call(Node, erlang, ports, [])),
221    true = is_port(RPort),
222    slave:stop(Node),
223    Terms = [{4711, -4711, [an_atom, "a list"]},
224             [1000000000000000000000,-1111111111111111, "blupp!", blipp],
225             {RPid, {RRef, RPort}, self(), hd(erlang:ports()), make_ref()},
226             {{}, [], [], fun () -> ok end, <<"hej hopp trallalaaaaaaaaaaaaaaa">>},
227             [44444444444444444444444,-44444444444, "b!", blippppppp],
228             {4711, RPid, {RRef, RPort}, -4711, [an_atom, "a list"]},
229             {RPid, {RRef, RPort}, hd(processes()), hd(erlang:ports())},
230             {4711, -4711, [an_atom, "a list"]},
231             {4711, -4711, [atom, "list"]},
232             {RPid, {RRef, RPort}, hd(processes()), hd(erlang:ports())},
233             {4444444444444444444,-44444, {{{{{{{{{{{{}}}}}}}}}}}}, make_ref()},
234             {444444444444444444444,-44444, [[[[[[[[[[[1]]]]]]]]]]], make_ref()},
235             {444444444444444444,-44444, {{{{{{{{{{{{2}}}}}}}}}}}}, make_ref()},
236             {4444444444444444444444,-44444, {{{{{{{{{{{{3}}}}}}}}}}}}, make_ref()},
237             {44444444444444444444,-44444, {{{{{{{{{{{{4}}}}}}}}}}}}, make_ref()},
238             {4444444444444444,-44444, [[[[[[[[[[[5]]]]]]]]]]], make_ref()},
239             {444444444444444444444,-44444, {{{{{{{{{{{{6}}}}}}}}}}}}, make_ref()},
240             {444444444444444,-44444, {{{{{{{{{{{{7}}}}}}}}}}}}, make_ref()},
241             {4444444444444444444,-44444, {{{{{{{{{{{{8}}}}}}}}}}}}, make_ref()},
242             #{},
243             #{1 => 11, 2 => 22, 3 => 33},
244             maps:from_list([{K,K*11} || K <- lists:seq(1,100)])],
245    ok = file:write_file(filename:join([BaseDir,
246                                        "send_term_SUITE_data",
247                                        "ext_terms.bin"]),
248                         term_to_binary(Terms, [compressed])),
249    {ok, IoDev} = file:open(filename:join([BaseDir,
250                                           "send_term_SUITE_data",
251                                           "ext_terms.h"]),
252                            [write]),
253    write_ext_terms_h(IoDev, Terms),
254    file:close(IoDev).
255
256write_ext_terms_h(IoDev, Terms) ->
257    write_license(IoDev),
258    io:format(IoDev, "#ifndef EXT_TERMS_H__~n",[]),
259    io:format(IoDev, "#define EXT_TERMS_H__~n",[]),
260    {ExtTerms, MaxSize} = make_ext_terms(Terms),
261    io:format(IoDev,
262              "static struct {~n"
263              "  unsigned char ext[~p];~n"
264              "  int ext_size;~n"
265              "  unsigned char cext[~p];~n"
266              "  int cext_size;~n"
267              "} ext_terms[] = {~n",[MaxSize, MaxSize]),
268    E = write_ext_terms_h(IoDev, ExtTerms, 0),
269    io:format(IoDev, "};~n",[]),
270    io:format(IoDev, "#define NO_OF_EXT_TERMS ~p~n", [E]),
271    io:format(IoDev, "#endif~n",[]).
272
273make_ext_terms([]) ->
274    {[], 0};
275make_ext_terms([T|Ts]) ->
276    E = term_to_binary(T),
277    ESz = size(E),
278    CE = term_to_binary(T, [compressed]),
279    CESz = size(CE),
280    true = CESz =< ESz, % Assertion
281    {ExtTerms, MaxSize} = make_ext_terms(Ts),
282    NewMaxSize = case MaxSize < ESz of
283		     true -> ESz;
284		     false -> MaxSize
285		 end,
286    {[{E, ESz, CE, CESz} | ExtTerms], NewMaxSize}.
287
288write_ext_terms_h(IoDev, [], N) ->
289    io:format(IoDev, "~n",[]),
290    N;
291write_ext_terms_h(IoDev, [ET|ETs], 0) ->
292    write_ext_term(IoDev, ET),
293    write_ext_terms_h(IoDev, ETs, 1);
294write_ext_terms_h(IoDev, [ET|ETs], N) ->
295    io:format(IoDev, ",~n",[]),
296    write_ext_term(IoDev, ET),
297    write_ext_terms_h(IoDev, ETs, N+1).
298
299write_ext_term(IoDev, {E, ESz, CE, CESz}) ->
300    ESz = write_bytes(IoDev, "  {{", binary_to_list(E), 0),
301    io:format(IoDev,
302	      ",~n"
303	      "   ~p,~n",
304	      [ESz]),
305    CESz = write_bytes(IoDev, "   {", binary_to_list(CE), 0),
306    io:format(IoDev,
307	      ",~n"
308	      "   ~p}",
309	      [CESz]).
310
311write_bytes(IoDev, _, [], N) ->
312    io:format(IoDev, "}",[]),
313    N;
314write_bytes(IoDev, Prefix, [B|Bs], N) ->
315    io:format(IoDev, "~s~w", [Prefix, B]),
316    write_bytes(IoDev, ",", Bs, N+1).
317
318write_license(IoDev) ->
319    S = "/* ``Licensed under the Apache License, Version 2.0 (the \"License\");~n"
320        " * you may not use this file except in compliance with the License.~n"
321        " * You may obtain a copy of the License at~n"
322        " * ~n"
323        " *     http://www.apache.org/licenses/LICENSE-2.0~n"
324        " * ~n"
325        " * Unless required by applicable law or agreed to in writing, software~n"
326        " * distributed under the License is distributed on an \"AS IS\" BASIS,~n"
327        " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.~n"
328        " * See the License for the specific language governing permissions and~n"
329        " * limitations under the License.~n"
330	" * ~n"
331	" * The Initial Developer of the Original Code is Ericsson AB.~n"
332	" * Portions created by Ericsson are Copyright 2007, Ericsson AB.~n"
333	" * All Rights Reserved.''~n"
334	" * ~n"
335	" *     $Id$~n"
336	" */~n"
337	"~n"
338	"/*~n"
339	" * Do not modify this file. This file and ext_terms.bin were~n"
340	" * automatically generated by send_term_SUITE:generate_external_terms_files/1~n"
341	" * and needs to be consistent with each other.~n"
342	" */~n",
343    io:format(IoDev, S, []).
344
345
346hostname() ->
347    hostname(atom_to_list(node())).
348
349hostname([$@ | Hostname]) ->
350    list_to_atom(Hostname);
351hostname([_C | Cs]) ->
352    hostname(Cs).
353