rustyio / sync

On-the-fly recompiling and reloading in Erlang. Code without friction.
MIT License
748 stars 163 forks source link

Fix for sync to work with meck #72

Closed toddgjohnson closed 7 years ago

toddgjohnson commented 7 years ago

Currently, the Erlang mocking library meck breaks when used with sync. Specifically, when meck loads a mocked module as a binary into the code server, sync picks this up as a change to the beam while running sync_scanner:process_beam_lastmod() and subsequently reloads the beam file into the code server, overwriting the loaded mock.

This PR proposes a general improvement to the compare_beams() cast method in sync_scanner which effectively makes sync only compare "relevant" beams, i.e. beams which have valid beam file references. And as a side effect meck can be used with sync.

Currently, the return values of code:which() and filelib:last_modified() here are not validated. As a result, when code:which() doesn't return a filename but instead an atom describing the module status (see the doc), this atom (instead of a valid path) is passed to filelib:last_modified() which returns 0 (file doesn't exist). Finally, the {module_name, last_modified} tuple is inappropriately added to the list to be processed. (For example, the zlib module is usually a preloaded module with no file reference, but is compared in the tuple list each time). This is a general description of why ignoring the returns of these two calls doesn't make sense. In the case of meck not working, it is a different outcome of the same issue...

The steps that break meck are the following:

  1. A user module "mod1" is loaded from path "ebin/mod1.beam" into the code server.
  2. A sync compare_beams method runs and adds a tuple, {"mod1", {{2016,10,14},{17,25,24}}}, to the beam lastmod list.
  3. Then meck:expect() is called to mock "mod1". It pokes a binary into the code server, specifying the empty list ("") as the beam file reference (here), which makes sense, since the loaded mocked module has now overridden the beam file.
  4. On the next compare_beams run, filelib:last_modified() is called with the empty list ("") and it returns 0 (since the file "" doesn't exist).
  5. Finally sync_scanner:process_beam_lastmod() sees a difference between the original datetime tuple and {"mod1", 0}, and reloads the beam from the path "ebin/mod1.beam" ultimately clobbering the loaded mock.

In summary, only beams with valid file references should be included in the list when comparing beam lastmod times. It makes sense in general, and allows meck, and possibly other libraries which programmatically alter binaries in the code server to run alongside sync.

choptastic commented 7 years ago

Great find! Thanks! This looks great.

toddgjohnson commented 7 years ago

Cool, thanks for reviewing so quickly!