%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1999-2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

-module(multi_load_SUITE).
-export([all/0, suite/0, many/1, on_load/1, errors/1]).

-include_lib("common_test/include/ct.hrl").

suite() ->
    [{ct_hooks,[ts_install_cth]}].

all() ->
    [many,on_load,errors].

many(_Config) ->

    N = case erlang:system_info(build_type) of
            valgrind ->
                10;
            _ ->
                100
        end,

    Ms = make_modules(N, fun many_module/1),

    io:put_chars("Light load\n"
		 "=========="),
    many_measure(Ms),

    _ = [spawn_link(fun many_worker/0) || _ <- lists:seq(1, 8)],
    erlang:yield(),
    io:put_chars("Heavy load\n"
		 "=========="),
    many_measure(Ms),
    ok.

many_module(M) ->
    ["-module("++M++").",
     "-compile(export_all).",
     "f1() -> ok.",
     "f2() -> ok.",
     "f3() -> ok.",
     "f4() -> ok."].

many_measure(Ms) ->
    many_purge(Ms),
    MsPrep1 = prepare_modules(Ms),
    Us1 = ms(fun() -> many_load_seq(MsPrep1) end),
    many_try_call(Ms),
    many_purge(Ms),
    MsPrep2 = prepare_modules(Ms),
    Us2 = ms(fun() -> many_load_par(MsPrep2) end),
    many_try_call(Ms),
    io:format("# modules:  ~9w\n"
	      "Sequential: ~9w µs\n"
	      "Parallel:   ~9w µs\n"
	      "Ratio:      ~9w\n",
	      [length(Ms),Us1,Us2,divide(Us1,Us2)]),
    ok.

divide(A,B) when B > 0 -> A div B;
divide(_,_) -> inf.

many_load_seq(Ms) ->
    [erlang:finish_loading([M]) || M <- Ms],
    ok.

many_load_par(Ms) ->
    erlang:finish_loading(Ms).

many_purge(Ms) ->
    _ = [catch erlang:purge_module(M) || {M,_} <- Ms],
    ok.

many_try_call(Ms) ->
    _ = [begin
	     ok = M:f1(),
	     ok = M:f2(),
	     ok = M:f3(),
	     ok = M:f4()
	 end || {M,_} <- Ms],
    ok.

many_worker() ->
    many_worker(lists:seq(1, 100)).

many_worker(L) ->
    N0 = length(L),
    N1 = N0 * N0 * N0,
    N2 = N1 div (N0 * N0),
    N3 = N2 + 10,
    _ = N3 - 10,
    many_worker(L).


on_load(_Config) ->
    On = make_modules(2, fun on_load_module/1),
    OnPrep = prepare_modules(On),
    {'EXIT',{system_limit,_}} = (catch erlang:finish_loading(OnPrep)),

    Normal = make_modules(1, fun on_load_normal/1),
    Mixed = Normal ++ tl(On),
    MixedPrep = prepare_modules(Mixed),
    {'EXIT',{system_limit,_}} = (catch erlang:finish_loading(MixedPrep)),

    [false,true] = [erlang:has_prepared_code_on_load(Code) ||
		       Code <- MixedPrep],
    {'EXIT',{badarg,_}} = (catch erlang:has_prepared_code_on_load(<<1,2,3>>)),
    Magic = ets:match_spec_compile([{'_',[true],['$_']}]),
    {'EXIT',{badarg,_}} = (catch erlang:has_prepared_code_on_load(Magic)),

    SingleOnPrep = tl(OnPrep),
    {on_load,[OnLoadMod]} = erlang:finish_loading(SingleOnPrep),
    ok = erlang:call_on_load_function(OnLoadMod),
    ok.

on_load_module(M) ->
    ["-module("++M++").",
     "-on_load(f/0).",
     "f() -> ok."].

on_load_normal(M) ->
    ["-module("++M++")."].


errors(_Config) ->
    finish_loading_badarg(x),
    finish_loading_badarg([x|y]),
    finish_loading_badarg([x]),
    finish_loading_badarg([<<>>]),

    Mods = make_modules(2, fun errors_module/1),
    Ms = lists:sort([M || {M,_} <- Mods]),
    Prep = prepare_modules(Mods),
    {duplicated,Dups} = erlang:finish_loading(Prep ++ Prep),
    Ms = lists:sort(Dups),
    ok.

finish_loading_badarg(Arg) ->
    {'EXIT',{badarg,[{erlang,finish_loading,[Arg],_}|_]}} =
	(catch erlang:finish_loading(Arg)).

errors_module(M) ->
    ["-module("++M++").",
     "-export([f/0]).",
     "f() -> ok."].

%%%
%%% Common utilities
%%%

ms(Fun) ->
    {Ms,ok} = timer:tc(Fun),
    Ms.

make_modules(0, _) ->
    [];
make_modules(N, Fun) ->
    U = erlang:unique_integer([positive]),
    M0 = "m__" ++ integer_to_list(N) ++ "_" ++ integer_to_list(U),
    Contents = Fun(M0),
    Forms = [make_form(S) || S <- Contents],
    {ok,M,Code} = compile:forms(Forms),
    [{M,Code}|make_modules(N-1, Fun)].

make_form(S) ->
    {ok,Toks,_} = erl_scan:string(S),
    {ok,Form} = erl_parse:parse_form(Toks),
    Form.

prepare_modules(Ms) ->
    [erlang:prepare_loading(M, Code) || {M,Code} <- Ms].
