summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan Sloughter <tristan.sloughter@gmail.com>2015-05-14 16:12:40 -0500
committerTristan Sloughter <tristan.sloughter@gmail.com>2015-05-14 16:12:40 -0500
commitda93ec9d0a80ddcea1baf9112851bd991d36f5f5 (patch)
treeed1f257d812e97d274cc12cfaa1702b600c6eb49
parent192e85aafa366a014600b6989480fa0b612b0408 (diff)
parentf37cee14b240aaede19807cbc484286cffc8be8d (diff)
Merge pull request #414 from tsloughter/hex_pkg_improvements
Hex pkg improvements
-rw-r--r--rebar.config4
-rw-r--r--src/rebar.hrl1
-rw-r--r--src/rebar_fetch.erl64
-rw-r--r--src/rebar_packages.erl28
-rw-r--r--src/rebar_pkg_resource.erl99
-rw-r--r--src/rebar_prv_install_deps.erl33
-rw-r--r--src/rebar_prv_update.erl6
-rw-r--r--src/rebar_resource.erl19
-rw-r--r--test/mock_pkg_resource.erl11
-rw-r--r--test/rebar_pkg_SUITE.erl207
-rw-r--r--test/rebar_pkg_SUITE_data/badindexchk-1.0.0.tarbin0 -> 10240 bytes
-rw-r--r--test/rebar_pkg_SUITE_data/badpkg-1.0.0.tarbin0 -> 10240 bytes
-rw-r--r--test/rebar_pkg_SUITE_data/goodpkg-1.0.0.tarbin0 -> 10240 bytes
13 files changed, 387 insertions, 85 deletions
diff --git a/rebar.config b/rebar.config
index 5eaf6de..504a462 100644
--- a/rebar.config
+++ b/rebar.config
@@ -30,9 +30,7 @@
{"rebar/include/*", "."}]}.
{erl_opts,
- [{platform_define, "R14", no_callback_support},
- {platform_define, "^[0-9]+", namespaced_types},
- {platform_define, "^R1[4|5]", deprecated_crypto},
+ [{platform_define, "^[0-9]+", namespaced_types},
no_debug_info,
warnings_as_errors]}.
diff --git a/src/rebar.hrl b/src/rebar.hrl
index 1f051d7..4540b1a 100644
--- a/src/rebar.hrl
+++ b/src/rebar.hrl
@@ -22,6 +22,7 @@
-define(DEFAULT_TEST_DEPS_DIR, "test/lib").
-define(DEFAULT_RELEASE_DIR, "rel").
-define(DEFAULT_CONFIG_FILE, "rebar.config").
+-define(DEFAULT_CDN, "https://s3.amazonaws.com/s3.hex.pm/tarballs").
-define(LOCK_FILE, "rebar.lock").
-ifdef(namespaced_types).
diff --git a/src/rebar_fetch.erl b/src/rebar_fetch.erl
index 20bf46b..0aca308 100644
--- a/src/rebar_fetch.erl
+++ b/src/rebar_fetch.erl
@@ -26,42 +26,34 @@ lock_source(AppDir, Source, State) ->
-spec download_source(file:filename_all(), rebar_resource:resource(), rebar_state:t()) ->
true | {error, any()}.
download_source(AppDir, Source, State) ->
- try
- Resources = rebar_state:resources(State),
- Module = get_resource_type(Source, Resources),
- TmpDir = ec_file:insecure_mkdtemp(),
- AppDir1 = ec_cnv:to_list(AppDir),
- case Module:download(TmpDir, Source, State) of
- {ok, _} ->
- ec_file:mkdir_p(AppDir1),
- code:del_path(filename:absname(filename:join(AppDir1, "ebin"))),
- ec_file:remove(filename:absname(AppDir1), [recursive]),
- ?DEBUG("Moving checkout ~p to ~p", [TmpDir, filename:absname(AppDir1)]),
- ok = rebar_file_utils:mv(TmpDir, filename:absname(AppDir1)),
- true;
- {tarball, File} ->
- Contents = filename:join(TmpDir, "contents"),
- ec_file:mkdir_p(AppDir1),
- ec_file:mkdir_p(Contents),
- ok = erl_tar:extract(File, [{cwd, TmpDir}]),
- ok = erl_tar:extract(filename:join(TmpDir, "contents.tar.gz"),
- [{cwd, Contents}, compressed]),
- code:del_path(filename:absname(filename:join(AppDir1, "ebin"))),
- ec_file:remove(filename:absname(AppDir1), [recursive]),
-
- ?DEBUG("Moving contents ~p to ~p", [Contents, filename:absname(AppDir1)]),
- ok = rebar_file_utils:mv(Contents, filename:absname(AppDir1)),
-
- ?DEBUG("Removing tmp dir ~p", [TmpDir]),
- ec_file:remove(TmpDir, [recursive]),
- true
- end
+ try download_source_(AppDir, Source, State) of
+ true ->
+ true;
+ Error ->
+ throw(?PRV_ERROR(Error))
catch
C:T ->
?DEBUG("rebar_fetch exception ~p ~p ~p", [C, T, erlang:get_stacktrace()]),
throw(?PRV_ERROR({fetch_fail, Source}))
end.
+download_source_(AppDir, Source, State) ->
+ Resources = rebar_state:resources(State),
+ Module = get_resource_type(Source, Resources),
+ TmpDir = ec_file:insecure_mkdtemp(),
+ AppDir1 = ec_cnv:to_list(AppDir),
+ case Module:download(TmpDir, Source, State) of
+ {ok, _} ->
+ ec_file:mkdir_p(AppDir1),
+ code:del_path(filename:absname(filename:join(AppDir1, "ebin"))),
+ ec_file:remove(filename:absname(AppDir1), [recursive]),
+ ?DEBUG("Moving checkout ~p to ~p", [TmpDir, filename:absname(AppDir1)]),
+ ok = rebar_file_utils:mv(TmpDir, filename:absname(AppDir1)),
+ true;
+ Error ->
+ Error
+ end.
+
-spec needs_update(file:filename_all(), rebar_resource:resource(), rebar_state:t()) -> boolean() | {error, string()}.
needs_update(AppDir, Source, State) ->
Resources = rebar_state:resources(State),
@@ -73,8 +65,18 @@ needs_update(AppDir, Source, State) ->
true
end.
+format_error({bad_download, CachePath}) ->
+ io_lib:format("Download of package does not match md5sum from server: ~s", [CachePath]);
+format_error({failed_extract, CachePath}) ->
+ io_lib:format("Failed to extract package: ~s", [CachePath]);
+format_error({bad_etag, Source}) ->
+ io_lib:format("MD5 Checksum comparison failed for: ~s", [Source]);
format_error({fetch_fail, Source}) ->
- io_lib:format("Failed to fetch and copy dep: ~p", [Source]).
+ io_lib:format("Failed to fetch and copy dep: ~s", [Source]);
+format_error({bad_checksum, File}) ->
+ io_lib:format("Checksum mismatch against tarball in ~s", [File]);
+format_error({bad_registry_checksum, File}) ->
+ io_lib:format("Checksum mismatch against registry in ~s", [File]).
get_resource_type({Type, Location}, Resources) ->
find_resource_module(Type, Location, Resources);
diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl
index 4ab5f9f..e21f1fd 100644
--- a/src/rebar_packages.erl
+++ b/src/rebar_packages.erl
@@ -2,7 +2,9 @@
-export([get_packages/1
,registry/1
+ ,package_dir/1
,check_registry/3
+ ,registry_checksum/2
,find_highest_matching/3]).
-export_type([package/0]).
@@ -15,8 +17,7 @@
-spec get_packages(rebar_state:t()) -> {rebar_dict(), rebar_digraph()}.
get_packages(State) ->
- RebarDir = rebar_dir:global_cache_dir(State),
- RegistryDir = filename:join(RebarDir, "packages"),
+ RegistryDir = package_dir(State),
DictFile = filename:join(RegistryDir, "dict"),
Edges = filename:join(RegistryDir, "edges"),
Vertices = filename:join(RegistryDir, "vertices"),
@@ -42,8 +43,7 @@ get_packages(State) ->
end.
registry(State) ->
- Dir = rebar_dir:global_cache_dir(State),
- RegistryDir = filename:join(Dir, "packages"),
+ RegistryDir = package_dir(State),
HexFile = filename:join(RegistryDir, "registry"),
case ets:file2tab(HexFile) of
{ok, T} ->
@@ -53,6 +53,17 @@ registry(State) ->
error
end.
+package_dir(State) ->
+ CacheDir = rebar_dir:global_cache_dir(State),
+ CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
+ {ok, {_, _, Host, _, Path, _}} = http_uri:parse(CDN),
+ CDNHostPath = lists:reverse(string:tokens(Host, ".")),
+ CDNPath = tl(filename:split(Path)),
+ PackageDir = filename:join([CacheDir, "hex"] ++ CDNHostPath ++ CDNPath ++ ["packages"]),
+ ok = filelib:ensure_dir(filename:join(PackageDir, "placeholder")),
+ PackageDir.
+
+
check_registry(Pkg, Vsn, State) ->
case rebar_state:registry(State) of
{ok, T} ->
@@ -66,6 +77,15 @@ check_registry(Pkg, Vsn, State) ->
false
end.
+registry_checksum({pkg, Name, Vsn}, State) ->
+ {ok, Registry} = registry(State),
+ case ets:lookup(Registry, {Name, Vsn}) of
+ [{{_, _}, [_, Checksum | _]}] ->
+ Checksum;
+ [] ->
+ none
+ end.
+
%% Hex supports use of ~> to specify the version required for a dependency.
%% Since rebar3 requires exact versions to choose from we find the highest
%% available version of the dep that passes the constraint.
diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl
index 3b44fc8..59ce0dc 100644
--- a/src/rebar_pkg_resource.erl
+++ b/src/rebar_pkg_resource.erl
@@ -23,12 +23,99 @@ needs_update(Dir, {pkg, _Name, Vsn}) ->
true
end.
-download(Dir, {pkg, Name, Vsn}, State) ->
- TmpFile = filename:join(Dir, "package.tar.gz"),
- CDN = rebar_state:get(State, rebar_packages_cdn, "https://s3.amazonaws.com/s3.hex.pm/tarballs"),
- Url = string:join([CDN, binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>)], "/"),
- {ok, saved_to_file} = httpc:request(get, {Url, []}, [], [{stream, TmpFile}]),
- {tarball, TmpFile}.
+download(TmpDir, Pkg={pkg, Name, Vsn}, State) ->
+ CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
+ PackageDir = rebar_packages:package_dir(State),
+ Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>),
+ CachePath = filename:join(PackageDir, Package),
+ Url = string:join([CDN, Package], "/"),
+ cached_download(TmpDir, CachePath, Pkg, Url, etag(CachePath), State).
+
+cached_download(TmpDir, CachePath, Pkg, Url, ETag, State) ->
+ case request(Url, ETag) of
+ {ok, cached} ->
+ serve_from_cache(TmpDir, CachePath, Pkg, State);
+ {ok, Body, NewETag} ->
+ serve_from_download(TmpDir, CachePath, Pkg, NewETag, Body, State);
+ error when ETag =/= false ->
+ ?DEBUG("Download ~s error, using ~s from cache", [Url, CachePath]),
+ serve_from_cache(TmpDir, CachePath, Pkg, State);
+ error ->
+ request_failed
+ end.
+
+serve_from_cache(TmpDir, CachePath, Pkg, State) ->
+ {Files, Contents, Version, Meta} = extract(TmpDir, CachePath),
+ case checksums(Pkg, Files, Contents, Version, Meta, State) of
+ {Chk, Chk, Chk} ->
+ ok = erl_tar:extract({binary, Contents}, [{cwd, TmpDir}, compressed]),
+ {ok, true};
+ {_Bin, Chk, Chk} ->
+ ?DEBUG("Checksums: registry: ~p, pkg: ~p", [Chk, _Bin]),
+ {failed_extract, CachePath};
+ {Chk, _Reg, Chk} ->
+ ?DEBUG("Checksums: registry: ~p, pkg: ~p", [_Reg, Chk]),
+ {bad_registry_checksum, CachePath};
+ {_Bin, _Reg, _Tar} ->
+ ?DEBUG("Checksums: registry: ~p, pkg: ~p, meta: ~p", [_Reg, _Bin, _Tar]),
+ {bad_checksum, CachePath}
+ end.
+
+serve_from_download(TmpDir, CachePath, Package, ETag, Binary, State) ->
+ ?DEBUG("Writing ~p to cache at ~s", [Package, CachePath]),
+ file:write_file(CachePath, Binary),
+ case etag(CachePath) of
+ ETag ->
+ serve_from_cache(TmpDir, CachePath, Package, State);
+ FileETag ->
+ ?DEBUG("Downloaded file ~s ETag ~s doesn't match returned ETag ~s", [CachePath, ETag, FileETag]),
+ {bad_download, CachePath}
+ end.
+
+
+extract(TmpDir, CachePath) ->
+ ec_file:mkdir_p(TmpDir),
+ {ok, Files} = erl_tar:extract(CachePath, [memory]),
+ {"contents.tar.gz", Contents} = lists:keyfind("contents.tar.gz", 1, Files),
+ {"VERSION", Version} = lists:keyfind("VERSION", 1, Files),
+ {"metadata.config", Meta} = lists:keyfind("metadata.config", 1, Files),
+ {Files, Contents, Version, Meta}.
+
+checksums(Pkg, Files, Contents, Version, Meta, State) ->
+ Blob = <<Version/binary, Meta/binary, Contents/binary>>,
+ <<X:256/big-unsigned>> = crypto:hash(sha256, Blob),
+ BinChecksum = list_to_binary(string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X])))),
+ RegistryChecksum = rebar_packages:registry_checksum(Pkg, State),
+ {"CHECKSUM", TarChecksum} = lists:keyfind("CHECKSUM", 1, Files),
+ {BinChecksum, RegistryChecksum, TarChecksum}.
make_vsn(_) ->
{error, "Replacing version of type pkg not supported."}.
+
+request(Url, ETag) ->
+ case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]},
+ [{relaxed, true}],
+ [{body_format, binary}]) of
+ {ok, {{_Version, 200, _Reason}, Headers, Body}} ->
+ ?DEBUG("Successfully downloaded ~s", [Url]),
+ {"etag", ETag1} = lists:keyfind("etag", 1, Headers),
+ {ok, Body, string:strip(ETag1, both, $")};
+ {ok, {{_Version, 304, _Reason}, _Headers, _Body}} ->
+ ?DEBUG("Cached copy of ~s still valid", [Url]),
+ {ok, cached};
+ {ok, {{_Version, Code, _Reason}, _Headers, _Body}} ->
+ ?DEBUG("Request to ~p failed: status code ~p", [Url, Code]),
+ error;
+ {error, Reason} ->
+ ?DEBUG("Request to ~p failed: ~p", [Url, Reason]),
+ error
+ end.
+
+etag(Path) ->
+ case file:read_file(Path) of
+ {ok, Binary} ->
+ <<X:128/big-unsigned-integer>> = crypto:hash(md5, Binary),
+ string:to_lower(lists:flatten(io_lib:format("~32.16.0b", [X])));
+ {error, _} ->
+ false
+ end.
diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl
index bc0a113..ba49532 100644
--- a/src/rebar_prv_install_deps.erl
+++ b/src/rebar_prv_install_deps.erl
@@ -151,7 +151,7 @@ handle_deps(Profile, State0, Deps, Upgrade, Locks) ->
,{Packages, Graph}),
update_pkg_deps(Profile, Packages, PkgDeps1
- ,Graph, Upgrade, Seen, State2)
+ ,Graph, Upgrade, Seen, State2, Locks)
end,
AllDeps = lists:ukeymerge(2
@@ -184,7 +184,7 @@ find_cycles(Apps) ->
cull_compile(TopSortedDeps, ProjectApps) ->
lists:dropwhile(fun not_needs_compile/1, TopSortedDeps -- ProjectApps).
-update_pkg_deps(Profile, Packages, PkgDeps, Graph, Upgrade, Seen, State) ->
+update_pkg_deps(Profile, Packages, PkgDeps, Graph, Upgrade, Seen, State, Locks) ->
case PkgDeps of
[] -> %% No pkg deps
{[], State};
@@ -196,13 +196,16 @@ update_pkg_deps(Profile, Packages, PkgDeps, Graph, Upgrade, Seen, State) ->
{ok, Solution, []} ->
Solution;
{ok, Solution, Discarded} ->
- [warn_skip_pkg(Pkg, State) || Pkg <- Discarded],
+ [warn_skip_pkg(Pkg, State) || Pkg <- Discarded, not(pkg_locked(Pkg, Locks))],
Solution
end,
- update_pkg_deps(Profile, S, Packages, Upgrade, Seen, State)
+ update_pkg_deps(Profile, S, Packages, Upgrade, Seen, State, Locks)
end.
-update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State) ->
+pkg_locked({Name, _}, Locks) ->
+ false =/= lists:keyfind(Name, 1, Locks).
+
+update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State, _Locks) ->
%% Create app_info record for each pkg dep
DepsDir = profile_dep_dir(State, Profile),
{Solved, _, State1}
@@ -563,10 +566,10 @@ fetch_app(AppInfo, AppDir, State) ->
?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]),
Source = rebar_app_info:source(AppInfo),
case rebar_fetch:download_source(AppDir, Source, State) of
- {error, Reason} ->
- throw(Reason);
- Result ->
- Result
+ true ->
+ true;
+ Error ->
+ throw(Error)
end.
update_app_info(AppInfo) ->
@@ -587,10 +590,10 @@ maybe_upgrade(AppInfo, AppDir, true, State) ->
true ->
?INFO("Upgrading ~s", [rebar_app_info:name(AppInfo)]),
case rebar_fetch:download_source(AppDir, Source, State) of
- {error, Reason} ->
- throw(Reason);
- Result ->
- Result
+ true ->
+ true;
+ Error ->
+ throw(Error)
end;
false ->
?INFO("No upgrade needed for ~s", [rebar_app_info:name(AppInfo)]),
@@ -610,7 +613,7 @@ parse_goal(Name, Constraint) ->
warn_skip_deps(AppInfo, State) ->
Msg = "Skipping ~s (from ~p) as an app of the same name "
- "has already been fetched~n",
+ "has already been fetched",
Args = [rebar_app_info:name(AppInfo),
rebar_app_info:source(AppInfo)],
case rebar_state:get(State, deps_error_on_conflict, false) of
@@ -620,7 +623,7 @@ warn_skip_deps(AppInfo, State) ->
warn_skip_pkg({Name, Source}, State) ->
Msg = "Skipping ~s (version ~s from package index) as an app of the same "
- "name has already been fetched~n",
+ "name has already been fetched",
Args = [Name, Source],
case rebar_state:get(State, deps_error_on_conflict, false) of
false -> ?WARN(Msg, Args);
diff --git a/src/rebar_prv_update.erl b/src/rebar_prv_update.erl
index 942b386..dfb719a 100644
--- a/src/rebar_prv_update.erl
+++ b/src/rebar_prv_update.erl
@@ -35,8 +35,7 @@ init(State) ->
do(State) ->
?INFO("Updating package index...", []),
try
- Dir = rebar_dir:global_cache_dir(State),
- RegistryDir = filename:join(Dir, "packages"),
+ RegistryDir = rebar_packages:package_dir(State),
filelib:ensure_dir(filename:join(RegistryDir, "dummy")),
HexFile = filename:join(RegistryDir, "registry"),
TmpDir = ec_file:insecure_mkdtemp(),
@@ -64,8 +63,7 @@ format_error(package_index_write) ->
"Failed to write package index.".
write_registry(Dict, {digraph, Edges, Vertices, Neighbors, _}, State) ->
- Dir = rebar_dir:global_cache_dir(State),
- RegistryDir = filename:join(Dir, "packages"),
+ RegistryDir = rebar_packages:package_dir(State),
filelib:ensure_dir(filename:join(RegistryDir, "dummy")),
ets:tab2file(Edges, filename:join(RegistryDir, "edges")),
ets:tab2file(Vertices, filename:join(RegistryDir, "vertices")),
diff --git a/src/rebar_resource.erl b/src/rebar_resource.erl
index 7c58135..cdce7a8 100644
--- a/src/rebar_resource.erl
+++ b/src/rebar_resource.erl
@@ -14,23 +14,6 @@
-type location() :: string().
-type ref() :: any().
--ifdef(no_callback_support).
-
-%% In the case where R14 or lower is being used to compile the system
-%% we need to export a behaviour info
--export([behaviour_info/1]).
-
--spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined.
-behaviour_info(callbacks) ->
- [{lock, 2},
- {download, 3},
- {needs_update,2},
- {make_vsn, 1}];
-behaviour_info(_) ->
- undefined.
-
--else.
-
-callback lock(file:filename_all(), tuple()) ->
rebar_resource:resource().
-callback download(file:filename_all(), tuple(), rebar_state:t()) ->
@@ -39,5 +22,3 @@ behaviour_info(_) ->
boolean().
-callback make_vsn(file:filename_all()) ->
{plain, string()} | {error, string()}.
-
--endif.
diff --git a/test/mock_pkg_resource.erl b/test/mock_pkg_resource.erl
index 560caef..e94ea93 100644
--- a/test/mock_pkg_resource.erl
+++ b/test/mock_pkg_resource.erl
@@ -16,6 +16,7 @@ mock() -> mock([]).
-spec mock(Opts) -> ok when
Opts :: [Option],
Option :: {update, [App]}
+ | {cache_dir, string()}
| {default_vsn, Vsn}
| {override_vsn, [{App, Vsn}]}
| {not_in_index, [{App, Vsn}]}
@@ -83,7 +84,8 @@ mock_download(Opts) ->
[kernel, stdlib] ++ [element(1,D) || D <- AppDeps]
),
rebar_test_utils:create_config(Dir, [{deps, AppDeps}]),
- Tarball = filename:join([Dir, App++"-"++binary_to_list(Vsn)++".tar"]),
+ TarApp = App++"-"++binary_to_list(Vsn)++".tar",
+ Tarball = filename:join([Dir, TarApp]),
Contents = filename:join([Dir, "contents.tar.gz"]),
Files = all_files(rebar_app_info:dir(AppInfo)),
ok = erl_tar:create(Contents,
@@ -92,8 +94,11 @@ mock_download(Opts) ->
ok = erl_tar:create(Tarball,
[{"contents.tar.gz", Contents}],
[]),
- [file:delete(F) || F <- Files],
- {tarball, Tarball}
+ Cache = proplists:get_value(cache_dir, Opts, filename:join(Dir,"cache")),
+ Cached = filename:join([Cache, TarApp]),
+ filelib:ensure_dir(Cached),
+ rebar_file_utils:mv(Tarball, Cached),
+ {ok, true}
end).
%% @doc On top of the pkg resource mocking, we need to mock the package
diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl
new file mode 100644
index 0000000..95eb6f6
--- /dev/null
+++ b/test/rebar_pkg_SUITE.erl
@@ -0,0 +1,207 @@
+%% Test suite for the rebar pkg index caching and decompression
+%% mechanisms.
+-module(rebar_pkg_SUITE).
+-compile(export_all).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-define(bad_etag, "abcdef").
+-define(good_etag, "22e1d7387c9085a462340088a2a8ba67").
+-define(bad_checksum, <<"D576B442A68C7B92BACDE1EFE9C6E54D8D6C74BDB71D8175B9D3C6EC8C7B62A7">>).
+-define(good_checksum, <<"1C6CE379D191FBAB41B7905075E0BF87CBBE23C77CECE775C5A0B786B2244C35">>).
+
+all() -> [good_uncached, good_cached, badindexchk, badpkg,
+ bad_to_good, good_disconnect, bad_disconnect].
+
+init_per_suite(Config) ->
+ application:start(meck),
+ Config.
+
+end_per_suite(_Config) ->
+ application:stop(meck).
+
+init_per_testcase(good_uncached=Name, Config0) ->
+ Config = [{good_cache, false},
+ {pkg, {<<"goodpkg">>, <<"1.0.0">>}}
+ | Config0],
+ mock_config(Name, Config);
+init_per_testcase(good_cached=Name, Config0) ->
+ Pkg = {<<"goodpkg">>, <<"1.0.0">>},
+ Config1 = [{good_cache, true},
+ {pkg, Pkg}
+ | Config0],
+ Config = mock_config(Name, Config1),
+ copy_to_cache(Pkg, Config),
+ Config;
+init_per_testcase(badindexchk=Name, Config0) ->
+ Config = [{good_cache, false},
+ {pkg, {<<"badindexchk">>, <<"1.0.0">>}}
+ | Config0],
+ mock_config(Name, Config);
+init_per_testcase(badpkg=Name, Config0) ->
+ Config = [{good_cache, false},
+ {pkg, {<<"badpkg">>, <<"1.0.0">>}}
+ | Config0],
+ mock_config(Name, Config);
+init_per_testcase(bad_to_good=Name, Config0) ->
+ Config1 = [{good_cache, false},
+ {pkg, {<<"goodpkg">>, <<"1.0.0">>}}
+ | Config0],
+ Config = mock_config(Name, Config1),
+ Source = filename:join(?config(data_dir, Config), <<"badpkg-1.0.0.tar">>),
+ Dest = filename:join(?config(cache_dir, Config), <<"goodpkg-1.0.0.tar">>),
+ ec_file:copy(Source, Dest),
+ Config;
+init_per_testcase(good_disconnect=Name, Config0) ->
+ Pkg = {<<"goodpkg">>, <<"1.0.0">>},
+ Config1 = [{good_cache, true},
+ {pkg, Pkg}
+ | Config0],
+ Config = mock_config(Name, Config1),
+ copy_to_cache(Pkg, Config),
+ meck:unload(httpc),
+ meck:new(httpc, [passthrough, unsticky]),
+ meck:expect(httpc, request, fun(_, _, _, _) -> {error, econnrefused} end),
+ Config;
+init_per_testcase(bad_disconnect=Name, Config0) ->
+ Pkg = {<<"goodpkg">>, <<"1.0.0">>},
+ Config1 = [{good_cache, false},
+ {pkg, Pkg}
+ | Config0],
+ Config = mock_config(Name, Config1),
+ meck:unload(httpc),
+ meck:new(httpc, [passthrough, unsticky]),
+ meck:expect(httpc, request, fun(_, _, _, _) -> {error, econnrefused} end),
+ Config.
+
+end_per_testcase(_, Config) ->
+ unmock_config(Config),
+ Config.
+
+good_uncached(Config) ->
+ Tmp = ?config(tmp_dir, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ State = ?config(state, Config),
+ ?assertEqual({ok, true},
+ rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)),
+ Cache = ?config(cache_dir, Config),
+ ?assert(filelib:is_regular(filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>))).
+
+good_cached(Config) ->
+ Tmp = ?config(tmp_dir, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ State = ?config(state, Config),
+ Cache = ?config(cache_dir, Config),
+ CachedFile = filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>),
+ ?assert(filelib:is_regular(CachedFile)),
+ {ok, Content} = file:read_file(CachedFile),
+ ?assertEqual({ok, true},
+ rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)),
+ {ok, Content} = file:read_file(CachedFile).
+
+badindexchk(Config) ->
+ Tmp = ?config(tmp_dir, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ State = ?config(state, Config),
+ ?assertMatch({bad_registry_checksum, _Path},
+ rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)),
+ %% The cached file is there for forensic purposes
+ Cache = ?config(cache_dir, Config),
+ ?assert(filelib:is_regular(filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>))).
+
+badpkg(Config) ->
+ Tmp = ?config(tmp_dir, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ State = ?config(state, Config),
+ ?assertMatch({bad_download, _Path},
+ rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)),
+ %% The cached file is there for forensic purposes
+ Cache = ?config(cache_dir, Config),
+ ?assert(filelib:is_regular(filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>))).
+
+bad_to_good(Config) ->
+ Tmp = ?config(tmp_dir, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ State = ?config(state, Config),
+ Cache = ?config(cache_dir, Config),
+ CachedFile = filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>),
+ ?assert(filelib:is_regular(CachedFile)),
+ {ok, Contents} = file:read_file(CachedFile),
+ ?assertEqual({ok, true},
+ rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)),
+ %% Cache has refreshed
+ ?assert({ok, Contents} =/= file:read_file(CachedFile)).
+
+good_disconnect(Config) ->
+ Tmp = ?config(tmp_dir, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ State = ?config(state, Config),
+ Cache = ?config(cache_dir, Config),
+ CachedFile = filename:join(Cache, <<Pkg/binary, "-", Vsn/binary, ".tar">>),
+ ?assert(filelib:is_regular(CachedFile)),
+ {ok, Content} = file:read_file(CachedFile),
+ ?assertEqual({ok, true},
+ rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)),
+ {ok, Content} = file:read_file(CachedFile).
+
+bad_disconnect(Config) ->
+ Tmp = ?config(tmp_dir, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ State = ?config(state, Config),
+ ?assertEqual(request_failed,
+ rebar_pkg_resource:download(Tmp, {pkg, Pkg, Vsn}, State)).
+
+
+%%%%%%%%%%%%%%%
+%%% Helpers %%%
+%%%%%%%%%%%%%%%
+mock_config(Name, Config) ->
+ Priv = ?config(priv_dir, Config),
+ CacheRoot = filename:join([Priv, "cache", atom_to_list(Name)]),
+ TmpDir = filename:join([Priv, "tmp", atom_to_list(Name)]),
+ T = ets:new(fake_registry, [public]),
+ ets:insert_new(T, [
+ {{<<"badindexchk">>,<<"1.0.0">>}, [[], ?bad_checksum]},
+ {{<<"goodpkg">>,<<"1.0.0">>}, [[], ?good_checksum]},
+ {{<<"badpkg">>,<<"1.0.0">>}, [[], ?good_checksum]}
+ ]),
+ CacheDir = filename:join([CacheRoot, "hex", "com", "test", "packages"]),
+ filelib:ensure_dir(filename:join([CacheDir, "registry"])),
+ ok = ets:tab2file(T, filename:join([CacheDir, "registry"])),
+ %% The state returns us a fake registry
+ meck:new(rebar_state, [passthrough]),
+ meck:expect(rebar_state, registry,
+ fun(_State) -> {ok, fake_registry} end),
+ meck:expect(rebar_state, get,
+ fun(_State, rebar_packages_cdn, _Default) ->
+ "http://test.com/"
+ end),
+ meck:new(rebar_dir, [passthrough]),
+ meck:expect(rebar_dir, global_cache_dir, fun(_) -> CacheRoot end),
+ %% Cache fetches are mocked -- we assume the server and clients are
+ %% correctly used.
+ GoodCache = ?config(good_cache, Config),
+ {Pkg,Vsn} = ?config(pkg, Config),
+ PkgFile = <<Pkg/binary, "-", Vsn/binary, ".tar">>,
+ {ok, PkgContents} = file:read_file(filename:join(?config(data_dir, Config), PkgFile)),
+ meck:new(httpc, [passthrough, unsticky]),
+ meck:expect(httpc, request,
+ fun(get, {_Url, _Opts}, _, _) when GoodCache ->
+ {ok, {{Vsn, 304, <<"Not Modified">>}, [{"etag", ?good_etag}], <<>>}};
+ (get, {_Url, _Opts}, _, _) ->
+ {ok, {{Vsn, 200, <<"OK">>}, [{"etag", ?good_etag}], PkgContents}}
+ end),
+ [{cache_root, CacheRoot},
+ {cache_dir, CacheDir},
+ {tmp_dir, TmpDir},
+ {mock_table, T} | Config].
+
+unmock_config(Config) ->
+ meck:unload(),
+ ets:delete(?config(mock_table, Config)).
+
+copy_to_cache({Pkg,Vsn}, Config) ->
+ Name = <<Pkg/binary, "-", Vsn/binary, ".tar">>,
+ Source = filename:join(?config(data_dir, Config), Name),
+ Dest = filename:join(?config(cache_dir, Config), Name),
+ ec_file:copy(Source, Dest).
diff --git a/test/rebar_pkg_SUITE_data/badindexchk-1.0.0.tar b/test/rebar_pkg_SUITE_data/badindexchk-1.0.0.tar
new file mode 100644
index 0000000..e5b963f
--- /dev/null
+++ b/test/rebar_pkg_SUITE_data/badindexchk-1.0.0.tar
Binary files differ
diff --git a/test/rebar_pkg_SUITE_data/badpkg-1.0.0.tar b/test/rebar_pkg_SUITE_data/badpkg-1.0.0.tar
new file mode 100644
index 0000000..4930cd2
--- /dev/null
+++ b/test/rebar_pkg_SUITE_data/badpkg-1.0.0.tar
Binary files differ
diff --git a/test/rebar_pkg_SUITE_data/goodpkg-1.0.0.tar b/test/rebar_pkg_SUITE_data/goodpkg-1.0.0.tar
new file mode 100644
index 0000000..e5b963f
--- /dev/null
+++ b/test/rebar_pkg_SUITE_data/goodpkg-1.0.0.tar
Binary files differ