summaryrefslogtreecommitdiff
path: root/erts/test/otp_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'erts/test/otp_SUITE.erl')
-rw-r--r--erts/test/otp_SUITE.erl557
1 files changed, 221 insertions, 336 deletions
diff --git a/erts/test/otp_SUITE.erl b/erts/test/otp_SUITE.erl
index 1690406314..a8424ea46f 100644
--- a/erts/test/otp_SUITE.erl
+++ b/erts/test/otp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2023. 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.
@@ -218,17 +218,13 @@ call_to_deprecated(Config) when is_list(Config) ->
{comment,integer_to_list(length(DeprecatedCalls))++" calls to deprecated functions"}.
call_to_size_1(Config) when is_list(Config) ->
- %% Applications that do not call erlang:size/1:
- Apps = [asn1,compiler,debugger,kernel,observer,parsetools,
- runtime_tools,stdlib,tools],
+ %% Forbid the use of erlang:size/1 in all applications.
+ Apps = all_otp_applications(Config),
not_recommended_calls(Config, Apps, {erlang,size,1}).
call_to_now_0(Config) when is_list(Config) ->
- %% Applications that do not call erlang:now/1:
- Apps = [asn1,common_test,compiler,debugger,dialyzer,
- kernel,mnesia,observer,parsetools,reltool,
- runtime_tools,sasl,stdlib,syntax_tools,
- tools],
+ %% Forbid the use of erlang:now/1 in all applications except et.
+ Apps = all_otp_applications(Config) -- [et],
not_recommended_calls(Config, Apps, {erlang,now,0}).
not_recommended_calls(Config, Apps0, MFA) ->
@@ -281,9 +277,20 @@ not_recommended_calls(Config, Apps0, MFA) ->
{comment, Mess}
end;
_ ->
- ct:fail({length(CallsToMFA),calls_to_size_1})
+ ct:fail({length(CallsToMFA),calls_to,MFA})
end.
+all_otp_applications(Config) ->
+ Server = proplists:get_value(xref_server, Config),
+ {ok,AllApplications} = xref:q(Server, "A"),
+ OtpAppsMap = get_otp_applications(Config),
+ [App || App <- AllApplications, is_map_key(App, OtpAppsMap)].
+
+get_otp_applications(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ [Current|_] = read_otp_version_table(DataDir),
+ read_version_lines([Current]).
+
is_present_application(Name, Server) ->
Q = io_lib:format("~w : App", [Name]),
case xref:q(Server, lists:flatten(Q)) of
@@ -475,6 +482,189 @@ check_apps_deps([{App, Deps}|AppDeps], IgnoreApps) ->
end
end.
+%%
+%% Test that the runtime dependencies have not become stale.
+%%
+
+%% Path of installed OTP releases.
+-define(OTP_RELEASES, "/usr/local/otp/releases").
+
+%% Wildcard to match all releases from OTP 17.
+-define(OTP_RELEASE_WC, "{sles10_64_17_patched,ubuntu16_64_*_patched}").
+
+test_runtime_dependencies_versions(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+
+ OtpReleases = ?OTP_RELEASES,
+ OtpReleasesWc = filename:join(OtpReleases, ?OTP_RELEASE_WC),
+
+ IgnoreApps = [],
+ IgnoreUndefs = ignore_undefs(),
+
+ FirstVersionForApp = get_first_app_versions(DataDir),
+
+ case {element(1, os:type()) =:= unix,
+ not is_development_build(DataDir),
+ filelib:is_dir(OtpReleases)} of
+ {true, true, true} ->
+ test_runtime_dependencies_versions_rels(
+ IgnoreApps,
+ IgnoreUndefs,
+ FirstVersionForApp,
+ OtpReleasesWc);
+ {false, _, _} ->
+ {skip, "This test only runs on Unix systems"};
+ {_, false, _} ->
+ {skip,
+ "This test case is designed to run in the Erlang/OTP teams "
+ "test system for daily tests. The test case depends on that "
+ "app versions have been set correctly by scripts that "
+ "are executed before creating builds for the daily tests."};
+ {_, _ ,false} ->
+ {skip, "Can not do the tests without a proper releases dir. "
+ "Check that " ++ OtpReleases ++ " is set up correctly."}
+ end.
+
+ignore_undefs() ->
+ Socket = case lists:member(prim_socket, erlang:pre_loaded()) of
+ true ->
+ #{};
+ false ->
+ Ignore = #{{prim_socket,'_','_'} => true,
+ {socket_registry,'_','_'} => true,
+ {prim_net,'_','_'} => true },
+ #{kernel => Ignore, erts => Ignore}
+ end,
+ Socket#{eunit =>
+ %% Intentional call to nonexisting function
+ #{{eunit_test, nonexisting_function, 0} => true},
+ diameter =>
+ %% The following functions are optional dependencies for diameter
+ #{{dbg,ctp,0} => true,
+ {dbg,p,2} => true,
+ {dbg,stop,0} => true,
+ {dbg,trace_port,2} => true,
+ {dbg,tracer,2} => true,
+ {erl_prettypr,format,1} => true,
+ {erl_syntax,form_list,1} => true},
+ common_test =>
+ %% ftp:start/0 has been part of the ftp application from
+ %% the beginning so it is unclear why xref report this
+ %% as undefined
+ #{{ftp,start,0} => true}}.
+
+%% Read the otp_versions.table file and create a mapping from application name
+%% the first version for each application.
+get_first_app_versions(DataDir) ->
+ Lines = read_otp_version_table(DataDir),
+ read_version_lines(Lines).
+
+test_runtime_dependencies_versions_rels(IgnoreApps, IgnoreUndefs,
+ FirstVersionForApp, OtpReleasesWc) ->
+ AppVersionToPath = version_to_path(OtpReleasesWc),
+ Deps = [Dep || {App,_,_}=Dep <- get_deps(FirstVersionForApp),
+ not lists:member(App, IgnoreApps)],
+ case test_deps(Deps, IgnoreUndefs, AppVersionToPath, FirstVersionForApp) of
+ [] ->
+ ok;
+ [_|_]=Undefs ->
+ _ = [print_undefs(Undef) || Undef <- Undefs],
+ ct:fail({length(Undefs),errors})
+ end.
+
+print_undefs({_App,AppPath,Deps,Undefs}) ->
+ App = filename:basename(filename:dirname(AppPath)),
+ io:format("Undefined functions in ~ts:", [App]),
+ io:put_chars([io_lib:format(" ~p:~p/~p\n", [M,F,A]) ||
+ {M,F,A} <- Undefs]),
+ io:format("Dependencies: ~ts\n", [lists:join(" ", Deps)]).
+
+version_to_path(OtpReleasesWc) ->
+ CurrentWc = filename:join([code:lib_dir(), "*-*", "ebin"]),
+ LibWc = filename:join([OtpReleasesWc, "lib", "*-*", "ebin"]),
+ Dirs = lists:sort(filelib:wildcard(CurrentWc) ++ filelib:wildcard(LibWc)),
+ All = [{filename:basename(filename:dirname(Dir)),Dir} || Dir <- Dirs],
+ maps:from_list(All).
+
+get_deps(FirstVersionForApp) ->
+ Paths = [begin
+ Dir = filename:dirname(Path),
+ [App0 | _] = string:split(filename:basename(Dir), "-"),
+ App = list_to_atom(App0),
+ AppFile = filename:join(Path, App0 ++ ".app"),
+ {Path, App, AppFile}
+ end || Path <- code:get_path(),
+ filename:basename(Path) =:= "ebin"],
+ %% Only keep applications included in OTP.
+ Apps = [Triple || {_, App, AppFile}=Triple <- Paths,
+ filelib:is_file(AppFile),
+ is_map_key(App, FirstVersionForApp)],
+ [{App, Path, get_runtime_deps(App, AppFile)} || {Path, App, AppFile} <- Apps].
+
+get_runtime_deps(App, AppFile) ->
+ {ok,[{application, App, Info}]} = file:consult(AppFile),
+ case lists:keyfind(runtime_dependencies, 1, Info) of
+ {runtime_dependencies, RDeps} ->
+ RDeps;
+ false ->
+ []
+ end.
+
+test_deps([{Name,Path,Deps}|Apps], IgnoreUndefs, AppVersionToPath, FirstVersionForApp) ->
+ case test_dep(Name, Path, Deps, AppVersionToPath, FirstVersionForApp, IgnoreUndefs) of
+ ok ->
+ test_deps(Apps, IgnoreUndefs, AppVersionToPath, FirstVersionForApp);
+ {error, Error} ->
+ [Error|test_deps(Apps, IgnoreUndefs, AppVersionToPath, FirstVersionForApp)]
+ end;
+test_deps([], _IgnoreUndefs, _AppVersionToPath, _FirstVersionForApp) ->
+ [].
+
+test_dep(App, AppPath, Deps, AppVersionToPath, FirstVersionForApp, IgnoreUndefs) ->
+ DepPaths = [get_app_path(Dep, AppVersionToPath, FirstVersionForApp, App) ||
+ Dep <- Deps],
+
+ Server = xref_server_test_dep,
+ {ok, _} = xref:start(Server, []),
+ xref:set_default(Server, [{verbose,false},
+ {warnings,false},
+ {builtins,true}]),
+ ok = xref:set_library_path(Server, DepPaths),
+ {ok, _} = xref:add_directory(Server, AppPath),
+ {ok, Undef0} = xref:analyze(Server, undefined_functions),
+ xref:stop(Server),
+
+ %% Filter out undefined functions that we should ignore.
+ Ignore = maps:get(App, IgnoreUndefs, #{}),
+ Undef = [MFA || {M,F,_A}=MFA <- Undef0,
+ not is_map_key(MFA, Ignore),
+ not is_map_key({M,'_','_'}, Ignore),
+ not is_map_key({M,F,'_'}, Ignore)],
+ case Undef of
+ [] ->
+ ok;
+ [_|_] ->
+ {error, {App, AppPath, Deps, Undef}}
+ end.
+
+get_app_path(App, AppVersionToPath, FirstVersionForApp, ReferencedBy) ->
+ case AppVersionToPath of
+ #{App := Path} ->
+ Path;
+ #{} ->
+ [Name0, _Version] = string:split(App, "-"),
+ Name = list_to_existing_atom(Name0),
+ First = map_get(Name, FirstVersionForApp),
+ io:format("WARNING: ~ts referenced by ~ts is too old; using ~ts instead\n",
+ [App, ReferencedBy, First]),
+ map_get(First, AppVersionToPath)
+ end.
+
+is_development_build(DataDir) ->
+ OTPVersionTicketsPath = filename:join(DataDir, "otp_version_tickets"),
+ {ok, FileContentBin} = file:read_file(OTPVersionTicketsPath),
+ string:trim(binary_to_list(FileContentBin), both, "\n ") =:= "DEVELOPMENT".
+
%%%
%%% Common help functions.
%%%
@@ -545,329 +735,24 @@ start_xref_server(Server, Mode) ->
end,
Server.
-get_suite_data_dir_path() ->
- filename:join(filename:dirname(code:which(?MODULE)), "otp_SUITE_data").
-
-get_otp_versions_table_path() ->
- filename:join(get_suite_data_dir_path(), "otp_versions.table").
-
-get_otp_version_tickets_path() ->
- filename:join(get_suite_data_dir_path(), "otp_version_tickets").
-
-%% Return a map that maps from app versions to the OTP versions they
-%% were last released in. The function makes use of the file
-%% "otp_versions.table" and the current code:get_path() to
-%% find apps.
-get_runtime_dep_to_otp_version_map() ->
- %% Find apps in "otp_versions.table"
- VersionsTableFile = get_otp_versions_table_path(),
- VersionsTableBin =
- case file:read_file(VersionsTableFile) of
- {ok, Bin} -> Bin;
- Error -> ct:fail("Could not read the file ~s which is needed to perform the test. "
- "Error: ~p~n",
- [VersionsTableFile, Error])
- end,
- VersionsTableStr = erlang:binary_to_list(VersionsTableBin),
- Lines = lists:reverse(string:tokens(VersionsTableStr, "\n")),
- AddVersionsInString =
- fun(Map, OTPVersion, AppVersionsString0) ->
- AppVersionsString =
- lists:flatten(string:replace(AppVersionsString0, "#", "", all)),
- AppVersions = string:tokens(AppVersionsString, " "),
- lists:foldl(
- fun(AppVersion0, MapSoFar) ->
- case string:trim(AppVersion0) of
- "" ->
- MapSoFar;
- AppVersion1 ->
- maps:put(AppVersion1, OTPVersion, MapSoFar)
- end
- end,
- Map,
- AppVersions)
- end,
- VersionMap0 =
- lists:foldl(
- fun(Line, MapSoFar) ->
- [OTPVersion, AppVersionsString| _] =
- string:tokens(Line, ":"),
- AddVersionsInString(MapSoFar,
- string:trim(OTPVersion),
- string:trim(AppVersionsString))
- end,
- #{},
- Lines),
- %% Find apps in code:get_path()
- lists:foldl(
- fun(Path, MapSoFar) ->
- case filelib:wildcard(filename:join(Path, "*.app")) of
- [AppFile] ->
- {ok,[{application, App, Info}]} = file:consult(AppFile),
- case lists:keyfind(vsn, 1, Info) of
- false ->
- MapSoFar;
- {vsn, VsnStr} ->
- AppVsnStr =
- erlang:atom_to_list(App) ++ "-" ++ VsnStr,
- maps:put(AppVsnStr, {latest, Path}, MapSoFar)
- end;
- _ ->
- MapSoFar
- end
- end,
- VersionMap0,
- code:get_path()).
-
-%% Find runtime dependencies for an app
-get_runtime_deps(App) ->
- AppFile = code:where_is_file(atom_to_list(App) ++ ".app"),
- {ok,[{application, App, Info}]} = file:consult(AppFile),
- case lists:keyfind(runtime_dependencies, 1, Info) of
- {runtime_dependencies, RDeps} ->
- RDeps;
- false ->
- []
- end.
-
-%% Given a release dir find the path to the given dependency
-find_dep_in_rel_dir(Dep, RelDirRoot) ->
- %% The dependencies that we have found are cached to avoid
- %% searching through the file system unnecessary many times
- CacheId = runtime_dep_test_cache,
- DepCache =
- case erlang:get(CacheId) of
- undefined -> #{};
- M-> M
- end,
- case maps:get(Dep, DepCache, none) of
- none ->
- DepPaths = filelib:wildcard(filename:join([RelDirRoot, "lib", "**", Dep, "ebin"])),
- case DepPaths of
- [Path] ->
- erlang:put(CacheId, maps:put(Dep, Path, DepCache)),
- Path;
- _ ->
- ErrorMessage =
- io_lib:format("ERROR: Could not find ~p in ~p (where it is supposed to be)."
- "Found ~p~n", [Dep, RelDirRoot, DepPaths]),
- io:format(lists:flatten(ErrorMessage)),
- ct:fail(ErrorMessage)
-
- end;
- Path ->
- Path
- end.
-
-%% Get the major OTP version part of an OTP version string
-%% Example: OTP-22.0 gives 22
-get_major_version(OtpVersion) ->
- [_,MajorVersion|_] = string:tokens(OtpVersion, "-."),
- MajorVersion.
-
-%% Returns the release directory for the oldest available OTP release
-%% on the current machine
-first_available_otp_rel() ->
- %% At least one version less than the current version should be available
- SholdBeAvailable = erlang:list_to_integer(erlang:system_info(otp_release)) - 1,
- (fun FindOldest(CurrRel, PrevAvailable) ->
- case test_server:is_release_available(erlang:integer_to_list(CurrRel)) of
- false -> PrevAvailable;
- true -> FindOldest(CurrRel-1, erlang:integer_to_list(CurrRel))
- end
- end)(SholdBeAvailable, none).
-
-%% Searches for the oldest available version of Dep
-get_oldest_available_version_of_dep(Dep) ->
- [DepName, _Version] = string:tokens(Dep, "-"),
- FirstAvailableRel = first_available_otp_rel(),
- (fun Find(LookInRel) ->
- case test_server:is_release_available(LookInRel) of
- true ->
- RelRoot = find_rel_root(LookInRel),
- Options0 = filelib:wildcard(filename:join([RelRoot, "lib", "**", "ebin"])),
- Options1 = [Opt ||
- Opt <- Options0,
- string:find(Opt,
- filename:join("lib", DepName ++ "-")) =/= nomatch],
- GetVersionTuple =
- fun(Path) ->
- [AppVerStr] =
- [C || C <- filename:split(Path),
- string:find(C, DepName ++ "-") =/= nomatch],
- [_,VerStr] = string:tokens(AppVerStr, "-"),
- erlang:list_to_tuple([erlang:list_to_integer(X) ||
- X <- string:tokens(VerStr, ".")])
- end,
- Options2 =
- lists:sort(fun(A,B) ->
- GetVersionTuple(A) =< GetVersionTuple(B)
- end,
- Options1),
- case Options2 of
- [Path|_] ->
- Path;
- _ ->
- NextRelToTry =
- erlang:integer_to_list(erlang:list_to_integer(LookInRel)+1),
- Find(NextRelToTry)
- end;
- false ->
- ct:fail({could_not_find_dep_anywhere, DepName})
- end
- end)(FirstAvailableRel).
-
-find_rel_root(Rel) ->
- case test_server:find_release(Rel) of
- not_available ->
- not_available;
- OtpRelErl -> filename:dirname(filename:dirname(OtpRelErl))
- end.
-
-%% Find the absolute paths to RuntimeDeps
-get_paths_to_dependencies(App, RuntimeDeps) ->
- FirstAvailableOTPRel = first_available_otp_rel(),
- FirstAvailableRel = erlang:list_to_integer(FirstAvailableOTPRel),
- DepToOtpVerMap = get_runtime_dep_to_otp_version_map(),
- lists:foldl(
- fun(Dep, SoFar) ->
- case maps:get(Dep, DepToOtpVerMap, false) of
- false ->
- ct:fail(io_lib:format(
- "The dependency ~s for ~p could not be found. "
- "Have you typed a non-existing version?",
- [Dep, App]));
- {latest, Path} ->
- %% The dependency is in Path (one of the paths returned by code:get_paths())
- [Path | SoFar];
- OtpVersionWithDep ->
- OtpMajorVersionWithDep = get_major_version(OtpVersionWithDep),
- OtpMajorVersionWithDepInt = erlang:list_to_integer(OtpMajorVersionWithDep),
- case find_rel_root(OtpMajorVersionWithDep) of
- not_available when FirstAvailableRel > OtpMajorVersionWithDepInt ->
- io:format("Warning: Could not find runtime dependency ~p for ~p. "
- "~p belongs to ~p but ~p is too old to be available on this machine. "
- "Trying to find the oldest available version of ~p...",
- [Dep, App, Dep, OtpVersionWithDep, OtpVersionWithDep, Dep]),
- [get_oldest_available_version_of_dep(Dep) | SoFar];
- RelDirRoot ->
- [find_dep_in_rel_dir(Dep, RelDirRoot) | SoFar]
- end
- end
- end,
- [],
- RuntimeDeps).
-
-test_app_runtime_deps_versions(AppPath, App, IgnoredUndefinedFunctions) ->
- %% Get a list of all runtime dependencies for app
- RuntimeDeps = get_runtime_deps(App),
- %% Get paths to the found runtime dependencies
- DepPaths = get_paths_to_dependencies(App, RuntimeDeps),
- XRefSName = test_app_runtime_deps_versions_server,
- %% Start xref server and do the test
- {ok, _} = xref:start(XRefSName, []),
- ok = xref:set_library_path(XRefSName, DepPaths),
- Dir = filename:join(AppPath, "ebin"),
- {ok, _} = xref:add_directory(XRefSName, Dir),
- {ok, UndefinedFunctions0} = xref:analyze(XRefSName, undefined_functions),
- xref:stop(XRefSName),
- %% Filter out undefined functions that we should ignore
- UndefinedFunctions1 =
- [F || F <- UndefinedFunctions0,
- not maps:get(F, IgnoredUndefinedFunctions, false),
- not maps:get({element(1,F),'_','_'}, IgnoredUndefinedFunctions, false),
- not maps:get({element(1,F),element(2,F),'_'}, IgnoredUndefinedFunctions, false)],
- case UndefinedFunctions1 of
- [] ->
- ok;
- UndefinedFunctions ->
- {error_undefined_functions_in_app, App, UndefinedFunctions}
- end.
-
-test_runtime_dependencies_versions_rels(IgnoreApps, AppsToIgnoredUndefinedFunctions) ->
- %% Do for every application:
- %% 1. Find deps from app file
- %% 2. Find where the deps are installed
- %% 3. Run xref tests on the apps with the specified dependencies
- %% 4. Report error if undefined function
- Apps0 = [{Path, list_to_atom(AppName)}
- || {match, [Path, AppName]}
- <- [re:run(X,"(" ++ code:lib_dir()++"/"++"([^/-]*).*)/ebin",
- [{capture,[1,2],list},unicode]) || X <- code:get_path()]],
- Apps = [{Path, App} ||
- {Path, App} <- Apps0,
- code:where_is_file(atom_to_list(App) ++ ".app") =/= non_existing],
- Res = [test_app_runtime_deps_versions(AppPath,
- App,
- maps:get(App, AppsToIgnoredUndefinedFunctions, #{})) ||
- {AppPath, App} <- Apps, not lists:member(App, IgnoreApps)],
- BadRes = [R || R <- Res, R =/= ok],
- case BadRes =:= [] of
- true -> ok;
- _ ->
- ct:fail(BadRes)
- end.
-
-is_development_build() ->
- {ok, FileContentBin} = file:read_file(get_otp_version_tickets_path()),
- "DEVELOPMENT" =:= string:trim(erlang:binary_to_list(FileContentBin), both, "\n ").
-
-test_runtime_dependencies_versions(_Config) ->
- ReleasesDir = "/usr/local/otp/releases",
- IgnoreApps = [],
- SocketIgnore = case lists:member(prim_socket, erlang:pre_loaded()) of
- true -> #{};
- false ->
- Ignore = #{{prim_socket,'_','_'} => true,
- {socket_registry,'_','_'} => true,
- {prim_net,'_','_'} => true },
- #{ kernel => Ignore, erts => Ignore }
- end,
- AppsToIgnoredUndefinedFunctions =
- #{eunit =>
- %% Intentional call to nonexisting function
- #{{eunit_test, nonexisting_function, 0} => true},
- diameter =>
- %% The following functions are optional dependencies for diameter
- #{{dbg,ctp,0} => true,
- {dbg,p,2} => true,
- {dbg,stop_clear,0} => true,
- {dbg,trace_port,2} => true,
- {dbg,tracer,2} => true,
- {erl_prettypr,format,1} => true,
- {erl_syntax,form_list,1} => true},
- common_test =>
- %% ftp:start/0 has been part of the ftp application from
- %% the beginning so it is unclear why xref report this
- %% as undefined
- #{{ftp,start,0} => true}},
- case {erlang:element(1, os:type()) =:= unix,
- not is_development_build(),
- filelib:is_dir(ReleasesDir),
- filelib:is_file(get_otp_versions_table_path()),
- first_available_otp_rel() =/= none} of
- {true, true, true, true, true} ->
- test_runtime_dependencies_versions_rels(
- IgnoreApps,
- maps:merge(AppsToIgnoredUndefinedFunctions, SocketIgnore));
- {_, _ ,_, false, _} -> {skip,
- "Could not find the file \"otp_versions.table\". "
- "Check that the test has been built correctly. "
- "\"otp_versions.table\" is copied to \"erts/test/otp_SUITE_data\" "
- "by the makefile \"erts/test/Makefile\""};
- {_, false , _, _, _} -> {skip,
- "This test case is designed to run in the Erlang/OTP teams "
- "test system for nightly tests. The test case depend on that "
- "app versions have been set correctly by scripts that "
- "are executed before creating builds for the nightly tests."};
- {_, _ ,false, _, _} -> {skip, "Can not do the tests without a proper releases dir. "
- "Check that " ++ ReleasesDir ++ " is set up correctly."};
- {_, _ , _, _, false} ->
- PrevRelNr = erlang:list_to_integer(erlang:system_info(otp_release)) - 1,
- PrevRelNrStr = erlang:integer_to_list(PrevRelNr),
- {skip,
- "Seems like the releases dir is not set up correctly. "
- "Is release " ++ PrevRelNrStr ++ " installed in the releases dir? "
- "(releases dir = " ++ ReleasesDir ++ ")"};
- {false, _ ,_, _, _} -> {skip, "This test only runs on Unix systems"}
- end.
+read_otp_version_table(DataDir) ->
+ VersionTableFile = filename:join(DataDir, "otp_versions.table"),
+ {ok, Contents} = file:read_file(VersionTableFile),
+ binary:split(Contents, <<"\n">>, [global,trim]).
+
+read_version_lines(Lines) ->
+ read_version_lines(Lines, #{}).
+
+read_version_lines([Line|Lines], Map0) ->
+ [<<"OTP-",_/binary>>, <<":">> | Apps] = binary:split(Line, <<" ">>, [global,trim]),
+ Map = lists:foldl(fun(App, Acc) ->
+ case binary:split(App, <<"-">>) of
+ [Name, _Version] ->
+ Acc#{binary_to_atom(Name) => binary_to_list(App)};
+ [_] ->
+ Acc
+ end
+ end, Map0, Apps),
+ read_version_lines(Lines, Map);
+read_version_lines([], Map) ->
+ Map.