defmodule Mix.Utils.Stale do import Record import Mix.Utils.StaleCompatible defrecordp :source, source: nil, compile_references: [], runtime_references: [], external: [] @stale_manifest "compile.espec_stale" @manifest_vsn 1 @agent_manifest_name :espec_stale_manifest_agent # set up stale manifest def set_up_stale_sources(matched_test_files) do manifest = manifest() modified = Mix.Utils.last_modified(manifest) all_sources = read_manifest() removed = for source(source: source) <- all_sources, source not in matched_test_files, do: Path.expand(source) sources_mtimes = mtimes(all_sources) # Let's start with the new sources # Plus the sources that have changed in disk changed = for( source <- matched_test_files, not List.keymember?(all_sources, source, source(:source)), do: source ) ++ for( source(source: source, external: external) <- all_sources, times = Enum.map([source | external], &Map.fetch!(sources_mtimes, &1)), Mix.Utils.stale?(times, [modified]), do: source ) stale = MapSet.new(changed -- removed) sources = update_stale_sources(all_sources, removed, changed) test_files_to_run = sources |> tests_with_changed_references() |> MapSet.union(stale) |> MapSet.to_list() if test_files_to_run == [] do {[], []} else {:ok, pid} = Agent.start_link(fn -> sources end, name: @agent_manifest_name) cwd = File.cwd!() parallel_require_callbacks = parallel_require_callbacks(pid, cwd) {test_files_to_run, parallel_require_callbacks} end end ## Manifest def manifest, do: Path.join(Mix.Project.manifest_path(), @stale_manifest) def write_manifest([]) do File.rm(manifest()) :ok end def write_manifest(sources) do manifest = manifest() File.mkdir_p!(Path.dirname(manifest)) manifest_data = :erlang.term_to_binary([@manifest_vsn | sources], [:compressed]) File.write!(manifest, manifest_data) end defp read_manifest() do try do [@manifest_vsn | sources] = manifest() |> File.read!() |> :erlang.binary_to_term() sources rescue _ -> [] end end def agent_manifest_name(), do: @agent_manifest_name def agent_write_manifest() do Agent.cast(@agent_manifest_name, fn sources -> write_manifest(sources) sources end) end ## Setup helpers defp mtimes(sources) do Enum.reduce(sources, %{}, fn source(source: source, external: external), map -> Enum.reduce([source | external], map, fn file, map -> Map.put_new_lazy(map, file, fn -> Mix.Utils.last_modified(file) end) end) end) end defp update_stale_sources(sources, removed, changed) do sources = Enum.reject(sources, fn source(source: source) -> source in removed end) sources = Enum.reduce(changed, sources, &List.keystore(&2, &1, source(:source), source(source: &1))) sources end end