edgurgel / mimic

A mocking library for Elixir
https://hexdocs.pm/mimic
Apache License 2.0
432 stars 34 forks source link

Unable to stub `Integer.to_string/2` #28

Open bterone opened 3 years ago

bterone commented 3 years ago

Issue

Trying to stub the Integer.to_string/2 method does not work.

Environment

Steps to reproduce

# test_helper.exs
Mimic.copy(Integer)

ExUnit.start()
# test/sometest.exs
defmodule SomeTest do
  use ExUnit.Case
  use Mimic

  test "greets the world" do
    stub(Integer, :to_string, fn _ -> "STUB" end)
    stub(Integer, :to_string, fn _, _ -> "STUB" end)

    random = Integer.to_string(5)

    assert random == "STUB"
  end
end

It seems the above test will fail, but if it's for some other method, like Integer.digits/1 it passes.

Not really sure what could be the issue without further investigation. 🤔

EDIT: I'll be more than happy to open an example repo reproducing the issue 😄

jimsynz commented 3 years ago

@bterone thanks for the detailed bug report. Out of interest, can you try stubbing :erlang.integer_to_binary/2 instead? It seems that Integer.to_string/1..2 is inlined into that function by the compiler.

bterone commented 3 years ago

Hi, thanks for taking your time to respond 😄 , I'm a bit unsure how to copy the :erlang atom to stub. Do I need some sort of adapter module to stub, or is there some macro that could help me out? 🤔

jimsynz commented 3 years ago

Hey @bterone. All module names in Elixir and Erlang are atoms. You should just be able to do Mimic.copy(:erlang) and then stub the function as per usual.

edgurgel commented 3 years ago

I think in general we want to avoid touching the standard lib.

I guess we could add a validation to copy to not allow some modules? 🤔

We can probably get all Elixir modules using this:

iex(3)> :application.get_key(:elixir, :modules)
{:ok,
 [Access, Agent.Server, Agent, Application, ArgumentError, ArithmeticError,
  Atom, BadArityError, BadBooleanError, BadFunctionError, BadMapError,
  BadStructError, Base, Behaviour, Bitwise, Calendar.ISO,
  Calendar.TimeZoneDatabase, Calendar.UTCOnlyTimeZoneDatabase, Calendar,
  CaseClauseError, Code.Formatter, Code.Identifier, Code.LoadError,
  Code.Typespec, Code, Collectable.BitString, Collectable.File.Stream,
  Collectable.HashDict, Collectable.HashSet, Collectable.IO.Stream,
  Collectable.List, Collectable.Map, Collectable.MapSet, Collectable,
  CompileError, CondClauseError, Config.Provider, Config.Reader, Config,
  Date.Range, Date, DateTime, Dict, DynamicSupervisor, Enum.EmptyError,
  Enum.OutOfBoundsError, Enum, Enumerable.Date.Range, ...]}

Not sure about Erlang though

jimsynz commented 3 years ago

Oh I agree. I just thought it was worth seeing if this fixed it because it may hint at something weird about the compiler inlining.

bterone commented 3 years ago

Hey @bterone. All module names in Elixir and Erlang are atoms. You should just be able to do Mimic.copy(:erlang) and then stub the function as per usual.

Hi @jimsynz, I've tried using Mimic.copy(:erlang) but it seems to fail when trying to fetch the compile options from that atom as it doesn't exist 🤔

** (Protocol.UndefinedError) protocol Enumerable not implemented for nil of type Atom. This protocol is implemented for the following type(s): HashSet, Range, Map, Function, List, Stream, Date.Range, HashDict, GenEvent.Stream, MapSet, File.Stream, IO.Stream
    (elixir 1.11.4) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.11.4) lib/enum.ex:141: Enumerable.reduce/3
    (elixir 1.11.4) lib/enum.ex:3473: Enum.filter/2
    (mimic 1.5.0) lib/mimic/module.ex:65: Mimic.Module.compiler_options/1
    (mimic 1.5.0) lib/mimic/module.ex:43: Mimic.Module.rename_module/2
    (mimic 1.5.0) lib/mimic/module.ex:27: Mimic.Module.replace!/1
    (mimic 1.5.0) lib/mimic.ex:344: Mimic.copy/1
    test/test_helper.exs:4: (file)

When checking that line that was causing the issue Screen Shot 2021-08-12 at 6 04 20 PM

Screen Shot 2021-08-12 at 5 59 46 PM

Even modifying the Keyword.get(:options) to give an empty [] as default has an error when we try to do :compile.forms(forms, compiler_options(module)) on line 43 🤔

** (CaseClauseError) no case clause matching: {:error, [{'erlang.erl', [{{353, 2}, :erl_lint, {:bad_module, {:erlang, :adler32, 1}}}, {{359, 2}, :erl_lint, {:bad_module, {:erlang, :adler32, 2}}}, {{366, 2}, :erl_lint, {:bad_module, {:erlang, :adler32_combine, 3}}}, {{374, 2}, :erl_lint, {:bad_module, {:erlang, :append_element, 2}}}, {{518, 2}, :erl_lint, {:bad_module, {:erlang, :bump_reductions, 1}}}, {{531, 2}, :erl_lint, {:bad_module, {:erlang, :call_on_load_function, 1}}}, {{537, 2}, :erl_lint, {:bad_module, {:erlang, :cancel_timer, 1}}}, {{546, 2}, :erl_lint, {:bad_module, {:erlang, :cancel_timer, 2}}}, {{602, 2}, :erl_lint, {:bad_module, {:erlang, :crc32, 1}}}, {{608, 2}, :erl_lint, {:bad_module, {:erlang, :crc32, 2}}}, {{615, 2}, :erl_lint, {:bad_module, {:erlang, :crc32_combine, 3}}}, {{629, 2}, :erl_lint, {:bad_module, {:erlang, :decode_packet, 3}}}, {{726, 2}, :erl_lint, {:bad_module, {:erlang, :delete_element, 2}}}, {{773, 2}, :erl_lint, {:bad_module, {:erlang, :display, 1}}}, {{779, 2}, :erl_lint, {:bad_module, {:erlang, :display_nl, 0}}}, {{784, 2}, :erl_lint, {:bad_module, {:erlang, :display_string, 1}}}, {{790, 2}, :erl_lint, {:bad_module, {:erlang, :dt_append_vm_tag_data, 1}}}, {{797, 2}, :erl_lint, {:bad_module, {:erlang, :dt_get_tag, 0}}}, {{802, 2}, :erl_lint, {:bad_module, {:erlang, :dt_get_tag_data, 0}}}, {{807, 2}, :erl_lint, {:bad_module, {:erlang, :dt_prepend_vm_tag_data, 1}}}, {{814, 2}, :erl_lint, {:bad_module, {:erlang, :dt_put_tag, 1}}}, {{820, 2}, :erl_lint, {:bad_module, {:erlang, :dt_restore_tag, 1}}}, {{826, 2}, :erl_lint, {:bad_module, {:erlang, :dt_spread_tag, 1}}}, {{888, 2}, :erl_lint, {:bad_module, {:erlang, :exit_signal, 2}}}, {{895, 2}, :erl_lint, {:bad_module, {:erlang, :external_size, 1}}}, {{901, 2}, :erl_lint, {:bad_module, {:erlang, :external_size, 2}}}, {{908, 2}, :erl_lint, {:bad_module, {:erlang, :finish_loading, 1}}}, {{917, 2}, :erl_lint, {:bad_module, {:erlang, :finish_after_on_load, 2}}}, {{970, 2}, :erl_lint, {:bad_module, {:erlang, :fun_info, 2}}}, {{978, 2}, :erl_lint, {:bad_module, {:erlang, :fun_info_mfa, 1}}}, {{987, 2}, :erl_lint, {:bad_module, {:erlang, :fun_to_list, 1}}}, {{993, 2}, :erl_lint, {:bad_module, {:erlang, :function_exported, 3}}}, {{1067, 2}, :erl_lint, {:bad_module, {:erlang, :garbage_collect_message_area, 0}}}, {{1099, 2}, :erl_lint, {:bad_module, {:erlang, :get_module_info, 1}}}, {{1157, 2}, :erl_lint, {:bad_module, {:erlang, :has_prepared_code_on_load, 1}}}, {{1163, 2}, :erl_lint, {:bad_module, {:erlang, :hibernate, 3}}}, {{1171, 2}, :erl_lint, {:bad_module, {:erlang, :insert_element, 3}}}, {{1204, 2}, :erl_lint, {:bad_module, {:erlang, :iolist_to_iovec, ...}}}, {{1215, 2}, :erl_lint, {:bad_module, {:erlang, ...}}}, {{1316, 2}, :erl_lint, {:bad_module, {...}}}, {{1322, 2}, :erl_lint, {:bad_module, ...}}, {{1347, 2}, :erl_lint, {...}}, {{1356, ...}, :erl_lint, ...}, {{...}, ...}, {...}, ...]}], [{'erlang.erl', [{{247, 2}, :erl_lint, {:unused_type, {:fun_info_item, 0}}}, {{259, 2}, :erl_lint, {:unused_type, {:seq_trace_info_returns, 0}}}, {{265, 2}, :erl_lint, {:unused_type, {:system_profile_option, 0}}}, {{274, 2}, :erl_lint, {:unused_type, {:system_monitor_option, 0}}}, {{282, 2}, :erl_lint, {:unused_type, {:raise_stacktrace, 0}}}, {{291, 2}, :erl_lint, {:unused_type, {:trace_flag, 0}}}, {{343, 2}, :erl_lint, {:unused_type, {:trace_info_return, 0}}}, {{2119, 2}, :erl_lint, {:unused_type, {:module_info_key, 0}}}, {{2595, 2}, :erl_lint, {:unused_type, {:scheduler_bind_type, 0}}}, {{2699, 2}, :erl_lint, {:unused_type, {:trace_pattern_mfa, 0}}}, {{2717, 2}, :erl_lint, {:unused_type, {:trace_pattern_flag, 0}}}, {{2751, 2}, :erl_lint, {:unused_type, {:cpu_topology, 0}}}, {{3451, 2}, :erl_lint, {:unused_type, {:dst, 0}}}, {{3976, 2}, :erl_lint, {:unused_type, {:memory_type, 0}}}]}]}
    (mimic 1.5.0) lib/mimic/module.ex:49: Mimic.Module.rename_module/2
    (mimic 1.5.0) lib/mimic/module.ex:27: Mimic.Module.replace!/1
    (mimic 1.5.0) lib/mimic.ex:344: Mimic.copy/1
    test/test_helper.exs:5: (file)

Not really sure if it's even possible to copy :erlang. 💭