defmodule Test do def modify(num) do if num in [2, 5, 9, 10, 12, 31] do num else num + 1 end end def add_to_num(num) do if num > 10 do num + 10 else num + 1 end end end #defmodule MutationTestingExperiment do #def mutate() do #{_mod, mod_binary, _filename} = :code.get_object_code(Test) #{:ok, {_, [{:abstract_code, {:raw_abstract_v1, [file, _module | rest] = abstract}}]}} = #:beam_lib.chunks(mod_binary, [:abstract_code]) #Agent.start(fn -> abstract end, name: TestAgent) #renamed_forms = [file, {:attribute, 1, :module, TestSavedVersion} | rest] #altered_forms = Enum.map(abstract, &mutate/1) #compile_and_load(renamed_forms) #compile_and_load(altered_forms) #end #def unmutate() do #abstract = Agent.get(TestAgent, & &1) #:code.purge(Test) #:code.delete(Test) #compile_and_load(abstract) #:code.purge(Test.Saved.Version) #:code.delete(Test.Saved.Version) #end #defp compile_and_load(abstract) do #case :compile.forms(abstract, [:return_errors]) do #{:ok, module_name, binary} -> :code.load_binary(module_name, '', binary) #{:ok, module_name, binary, _} -> :code.load_binary(module_name, '', binary) #_ -> :error #end #end #defp mutate({:attribute, _, _, _} = form) do #form #end #defp mutate({:function, 2, :pure_fun, 1, body}) do #[{:clause, 2, [{:var, 2, :_num@1}], [], clause}] = body #new_clause = Enum.map(clause, &mutate/1) #{:function, 2, :pure_fun, 1, [{:clause, 2, [{:var, 2, :_num@1}], [], new_clause}]} #end #defp mutate({:case, 3, condition, clauses}) do #{:op, 3, :>, {:var, 3, :_num@1}, {:integer, 0, 10}} = condition #new_condition_1 = {:op, 3, :<, {:var, 3, :_num@1}, {:integer, 0, 10}} #new_condition_2 = {:op, 3, :>, {:var, 3, :_num@1}, {:integer, 0, 0}} #[clause1, clause2] = clauses #{:clause, [generated: true, location: 3], [{:atom, 0, false}], [], #[{:op, 6, :+, {:var, 6, :_num@1}, {:integer, 0, 1}}]} = clause1 #new_clause1_1 = #{:clause, [generated: true, location: 3], [{:atom, 0, false}], [], #[{:op, 6, :-, {:var, 6, :_num@1}, {:integer, 0, 1}}]} #new_clause1_2 = #{:clause, [generated: true, location: 3], [{:atom, 0, false}], [], #[{:op, 6, :+, {:var, 6, :_num@1}, {:integer, 0, 0}}]} #new_clause1_3 = #{:clause, [generated: true, location: 3], [{:atom, 0, true}], [], #[{:op, 6, :+, {:var, 6, :_num@1}, {:integer, 0, 1}}]} #{:clause, [generated: true, location: 3], [{:atom, 0, true}], [], #[{:op, 4, :+, {:var, 4, :_num@1}, {:integer, 0, 10}}]} = clause2 #new_clause2_1 = #{:clause, [generated: true, location: 3], [{:atom, 0, true}], [], #[{:op, 4, :-, {:var, 4, :_num@1}, {:integer, 0, 10}}]} #new_clause2_2 = #{:clause, [generated: true, location: 3], [{:atom, 0, true}], [], #[{:op, 4, :+, {:var, 4, :_num@1}, {:integer, 0, 0}}]} #new_clause2_3 = #{:clause, [generated: true, location: 3], [{:atom, 0, false}], [], #[{:op, 4, :+, {:var, 4, :_num@1}, {:integer, 0, 10}}]} #case Enum.random(0..7) do #0 -> {:case, 3, new_condition_1, [clause1, clause2]} #1 -> {:case, 3, new_condition_2, [clause1, clause2]} #2 -> {:case, 3, condition, [new_clause1_1, clause2]} #3 -> {:case, 3, condition, [new_clause1_2, clause2]} #4 -> {:case, 3, condition, [new_clause1_3, clause2]} #5 -> {:case, 3, condition, [clause1, new_clause2_1]} #6 -> {:case, 3, condition, [clause1, new_clause2_2]} #_ -> {:case, 3, condition, [clause1, new_clause2_3]} #end #end #defp mutate(form) do #form #end #end