diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml new file mode 100644 index 0000000..460848c --- /dev/null +++ b/.github/workflows/CI.yaml @@ -0,0 +1,25 @@ +name: Erlang CI + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + container: + image: erlang:26 + + steps: + - uses: actions/checkout@v4 + - name: Compile + run: rebar3 compile + - name: Run tests + run: rebar3 eunit diff --git a/Makefile b/Makefile index 184ac1f..df59c63 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,5 @@ TARGET_DIR=ebin -COMPILE_OPTIONS=+debug_info +export_all -TEST_COMPILE_OPTIONS=+debug_info +export_all - REBAR3=/usr/bin/env rebar3 default: compile @@ -10,25 +7,14 @@ default: compile all: compile test compile: - $(RM) ebin + $(RM) ${TARGET_DIR} $(REBAR3) escriptize - ln -s _build/default/bin/ ebin + ln -s _build/default/bin/ ${TARGET_DIR} clean: $(REBAR3) clean test: FORCE - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/check_equiv.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/equivchecker_utils.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/typing.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/slicing.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/functions.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} test/scoping_tests.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} test/typing_tests.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/config.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/diff.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/equivchecker_testing.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/repo.erl - erlc ${TEST_COMPILE_OPTIONS} -I include -o ${TARGET_DIR} src/cli.erl - erl -eval 'scoping_tests:test(), typing_tests:test(), init:stop()' -noshell + $(REBAR3) eunit + FORCE: diff --git a/README.md b/README.md index 8b9edde..b507eab 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ - Uses property based testing to check the equivalence of code refactored by Wrangler - Compares folders or commits of the same codebase with the original and refactored code (git) +## Disclaimer + +Randomized testing in itself can never be sufficient for eliminating the possibility of bugs. +EquivcheckEr will never report false positives, but its entirely possible for it to omit true positives, +so it cannot replace engineering discipline. +As Dijkstra once said: +> "Testing shows the presence, not the absence of bugs" + ## Dependencies - [Wrangler](https://refactoringtools.github.io/docs/wrangler/) diff --git a/rebar.config b/rebar.config index c5a7262..b2682ec 100644 --- a/rebar.config +++ b/rebar.config @@ -2,8 +2,8 @@ {deps, [ jsone, proper, - {wrangler, {git, "git@github.com:RefactoringTools/wrangler.git", {branch, "master"}}}, - {parse_trans, {git, "git@github.com:uwiger/parse_trans.git", {branch, "master"}}} + {wrangler, {git, "https://github.com/RefactoringTools/wrangler.git", {branch, "master"}}}, + {parse_trans, {git, "https://github.com/uwiger/parse_trans.git", {branch, "master"}}} ] }. diff --git a/rebar.lock b/rebar.lock index 0387cb7..2f9adc4 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,19 +1,19 @@ {"1.2.0", -[{<<"jsone">>,{pkg,<<"jsone">>,<<"1.8.0">>},0}, +[{<<"jsone">>,{pkg,<<"jsone">>,<<"1.8.1">>},0}, {<<"parse_trans">>, - {git,"git@github.com:uwiger/parse_trans.git", - {ref,"cdb01ba260ba9a00b2aafa17affead0f6fac081c"}}, + {git,"https://github.com/uwiger/parse_trans.git", + {ref,"d99fb36755c813a5db23e6f93741aa58323ef911"}}, 0}, {<<"proper">>,{pkg,<<"proper">>,<<"1.4.0">>},0}, {<<"wrangler">>, - {git,"git@github.com:RefactoringTools/wrangler.git", - {ref,"e8b0159e136768f81a5ffb6d6d6ddd79ae55b007"}}, + {git,"https://github.com/RefactoringTools/wrangler.git", + {ref,"0fac2d415952bc764dd4f283b89fa3c6fd2b39f1"}}, 0}]}. [ {pkg_hash,[ - {<<"jsone">>, <<"347FF1FA700E182E1F9C5012FA6D737B12C854313B9AE6954CA75D3987D6C06D">>}, + {<<"jsone">>, <<"6BC74D3863D55D420077346DA97C601711017A057F2FD1DF65D6D65DD562FBAB">>}, {<<"proper">>, <<"89A44B8C39D28BB9B4BE8E4D715D534905B325470F2E0EC5E004D12484A79434">>}]}, {pkg_hash_ext,[ - {<<"jsone">>, <<"08560B78624A12E0B5E7EC0271EC8CA38EF51F63D84D84843473E14D9B12618C">>}, + {<<"jsone">>, <<"C78918124148C51A7A84C678E39BBC6281F8CB582F1D88584628A98468E99738">>}, {<<"proper">>, <<"18285842185BD33EFBDA97D134A5CB5A0884384DB36119FEE0E3CFA488568CBB">>}]} ]. diff --git a/src/check_equiv.erl b/src/check_equiv.erl index af31012..fcc2766 100644 --- a/src/check_equiv.erl +++ b/src/check_equiv.erl @@ -54,13 +54,16 @@ get_typeinfo(Dir) -> TyperOut = os:cmd("typer -I include -r " ++ Dir), typing:types(TyperOut). -check_equiv(OrigDir, RefacDir) -> +check_equiv(OrigDir, RefacDir, IsVerbose) -> Configs = config:load_config(), + equivchecker_utils:log(IsVerbose, "Loaded config", Configs), typing:ensure_plt(Configs), DiffOutput = os:cmd("diff -x '.git' -u0 -br " ++ OrigDir ++ " " ++ RefacDir), Diffs = diff:diff(DiffOutput), + equivchecker_utils:log(IsVerbose, "Diffs found", Diffs), ModFiles = diff:modified_files(Diffs), + equivchecker_utils:log(IsVerbose, "Modified files", ModFiles), % TODO Compile everything for now % Files = string:split(string:trim(os:cmd("find -name '*.erl'")), "\n", all), @@ -71,12 +74,15 @@ check_equiv(OrigDir, RefacDir) -> ModFiles), {OrigTypeInfo, RefacTypeInfo} = {get_typeinfo(OrigDir), get_typeinfo(RefacDir)}, {OrigModFuns, RefacModFuns} = functions:modified_functions(Diffs, FileInfos), + equivchecker_utils:log(IsVerbose, "Original functions", OrigModFuns), + equivchecker_utils:log(IsVerbose, "Refactored functions", RefacModFuns), CallGraph = functions:callgraph(OrigDir, RefacDir), Types = typing:add_types(OrigTypeInfo, RefacTypeInfo), % Gets back the functions that have to be tested FunsToTest = slicing:scope(OrigModFuns, RefacModFuns, CallGraph, Types), + equivchecker_utils:log(IsVerbose, "Testing functions", RefacModFuns), % Compile the necessary modules into two separate folders % This is needed because QuickCheck has to evaluate to old and the new @@ -87,19 +93,23 @@ check_equiv(OrigDir, RefacDir) -> RefacFiles = lists:map(fun(File) -> filename:join([RefacDir, File]) end, ModFiles), Seed = os:timestamp(), % seed for the PropEr generator + equivchecker_utils:log(IsVerbose, "Seed used for test generation", Seed), + compile(OrigFiles, ?ORIGINAL_BIN_FOLDER, Seed), compile(RefacFiles, ?REFACTORED_BIN_FOLDER, Seed), unzip_modules(), {OrigNode, RefacNode} = start_nodes(), + equivchecker_utils:log(IsVerbose, "Started testing nodes"), % This is needed because PropEr needs the source for constructing the generator {ok, Dir} = file:get_cwd(), file:set_cwd(OrigDir), - Result = equivchecker_testing:run_tests(FunsToTest, OrigNode, RefacNode, Types, CallGraph), + Result = equivchecker_testing:run_tests(FunsToTest, OrigNode, RefacNode, Types, CallGraph, IsVerbose), file:set_cwd(Dir), stop_nodes(OrigNode, RefacNode), + equivchecker_utils:log(IsVerbose, "Stopped testing nodes"), Result. diff --git a/src/cli.erl b/src/cli.erl index 3bfa79f..ec6da0f 100644 --- a/src/cli.erl +++ b/src/cli.erl @@ -5,62 +5,91 @@ -export([run/1]). -spec handler(map()) -> none(). -handler(#{target := Target, source := Source, json := Json, commit := Commit, stats := Stats}) when Commit -> - commit_to_commit(Source, Target, Json, Stats); -handler(#{target := Target, source := Source, json := Json, commit := Commit, stats := Stats}) when not Commit -> - folder_to_folder(Source, Target, Json, Stats); -handler(#{target := Target, json := Json, commit := Commit, stats := Stats}) when Commit -> - folder_to_commit(Target,Json,Stats); -handler(#{target := Target, json := Json, commit := Commit, stats := Stats}) when not Commit -> - folder_to_folder(Target, Json, Stats); -handler(#{json := Json, commit := _, stats := Stats}) -> - folder_to_commit(Json,Stats). - --spec commit_to_commit(commit(), commit(), boolean(), boolean()) -> none(). -commit_to_commit(RefacCommit, OrigCommit, Json, Stats) -> - not Json andalso io:format("Checking commit ~p against commit ~p~n", [OrigCommit, RefacCommit]), +handler(#{target := Target, + source := Source, + json := Json, + commit := Commit, + stats := Stats, + verbose := IsVerbose}) + when Commit -> + commit_to_commit(Source, Target, Json, Stats, IsVerbose); +handler(#{target := Target, + source := Source, + json := Json, + commit := Commit, + stats := Stats, + verbose := IsVerbose}) + when not Commit -> + folder_to_folder(Source, Target, Json, Stats, IsVerbose); +handler(#{target := Target, + json := Json, + commit := Commit, + stats := Stats, + verbose := IsVerbose}) + when Commit -> + folder_to_commit(Target, Json, Stats, IsVerbose); +handler(#{target := Target, + json := Json, + commit := Commit, + stats := Stats, + verbose := IsVerbose}) + when not Commit -> + folder_to_folder(Target, Json, Stats, IsVerbose); +handler(#{json := Json, + commit := _, + stats := Stats, + verbose := IsVerbose}) -> + folder_to_commit(Json, Stats, IsVerbose). + +-spec commit_to_commit(commit(), commit(), boolean(), boolean(), boolean()) -> none(). +commit_to_commit(RefacCommit, OrigCommit, Json, Stats, IsVerbose) -> + not Json + andalso io:format("Checking commit ~p against commit ~p~n", [OrigCommit, RefacCommit]), {ok, ProjFolder} = file:get_cwd(), Original = repo:copy(ProjFolder, ?ORIGINAL_SOURCE_FOLDER), repo:checkout(Original, OrigCommit), Refactored = repo:copy(ProjFolder, ?REFACTORED_SOURCE_FOLDER), repo:checkout(Refactored, RefacCommit), - run_check(Original, Refactored, Json, Stats). + run_check(Original, Refactored, Json, Stats, IsVerbose). --spec folder_to_folder(filename(), filename(), boolean(), boolean()) -> none(). -folder_to_folder(Refactored, Original, Json, Stats) -> - not Json andalso io:format("Checking folder ~p against folder ~p~n", [Original, Refactored]), - run_check(Original, Refactored, Json, Stats). +-spec folder_to_folder(filename(), filename(), boolean(), boolean(), boolean()) -> none(). +folder_to_folder(Refactored, Original, Json, Stats, IsVerbose) -> + not Json + andalso io:format("Checking folder ~p against folder ~p~n", [Original, Refactored]), + run_check(Original, Refactored, Json, Stats, IsVerbose). --spec folder_to_folder(filename(), boolean(), boolean()) -> none(). -folder_to_folder(Original, Json, Stats) -> +-spec folder_to_folder(filename(), boolean(), boolean(), boolean()) -> none(). +folder_to_folder(Original, Json, Stats, IsVerbose) -> not Json andalso io:format("Checking current folder against ~p~n", [Original]), {ok, Refactored} = file:get_cwd(), - run_check(Original, Refactored, Json, Stats). + run_check(Original, Refactored, Json, Stats, IsVerbose). --spec folder_to_commit(commit(), boolean(), boolean()) -> none(). -folder_to_commit(Commit, Json, Stats) -> +-spec folder_to_commit(commit(), boolean(), boolean(), boolean()) -> none(). +folder_to_commit(Commit, Json, Stats, IsVerbose) -> not Json andalso io:format("Checking current folder against commit ~p~n", [Commit]), {ok, Refactored} = file:get_cwd(), Original = repo:copy(Refactored, ?ORIGINAL_SOURCE_FOLDER), repo:checkout(Original, Commit), - run_check(Original, Refactored, Json, Stats). + run_check(Original, Refactored, Json, Stats, IsVerbose). --spec folder_to_commit(boolean(), boolean()) -> none(). -folder_to_commit(Json, Stats) -> +-spec folder_to_commit(boolean(), boolean(), boolean()) -> none(). +folder_to_commit(Json, Stats, IsVerbose) -> not Json andalso io:format("Checking current folder against current commit~n"), {ok, Refactored} = file:get_cwd(), Commit = repo:current_commit(), Original = repo:copy(Refactored, ?ORIGINAL_SOURCE_FOLDER), repo:checkout(Original, Commit), - run_check(Original, Refactored, Json, Stats). + run_check(Original, Refactored, Json, Stats, IsVerbose). --spec run_check(filename(), filename(), boolean(), boolean()) -> none(). -run_check(Original, Refactored, Json, Stats) -> - Res = check_equiv:check_equiv(filename:absname(Original), filename:absname(Refactored)), +-spec run_check(filename(), filename(), boolean(), boolean(), boolean()) -> none(). +run_check(Original, Refactored, Json, Stats, IsVerbose) -> + Res = check_equiv:check_equiv( + filename:absname(Original), filename:absname(Refactored), IsVerbose), show_result(Res, Json, Stats). setup() -> % Sets the name of the master node + os:cmd("epmd"), net_kernel:start(master, #{name_domain => shortnames}), % These are needed for storing statistics @@ -88,28 +117,30 @@ run(Args) -> % Second arg turns on json output, third shows statistics -spec show_result([{filename(), mfa(), [any()]}], boolean(), boolean()) -> none(). show_result(Result, false, false) -> - Formatted = format_results(Result,false), + Formatted = format_results(Result, false), io:format("Results: ~p~n", [Formatted]); show_result(Result, false, true) -> {FailCounts, Average} = equivchecker_utils:statistics(), - Formatted = format_results(Result,false), + Formatted = format_results(Result, false), io:format("Results: ~p~n", [Formatted]), io:format("Number of functions that failed: ~p~n", [length(FailCounts)]), io:format("Average no. tries before counterexample is found: ~p~n", [Average]); show_result(Result, true, false) -> - Formatted = format_results(Result,true), - io:format("~s\n", [jsone:encode(Formatted,[{indent, 2}, {space, 1}])]); + Formatted = format_results(Result, true), + io:format("~s\n", [jsone:encode(Formatted, [{indent, 2}, {space, 1}])]); show_result(Result, true, true) -> {FailCounts, Average} = equivchecker_utils:statistics(), Stats = #{failed_count => length(FailCounts), average_test_count => Average}, - Output = #{statistics => Stats, results => format_results(Result,true)}, - io:format("~s\n", [jsone:encode(Output,[{indent, 2}, {space, 1}])]). + Output = #{statistics => Stats, results => format_results(Result, true)}, + io:format("~s\n", [jsone:encode(Output, [{indent, 2}, {space, 1}])]). -spec format_results([{filename(), mfa(), [any()]}], boolean()) -> none(). format_results(Results, Json) -> case Json of - true -> lists:map(fun format_json/1, Results); - false -> lists:map(fun format_stdout/1, Results) + true -> + lists:map(fun format_json/1, Results); + false -> + lists:map(fun format_stdout/1, Results) end. % Default formatting @@ -120,4 +151,6 @@ format_stdout({FileName, MFA, [CounterExample]}) -> % Format the output to json -spec format_json({filename(), mfa(), [any()]}) -> map(). format_json({FileName, MFA, [CounterExample]}) -> - #{filename => erlang:list_to_atom(FileName), mfa => MFA, counterexample => CounterExample}. + #{filename => erlang:list_to_atom(FileName), + mfa => MFA, + counterexample => CounterExample}. diff --git a/src/diff.erl b/src/diff.erl index 1a4ef81..45d047e 100644 --- a/src/diff.erl +++ b/src/diff.erl @@ -1,6 +1,6 @@ %% This module is for extracting information from the diff, and presenting it %% The main unit of the differences between two versions is a hunk, which contains -%% all the information about a change that is needed for finding out what has to be testsd +%% all the information about a change that is needed for finding out what has to be tested -module(diff). -include("equivchecker.hrl"). @@ -74,13 +74,13 @@ parse_diff(DiffStr) -> -spec extract_file(string()) -> filename(). extract_file(DiffLine) -> Options = [global, {capture, [1,2], list}], - {match, [[OrigFile, RefacFile]]} = re:run(DiffLine, ".*?(/.*?\.erl).*?(/.*?\.erl)", Options), + {match, [[OrigFile, RefacFile]]} = re:run(DiffLine, ".*?(/.*?\\.erl).*?(/.*?\\.erl)", Options), equivchecker_utils:common_filename_postfix(OrigFile, RefacFile). % Checks if the given file in the diff output is erlang source code -spec is_erl_source([diff_line()]) -> boolean(). is_erl_source(Header) -> - case re:run(Header, ".*(.*\.erl).*") of + case re:run(Header, ".*(.*\\.erl).*") of {match,_} -> true; nomatch -> false end. diff --git a/src/equivchecker.erl b/src/equivchecker.erl index 2188c4a..537a48f 100644 --- a/src/equivchecker.erl +++ b/src/equivchecker.erl @@ -18,7 +18,8 @@ cli() -> #{name => source, required => false}, #{name => json, type => boolean, short => $j, long => "-json", default => false}, #{name => commit, type => boolean, short => $c, long => "-commit", default => false}, - #{name => stats, type => boolean, short => $s, long => "-statistics", default => false} + #{name => stats, type => boolean, short => $s, long => "-statistics", default => false}, + #{name => verbose, type => boolean, short => $v, long => "-verbose", default => false} ], handler => fun cli:run/1 }. diff --git a/src/equivchecker_testing.erl b/src/equivchecker_testing.erl index 0e8bce5..b26e4a5 100644 --- a/src/equivchecker_testing.erl +++ b/src/equivchecker_testing.erl @@ -2,8 +2,8 @@ -type type() :: string(). --export([run_tests/5, - run_tests/6, +-export([run_tests/6, + run_tests/7, eval_proc/3, eval_func/4]). @@ -11,12 +11,12 @@ -include_lib("proper/include/proper.hrl"). -run_tests(Functions, OrigNode, RefacNode, Types, CallGraph) -> - run_tests(Functions, OrigNode, RefacNode, Types, CallGraph, []). +run_tests(Functions, OrigNode, RefacNode, Types, CallGraph, IsVerbose) -> + run_tests(Functions, OrigNode, RefacNode, Types, CallGraph, [], IsVerbose). -run_tests([], _, _, _, _, Results) -> +run_tests([], _, _, _, _, Results, _) -> Results; -run_tests(Functions, OrigNode, RefacNode, Types, CallGraph, Results) -> +run_tests(Functions, OrigNode, RefacNode, Types, CallGraph, Results, IsVerbose) -> % proper:quickchek/2 stops the server, so it has to be started every time proper_typeserver:start(), ProperOpts = [long_result, {on_output, fun(X,Y) -> equivchecker_utils:count_tests(X,Y) end}], @@ -33,12 +33,15 @@ run_tests(Functions, OrigNode, RefacNode, Types, CallGraph, Results) -> Res = collect_results(length(FunctionsTyped), []), FailedFuns = lists:filter(fun({_, _, Eq}) -> Eq =/= true end, Res), + equivchecker_utils:log(IsVerbose, "Failed functions: ", FailedFuns), + FailedMFA = lists:map(fun({FileName, {M,F,A}, _}) -> {FileName, {M, F, A}} end, FailedFuns), Callers = lists:uniq(lists:flatmap(fun({FileName, {_,F,A}}) -> CallGraph({FileName, F, A}, refactored) end, FailedMFA)), + equivchecker_utils:log(IsVerbose, "Callers of failed functions: ", Callers), CallersTyped = lists:map(fun({FileName, MFA}) -> {FileName, MFA, Types(MFA, refactored)} end, Callers), - run_tests(CallersTyped, OrigNode, RefacNode, Types, CallGraph, FailedFuns ++ Results). + run_tests(CallersTyped, OrigNode, RefacNode, Types, CallGraph, FailedFuns ++ Results, IsVerbose). collect_results(0, Res) -> Res; collect_results(Num, Res) -> diff --git a/src/equivchecker_utils.erl b/src/equivchecker_utils.erl index 8c662f0..9ca760f 100644 --- a/src/equivchecker_utils.erl +++ b/src/equivchecker_utils.erl @@ -36,7 +36,7 @@ args([H|T], Acc) when [H] =:= "[" -> bracket(T, "[" ++ Acc, 0); args([H|T], Acc) -> args(T, Acc ++ [H]). paren([H|T], Acc, Level) when [H] =:= "(" -> paren(T, Acc ++ "(", Level + 1); -paren([H|[_|T]], Acc, Level) when ([H] =:= ")") and (Level =:= 0) -> args(T, Acc ++ ") "); +paren([H|[_|T]], Acc, Level) when ([H] =:= ")") and (Level =:= 0) -> [Acc ++ ")"|args(T, "")]; paren([H|[]], Acc, Level) when ([H] =:= ")") and (Level =:= 0) -> [Acc ++ ")"]; paren([H|T], Acc, Level) when ([H] =:= ")") and (Level =/= 0) -> paren(T, Acc ++ ")", Level - 1); paren([H|T], Acc, Level) -> paren(T, Acc ++ [H], Level). @@ -141,3 +141,15 @@ remove_pid(Pid) when is_pid(Pid) -> pid; remove_pid(Item) -> Item. + +log(IsVerbose, Message) -> + if IsVerbose -> + io:format(Message ++ "~n", []); + true -> ok + end. +log(IsVerbose, Message, Term) -> + if IsVerbose -> + io:format(Message, []), + io:format(": ~p~n", [Term]); + true -> ok + end. diff --git a/src/functions.erl b/src/functions.erl index b4c797e..01b52aa 100644 --- a/src/functions.erl +++ b/src/functions.erl @@ -112,7 +112,7 @@ callgraph(OrigDir, RefacDir) -> -spec find_callers({filename(), atom(), arity()}, string()) -> [{filename(), mfa()}]. find_callers({FileName, FunName, Arity}, Dir) -> AbsDir = filename:absname(Dir), - AbsFileName = AbsDir ++ "/" ++ filename:basename(FileName), + AbsFileName = AbsDir ++ "/" ++ FileName, Leader = equivchecker_utils:disable_output(), {_, Funs} = wrangler_code_inspector_lib:calls_to_fun_1(AbsFileName, FunName, Arity, [AbsDir], 4), equivchecker_utils:enable_output(Leader), diff --git a/test/diff_tests.erl b/test/diff_tests.erl new file mode 100644 index 0000000..a114069 --- /dev/null +++ b/test/diff_tests.erl @@ -0,0 +1,42 @@ +-module(diff_tests). + +-import(diff, + [extract_file/1, extract_lines/1, parse_diff/1, extract_line/1, extract_file/1]). + +-include_lib("eunit/include/eunit.hrl"). + +diff_output() -> + equivchecker_utils:read("test_data/diff_output/diff.txt"). + +diff_files() -> + equivchecker_utils:read("test_data/diff_output/diff_files.txt"). + +extract_hunks_right_number_of_files_test() -> + HunksByFile = parse_diff(diff_output()), + ?assertEqual(length(HunksByFile), 7). + +extract_hunks_right_number_of_hunks_test() -> + HunksByFile = parse_diff(diff_output()), + AllHunks = lists:flatmap(fun({_, Hunks}) -> Hunks end, HunksByFile), + ?assertEqual(length(AllHunks), 95). + +extract_hunks_right_number_of_linenums_test() -> + HunksByFile = parse_diff(diff_output()), + AllHunks = lists:flatmap(fun({_, Hunks}) -> Hunks end, HunksByFile), + LineNums = lists:map(fun(Line) -> extract_line(Line) end, AllHunks), + ?assertEqual(length(LineNums), 95). + +extract_filenames_test() -> + FileNames = + lists:map(fun(DiffLine) -> extract_file(DiffLine) end, + string:split( + string:trim(diff_files()), "\n", all)), + Expected = + ["/a/test2.erl", + "/test2.erl", + "/test3.erl", + "/test4.erl", + "/test5.erl", + "/test6.erl", + "/test.erl"], + ?assertEqual(FileNames, Expected). diff --git a/test/scoping_tests.erl b/test/scoping_tests.erl index dd3386e..20acd33 100644 --- a/test/scoping_tests.erl +++ b/test/scoping_tests.erl @@ -9,30 +9,13 @@ -include_lib("eunit/include/eunit.hrl"). -get_name_test_() -> - [?_assertEqual("some_name", get_name("some_name()")), - ?_assertEqual("some_name", get_name("some_name(args)")), - ?_assertEqual("some_name", get_name("some_name(some,args)"))]. +-import(equivchecker_utils, [read/1]). +sources() -> + {ok, Filenames} = file:list_dir("test_data/sources"), + Paths = lists:map(fun(Filename) -> "test_data/sources/" ++ Filename end, Filenames), + lists:map(fun equivchecker_utils:read/1, Paths). -get_arity_test_() -> - [?_assertEqual(1, get_arity("some_name()")), - ?_assertEqual(1, get_arity("some_name(args)")), - ?_assertEqual(2, get_arity("some_name(some,args)"))]. - -get_module_test_() -> - [?_assertEqual(test1, get_module("test1.erl")), - ?_assertEqual(test2, get_module("some/path/test2.erl")), - ?_assertEqual(test3, get_module("some/longer/path/test3.erl"))]. - -extract_file_test_() -> - [?_assertEqual("test.erl", extract_file("a/test.erl b/test.erl")), - ?_assertEqual("test2.erl", extract_file("a/test2.erl b/test2.erl"))]. - -is_function_def_test_() -> - [?_assert(is_function_def("-f_old(Xs) -> lists:sum(Xs).")), - ?_assert(is_function_def("+f_new(Xs) ->")), - ?_assert(is_function_def("-some_fun(some,args) ->")), - ?_assertNot(is_function_def("+-spec f_new(list(integer())) -> integer().")), - ?_assertNot(is_function_def("+ f_new([X]) + 4.")), - ?_assertNot(is_function_def("-type fun_info() :: {atom(), string(), integer()}.")), - ?_assertNot(is_function_def("-compile(export_all)."))]. +specs() -> + {ok, Filenames} = file:list_dir("test_data/specs"), + Paths = lists:map(fun(Filename) -> "test_data/specs/" ++ Filename end, Filenames), + lists:map(fun equivchecker_utils:read/1, Paths). diff --git a/test/typing_tests.erl b/test/typing_tests.erl index e05dbea..1b355ba 100644 --- a/test/typing_tests.erl +++ b/test/typing_tests.erl @@ -7,39 +7,18 @@ -include_lib("eunit/include/eunit.hrl"). -source() -> -"-module(test3). --compile(export_all). % Exports all functions - -i(Xs,X) -> test2:g(Xs) + test2:h(X). - --spec p() -> integer(). -p(Xs) -> - test:f_new(Xs) - 4. - --spec q(list(integer())) -> integer(). -q(Xs) -> - X = 4, - test:f_new(Xs) + X. - --spec r(integer(),string()) -> integer(). -r(X) -> - test:f_new([X]) + 4.". - -specs() -> +valid_specs() -> ["-spec p() -> integer().", "-spec q(list(integer())) -> integer().", - "-spec r(integer(),string()) -> integer()."]. - -get_specs_test() -> - ?assertEqual(specs(), get_specs(source())). - -get_args_test_() -> - [?_assertEqual([], get_args(source(), "p", 0)), - ?_assertEqual(["list(integer())"], get_args(source(), "q", 1)), - ?_assertEqual(["integer()","string()"], get_args(source(), "r", 2))]. - -parse_spec_test_() -> - [?_assertEqual({"p", []}, parse_spec("-spec p() -> integer().")), - ?_assertEqual({"q", ["list(integer())"]}, parse_spec("-spec q(list(integer())) -> integer().")), - ?_assertEqual({"r", ["integer()","string()"]}, parse_spec("-spec r(integer(),string()) -> integer()."))]. + "-spec r(integer(),string()) -> integer()." + ]. + +expected_specs() -> + [ + {p, []}, + {q, ["list(integer())"]}, + {r, ["integer()", "string()"]} + ]. + +parse_spec_test() -> + ?assertEqual(expected_specs(), lists:map(fun typing:parse_spec/1, valid_specs())). diff --git a/test_data/diff_output/diff.txt b/test_data/diff_output/diff.txt new file mode 100644 index 0000000..f1cfea4 --- /dev/null +++ b/test_data/diff_output/diff.txt @@ -0,0 +1,313 @@ +diff -x .git -u0 -br orig/a/test2.erl refac/a/test2.erl +--- orig/a/test2.erl 2023-09-21 07:55:39.855466036 +0200 ++++ refac/a/test2.erl 2023-09-21 07:55:48.518897278 +0200 +@@ -7 +7 @@ +- test:f_old(Xs) + 3. ++ test:f_new(Xs) + 3. +@@ -11 +11 @@ +- test:f_old([1,2,X]) + 4. ++ test:f_new([1,2,X]) + 4. +@@ -15 +15 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -20 +20 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -24 +24 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +Only in refac: orig@T480 +Only in refac: refac@T480 +diff -x .git -u0 -br orig/test2.erl refac/test2.erl +--- orig/test2.erl 2023-09-04 07:51:01.593484919 +0200 ++++ refac/test2.erl 2023-09-11 09:33:09.680562679 +0200 +@@ -7 +7 @@ +- test:f_old(Xs) + 3. ++ test:f_new(Xs) + 3. +@@ -11 +11 @@ +- test:f_old([1,2,X]) + 4. ++ test:f_new([1,2,X]) + 4. +@@ -15 +15 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -20 +20 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -24 +24 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +diff -x .git -u0 -br orig/test3.erl refac/test3.erl +--- orig/test3.erl 2023-09-04 07:51:01.593484919 +0200 ++++ refac/test3.erl 2023-09-11 09:33:09.680562679 +0200 +@@ -9 +9 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -14 +14 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -18 +18 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +diff -x .git -u0 -br orig/test4.erl refac/test4.erl +--- orig/test4.erl 2023-09-04 07:51:01.593484919 +0200 ++++ refac/test4.erl 2023-09-11 09:33:09.680562679 +0200 +@@ -7 +7 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -12 +12 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -16 +16 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -20 +20 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -25 +25 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -29 +29 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -33 +33 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -38 +38 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -42 +42 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -46 +46 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -51 +51 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -55 +55 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -59 +59 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -64 +64 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -68 +68 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -72 +72 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -77 +77 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -81 +81 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -85 +85 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -90 +90 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -94 +94 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -98 +98 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -103 +103 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -107 +107 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -111 +111 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -115 +115 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +diff -x .git -u0 -br orig/test5.erl refac/test5.erl +--- orig/test5.erl 2023-09-04 07:51:01.593484919 +0200 ++++ refac/test5.erl 2023-09-11 09:33:09.680562679 +0200 +@@ -7 +7 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -12 +12 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -16 +16 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -20 +20 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -25 +25 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -29 +29 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -33 +33 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -38 +38 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -42 +42 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -46 +46 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -51 +51 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -55 +55 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -59 +59 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -64 +64 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -68 +68 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -72 +72 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -77 +77 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -81 +81 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -85 +85 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -90 +90 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -94 +94 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -98 +98 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -103 +103 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -107 +107 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -111 +111 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -115 +115 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +diff -x .git -u0 -br orig/test6.erl refac/test6.erl +--- orig/test6.erl 2023-09-04 07:51:01.593484919 +0200 ++++ refac/test6.erl 2023-09-11 09:33:09.680562679 +0200 +@@ -7 +7 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -12 +12 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -16 +16 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -20 +20 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -25 +25 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -29 +29 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -33 +33 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -38 +38 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -42 +42 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -46 +46 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -51 +51 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -55 +55 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -59 +59 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -64 +64 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -68 +68 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -72 +72 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -77 +77 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -81 +81 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -85 +85 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -90 +90 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -94 +94 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -98 +98 @@ +- test:f_old(Xs) - 4. ++ test:f_new(Xs) - 4. +@@ -103 +103 @@ +- test:f_old(Xs) + X. ++ test:f_new(Xs) + X. +@@ -107 +107 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -111 +111 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +@@ -115 +115 @@ +- test:f_old([X]) + 4. ++ test:f_new([X]) + 4. +diff -x .git -u0 -br orig/test.erl refac/test.erl +--- orig/test.erl 2023-10-09 07:17:35.424029655 +0200 ++++ refac/test.erl 2023-10-09 07:17:45.357449622 +0200 +@@ -6 +6,6 @@ +-f_old(Xs) -> lists:sum(Xs). ++-spec f_new(list(integer())) -> integer(). ++f_new(Xs) -> ++ case lists:member(3, Xs) of ++ false -> lists:sum(Xs); ++ true -> 38 ++ end. +@@ -11 +16 @@ +- f_old(Xs) - 4. ++ f_new(Xs) - 4. +@@ -17 +22 @@ +- f_old(Xs) + X. ++ f_new(Xs) + X. +@@ -21 +26 @@ +- f_old([X]) + 4. ++ f_new([X]) + 4. diff --git a/test_data/diff_output/diff_files.txt b/test_data/diff_output/diff_files.txt new file mode 100644 index 0000000..7e774dd --- /dev/null +++ b/test_data/diff_output/diff_files.txt @@ -0,0 +1,7 @@ +diff -x .git -u0 -br orig/a/test2.erl refac/a/test2.erl +diff -x .git -u0 -br orig/test2.erl refac/test2.erl +diff -x .git -u0 -br orig/test3.erl refac/test3.erl +diff -x .git -u0 -br orig/test4.erl refac/test4.erl +diff -x .git -u0 -br orig/test5.erl refac/test5.erl +diff -x .git -u0 -br orig/test6.erl refac/test6.erl +diff -x .git -u0 -br orig/test.erl refac/test.erl