1-module(rebar_pkg_alias_SUITE). 2-compile(export_all). 3-include_lib("common_test/include/ct.hrl"). 4-include_lib("eunit/include/eunit.hrl"). 5-include_lib("kernel/include/file.hrl"). 6-include("rebar.hrl"). 7 8all() -> [same_alias, diff_alias, diff_alias_vsn, transitive_alias%% , 9 %% transitive_hash_mismatch 10 ]. 11 12%% {uuid, {pkg, uuid}} = uuid 13%% {uuid, {pkg, alias}} = uuid on disk 14%% another run should yield the same lock file without error 15init_per_suite(Config) -> 16 Config. 17 %% mock_config(?MODULE, Config). 18 19end_per_suite(Config) -> 20 Config. 21 %% unmock_config(Config). 22 23init_per_testcase(same_alias, Config0) -> 24 mock_config(?MODULE, Config0), 25 Config = rebar_test_utils:init_rebar_state(Config0,"same_alias_"), 26 AppDir = ?config(apps, Config), 27 rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]), 28 RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{fakelib, {pkg, fakelib}}]}]), 29 [{rebarconfig, RebarConf} | Config]; 30init_per_testcase(diff_alias, Config0) -> 31 mock_config(?MODULE, Config0), 32 Config = rebar_test_utils:init_rebar_state(Config0,"diff_alias_"), 33 AppDir = ?config(apps, Config), 34 rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]), 35 RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{fakelib, {pkg, goodpkg}}]}]), 36 [{rebarconfig, RebarConf} | Config]; 37init_per_testcase(diff_alias_vsn, Config0) -> 38 mock_config(?MODULE, Config0), 39 Config = rebar_test_utils:init_rebar_state(Config0,"diff_alias_vsn_"), 40 AppDir = ?config(apps, Config), 41 rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]), 42 RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{fakelib, "1.0.0", {pkg, goodpkg}}]}]), 43 [{rebarconfig, RebarConf} | Config]; 44init_per_testcase(transitive_alias, Config0) -> 45 mock_config(?MODULE, Config0), 46 Config = rebar_test_utils:init_rebar_state(Config0,"transitive_alias_vsn_"), 47 AppDir = ?config(apps, Config), 48 rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]), 49 RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{topdep, "1.0.0", {pkg, topdep}}]}]), 50 [{rebarconfig, RebarConf} | Config]; 51init_per_testcase(transitive_hash_mismatch, Config0) -> 52 mock_config(?MODULE, Config0), 53 Config = rebar_test_utils:init_rebar_state(Config0,"transitive_alias_vsn_"), 54 AppDir = ?config(apps, Config), 55 rebar_test_utils:create_app(AppDir, "A", "0.0.0", [kernel, stdlib]), 56 RebarConf = rebar_test_utils:create_config(AppDir, [{deps, [{topdep, "1.0.0", {pkg, topdep}}]}]), 57 [{rebarconfig, RebarConf} | Config]. 58 59end_per_testcase(_, Config) -> 60 unmock_config(Config), 61 Config. 62 63same_alias(Config) -> 64 {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), 65 rebar_test_utils:run_and_check( 66 Config, RebarConfig, ["lock"], 67 {ok, [{lock, "fakelib"}, {dep, "fakelib"}]} 68 ). 69 70diff_alias(Config) -> 71 %% even though the dep is 'fakelib' aliased as 'goodpkg' all 72 %% internal records use 'fakelib' as a value. Just make sure 73 %% the lock actually maintains the proper source as 'goodpkg' 74 AppDir = ?config(apps, Config), 75 Lockfile = filename:join([AppDir, "rebar.lock"]), 76 {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), 77 rebar_test_utils:run_and_check( 78 Config, RebarConfig, ["lock"], 79 {ok, [{lock, "fakelib"},{dep, "fakelib"}]} 80 ), 81 {ok, [{Vsn, LockData}|_]} = file:consult(Lockfile), 82 ?assert(lists:any(fun({<<"fakelib">>,{pkg,<<"goodpkg">>,_},_}) -> true 83 ; (_) -> false end, LockData)), 84 %% An second run yields the same 85 rebar_test_utils:run_and_check( 86 Config, RebarConfig, ["lock"], 87 {ok, [{lock, "fakelib"},{dep, "fakelib"}]} 88 ), 89 {ok, [{Vsn, LockData}|_]} = file:consult(Lockfile), 90 %% So does an upgrade 91 rebar_test_utils:run_and_check( 92 Config, RebarConfig, ["upgrade"], 93 {ok, [{lock, "fakelib"},{dep, "fakelib"}]} 94 ), 95 {ok, [{Vsn, LockData}|_]} = file:consult(Lockfile). 96 97diff_alias_vsn(Config) -> diff_alias(Config). 98 99transitive_alias(Config) -> 100 %% ensure that the apps fetched under transitive aliases are 101 %% locked properly, but also that they are stored in the right 102 %% directory in the build dir to avoid breaking includes and 103 %% static analysis tools that rely on the location to work 104 AppDir = ?config(apps, Config), 105 Lockfile = filename:join([AppDir, "rebar.lock"]), 106 {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), 107 rebar_test_utils:run_and_check( 108 Config, RebarConfig, ["lock"], 109 {ok, [{lock, "topdep"},{dep, "topdep"}, 110 {lock,"transitive_app"},{dep,"transitive_app"}]} 111 ), 112 {ok, [{_Vsn, LockData}|_]} = file:consult(Lockfile), 113 ?assert(lists:any(fun({<<"transitive_app">>,{pkg,<<"transitive">>,_},_}) -> true 114 ; (_) -> false end, LockData)), 115 AppDir = ?config(apps, Config), 116 AliasedName = filename:join([AppDir, "_build", "default", "lib", "transitive_app"]), 117 PkgName = filename:join([AppDir, "_build", "default", "lib", "transitive"]), 118 ?assert(filelib:is_dir(AliasedName)), 119 ?assertNot(filelib:is_dir(PkgName)), 120 %% An second run yields the same 121 rebar_test_utils:run_and_check( 122 Config, RebarConfig, ["lock"], 123 {ok, [{lock, "topdep"},{dep, "topdep"}, 124 {lock,"transitive_app"},{dep,"transitive_app"}]} 125 ), 126 {ok, [{Vsn, LockData}|_]} = file:consult(Lockfile), 127 ?assert(filelib:is_dir(AliasedName)), 128 ?assertNot(filelib:is_dir(PkgName)), 129 %% So does an upgrade 130 rebar_test_utils:run_and_check( 131 Config, RebarConfig, ["upgrade"], 132 {ok, [{lock, "topdep"},{dep, "topdep"}, 133 {lock,"transitive_app"},{dep,"transitive_app"}]} 134 ), 135 {ok, [{Vsn, LockData}|_]} = file:consult(Lockfile), 136 ?assert(filelib:is_dir(AliasedName)), 137 ?assertNot(filelib:is_dir(PkgName)), 138 ok. 139 140transitive_hash_mismatch(Config) -> 141 %% ensure that the apps fetched under transitive aliases are 142 %% locked properly, but also that they are stored in the right 143 %% directory in the build dir to avoid breaking includes and 144 %% static analysis tools that rely on the location to work 145 AppDir = ?config(apps, Config), 146 Lockfile = filename:join([AppDir, "rebar.lock"]), 147 {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), 148 rebar_test_utils:run_and_check( 149 Config, RebarConfig, ["lock"], 150 {ok, [{lock, "topdep"},{dep, "topdep"}, 151 {lock,"transitive_app"},{dep,"transitive_app"}]} 152 ), 153 {ok, [LockData|Attrs]} = file:consult(Lockfile), 154 %% Change Lock hash data to cause a failure next time, but on transitive 155 %% deps only 156 NewLock = [LockData|lists:map( 157 fun([{pkg_hash, Hashes}|Rest]) -> 158 [{pkg_hash, [{<<"transitive_app">>, <<"fakehash">>} 159 | lists:keydelete(<<"transitive_app">>, 1, Hashes)]} 160 | Rest] 161 ; (Attr) -> 162 Attr 163 end, Attrs)], 164 {ok, Io} = file:open(Lockfile, [write]), 165 [io:format(Io, "~p.~n", [Attr]) || Attr <- NewLock], 166 file:close(Io), 167 ct:pal("lock: ~p", [file:consult(Lockfile)]), 168 ec_file:remove(filename:join([AppDir, "_build"]), [recursive]), 169 ?assertMatch( 170 {error, {rebar_fetch, {unexpected_hash, _, _, _}}}, 171 rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], return) 172 ), 173 ok. 174 175parse_deps(Deps) -> 176 [{maps:get(app, D, Name), {pkg, Name, Constraint, undefined}} || D=#{package := Name, 177 requirement := Constraint} <- Deps]. 178 179mock_config(Name, Config) -> 180 {ChkFake, Etag} = create_lib(Name, Config, "fakelib"), 181 {ChkTop, _} = create_lib(Name, Config, "topdep"), 182 {ChkTrans, _} = create_lib(Name, Config, "transitive_app", "transitive"), 183 ct:pal("{~p, _}",[ChkTop]), 184 ct:pal("{~p, _}",[ChkTrans]), 185 Priv = ?config(priv_dir, Config), 186 TmpDir = filename:join([Priv, "tmp", atom_to_list(Name)]), 187 %% Add an alias for goodpkg -> fakelib by hand 188 AppDir = filename:join([Priv, "fakelib"]), 189 CacheRoot = filename:join([Priv, "cache", atom_to_list(Name)]), 190 CacheDir = filename:join([CacheRoot, "hex", "com", "test", "packages"]), 191 rebar_test_utils:create_app(AppDir, "fakelib", "1.0.0", [kernel, stdlib]), 192 ct:pal("{~p, ~p}",[ChkFake, Etag]), 193 {ChkGood, EtagGood} = rebar_test_utils:package_app(AppDir, CacheDir, "goodpkg", "1.0.0"), 194 195 AllDeps = [ 196 {<<"fakelib">>,[[<<"1.0.0">>]]}, 197 {<<"goodpkg">>,[[<<"1.0.0">>]]}, 198 {<<"topdep">>,[[<<"1.0.0">>]]}, 199 {<<"transitive">>, [[<<"1.0.0">>]]}, 200 {{<<"fakelib">>,<<"1.0.0">>}, [[], ChkFake, [<<"rebar3">>]]}, 201 {{<<"goodpkg">>,<<"1.0.0">>}, [[], ChkGood, [<<"rebar3">>]]}, 202 {{<<"topdep">>,<<"1.0.0">>}, 203 [[ 204 {<<"transitive">>, <<"1.0.0">>, false, <<"transitive_app">>} 205 ], ChkTop, [<<"rebar3">>]]}, 206 {{<<"transitive">>,<<"1.0.0">>}, [[], ChkTrans, [<<"rebar3">>]]} 207 ], 208 Tid = ets:new(registry_table, [public]), 209 ets:insert_new(Tid, AllDeps), 210 ok = ets:tab2file(Tid, filename:join([CacheDir, "registry"])), 211 %% ets:delete(Tid), 212 %% The state returns us a fake registry 213 meck:new(rebar_dir, [passthrough, no_link]), 214 meck:expect(rebar_dir, global_cache_dir, fun(_) -> CacheRoot end), 215 216 meck:new(rebar_packages, [passthrough, no_link]), 217 meck:expect(rebar_packages, registry_dir, fun(_) -> {ok, CacheDir} end), 218 meck:expect(rebar_packages, package_dir, fun(_, _) -> {ok, CacheDir} end), 219 220 %% TODO: is something else wrong that we need this for transitive_alias to pass 221 meck:expect(rebar_packages, update_package, fun(_, _, _) -> ok end), 222 223 meck:new(rebar_prv_update, [passthrough]), 224 meck:expect(rebar_prv_update, do, fun(State) -> {ok, State} end), 225 226 catch ets:delete(?PACKAGE_TABLE), 227 rebar_packages:new_package_table(), 228 229 lists:foreach(fun({{N, Vsn}, [Deps, Checksum, _]}) -> 230 case ets:member(?PACKAGE_TABLE, {ec_cnv:to_binary(N), Vsn, <<"hexpm">>}) of 231 false -> 232 ets:insert(?PACKAGE_TABLE, #package{key={ec_cnv:to_binary(N), ec_semver:parse(Vsn), <<"hexpm">>}, 233 dependencies=[{DAppName, {pkg, DN, DV, undefined}} || {DN, DV, _, DAppName} <- Deps], 234 retired=false, 235 outer_checksum=Checksum}); 236 true -> 237 ok 238 end; 239 ({_N, _Vsns}) -> 240 ok 241 242 end, AllDeps), 243 244 meck:new(r3_hex_repo, [passthrough]), 245 meck:expect(r3_hex_repo, get_package, 246 fun(_Config, PkgName) -> 247 Matches = ets:match_object(Tid, {{PkgName,'_'}, '_'}), 248 Releases = 249 [#{outer_checksum => Checksum, 250 version => Vsn, 251 dependencies => [{DAppName, {pkg, DN, DV, undefined}} || 252 {DN, DV, _, DAppName} <- Deps]} || 253 {{_, Vsn}, [Deps, Checksum, _]} <- Matches], 254 {ok, {200, #{}, Releases}} 255 end), 256 257 meck:expect(r3_hex_repo, get_tarball, fun(_, _, _) -> 258 {ok, {304, #{<<"etag">> => EtagGood}, <<>>}} 259 end), 260 261 %% Move all packages to cache 262 NewConf = [{cache_root, CacheRoot}, 263 {cache_dir, CacheDir}, 264 {tmp_dir, TmpDir}, 265 {mock_table, Tid} | Config], 266 NewConf. 267 268unmock_config(Config) -> 269 meck:unload(), 270 Config. 271 272create_lib(Name, Config, PkgName) -> 273 create_lib(Name, Config, PkgName, PkgName). 274 275create_lib(Name, Config, AppName, PkgName) -> 276 Priv = ?config(priv_dir, Config), 277 AppDir = filename:join([Priv, PkgName]), 278 CacheRoot = filename:join([Priv, "cache", atom_to_list(Name)]), 279 CacheDir = filename:join([CacheRoot, "hex", "com", "test", "packages"]), 280 filelib:ensure_dir(filename:join([CacheDir, "registry"])), 281 rebar_test_utils:create_app(AppDir, AppName, "1.0.0", [kernel, stdlib]), 282 rebar_test_utils:package_app(AppDir, CacheDir, PkgName, "1.0.0"). 283