From fac59ddeb804510f0620cc009df955a28286834a Mon Sep 17 00:00:00 2001 From: TheFirstAvenger Date: Tue, 12 Feb 2019 14:45:56 -0500 Subject: [PATCH] Add :dialyzer_ignore check. Improved test suite. v0.2.0. --- CHANGELOG.md | 4 +- README.md | 2 +- config/test.exs | 3 +- coveralls.json | 9 ++ lib/ironman/checks/dialyzer_config.ex | 88 ++++++++++++++++ lib/ironman/config.ex | 69 ++++++++++++- lib/ironman/runner.ex | 12 ++- lib/ironman/utils.ex | 45 +++++--- lib/ironman/utils/deps.ex | 44 +++++--- lib/ironman/utils/file.ex | 10 ++ lib/ironman/utils/file/default_impl.ex | 8 ++ lib/ironman/utils/file/impl.ex | 7 ++ mix.exs | 2 +- test/ironman/checks/dialyzer_config_test.exs | 61 +++++++++++ test/ironman/checks/simple_dep_test.exs | 52 ++++------ test/ironman/config_test.exs | 102 +++++++++++++++++++ test/support/mix_builder.ex | 34 ++++++- test/support/mox_helpers.ex | 42 ++++++++ test/test_helper.exs | 1 + 19 files changed, 516 insertions(+), 79 deletions(-) create mode 100644 coveralls.json create mode 100644 lib/ironman/checks/dialyzer_config.ex create mode 100644 lib/ironman/utils/file.ex create mode 100644 lib/ironman/utils/file/default_impl.ex create mode 100644 lib/ironman/utils/file/impl.ex create mode 100644 test/ironman/checks/dialyzer_config_test.exs create mode 100644 test/ironman/config_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index dfba3f2..6bb1117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## Next +## 0.2.0 * Change dependency checking to use regex * Clear dialyzer ignores fixed in elixir 1.8.1 * Change format of IO questions * Add :git_hooks check +* Add :dialyzer_config check +* Mox/Impl all File operations ## 0.1.2 diff --git a/README.md b/README.md index 3ed4aaf..e3f4e53 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Contributions welcome. Specifically looking to: ### Add Configuration -* [ ] [`:dialyxir`](https://github.com/jeremyjh/dialyxir) +* [X] [`:dialyxir`](https://github.com/jeremyjh/dialyxir) * [ ] [`:credo`](https://github.com/rrrene/credo) * [ ] [`:excoveralls`](https://github.com/parroty/excoveralls) * [ ] [`:git_hooks`](https://github.com/qgadrian/elixir_git_hooks) diff --git a/config/test.exs b/config/test.exs index 079e0dc..2d8910b 100644 --- a/config/test.exs +++ b/config/test.exs @@ -4,4 +4,5 @@ config :ironman, hex_repo: "https://hex.pm", http_client: Ironman.MockHttpClient, io: Ironman.MockIO, - cmd: Ironman.MockCmd + cmd: Ironman.MockCmd, + file: Ironman.MockFile diff --git a/coveralls.json b/coveralls.json new file mode 100644 index 0000000..b5b7211 --- /dev/null +++ b/coveralls.json @@ -0,0 +1,9 @@ +{ + "skip_files": [ + "lib/ironman.ex", + "lib/mix/tasks/suit_up.ex", + "lib/ironman/utils/io/default_impl.ex", + "lib/ironman/utils/http_client/default_impl.ex", + "lib/ironman/utils/cmd/default_impl.ex" + ] +} \ No newline at end of file diff --git a/lib/ironman/checks/dialyzer_config.ex b/lib/ironman/checks/dialyzer_config.ex new file mode 100644 index 0000000..38050ea --- /dev/null +++ b/lib/ironman/checks/dialyzer_config.ex @@ -0,0 +1,88 @@ +defmodule Ironman.Checks.DialyzerConfig do + @moduledoc false + alias Ironman.{Config, Utils} + + @spec run(Config.t()) :: {:error, any()} | {:no | :yes | :up_to_date, Config.t()} + def run(%Config{} = config) do + config + |> Config.starting_project_config() + |> Keyword.get(:dialyzer, nil) + |> case do + nil -> offer_add_dialyzer_config(config) + _ -> {:up_to_date, config} + end + end + + def offer_add_dialyzer_config(%Config{} = config) do + Utils.ask( + "Add dialyzer config to project?", + fn -> do_add_config(config) end, + fn -> skip_install(config) end + ) + end + + def do_add_config(%Config{} = config) do + config = set_dialyzer_mix_exs(config) + + config = add_dialyzer_ignore_file(config) + + config = add_plt_to_gitignore(config) + + {:yes, config} + end + + defp set_dialyzer_mix_exs(config) do + mix_exs = Config.mix_exs(config) + mix_exs = Regex.replace(~r/def project do\n.*?\[/, mix_exs, "def project do\n [ " <> dialyzer_config(config)) + Config.set_mix_exs(config, mix_exs) + end + + defp add_dialyzer_ignore_file(config) do + case Config.dialyzer_ignore(config) do + nil -> + Utils.puts("Adding dialyzer ignore file") + Config.set_dialyzer_ignore(config, "[\n\n]") + + _ -> + config + end + end + + defp dialyzer_config(config) do + app_name = Config.app_name(config) + + """ + dialyzer: [ + ignore_warnings: ".dialyzer_ignore.exs", + list_unused_filters: true, + plt_file: {:no_warn, "#{app_name}.plt"} + ], + """ + end + + defp add_plt_to_gitignore(config) do + app_name = Config.app_name(config) + + case Config.gitignore(config) do + nil -> + Utils.puts("Creating .gitignore with #{app_name}.plt") + Config.set_gitignore(config, "# dialyzer plt for CI caching\n#{app_name}.plt\n") + + gitignore -> + if String.contains?(gitignore, "#{app_name}.plt") do + config + else + Utils.puts("Adding #{app_name}.plt to .gitignore") + + newlines = if String.ends_with?(gitignore, "\n"), do: "", else: "\n\n" + Config.set_gitignore(config, "#{gitignore}#{newlines}# dialyzer plt\n#{app_name}.plt\n") + end + end + end + + @spec skip_install(Config.t()) :: {:no, Config.t()} + def skip_install(%Config{} = config) do + Utils.puts("Skipping dialyzer config") + {:no, config} + end +end diff --git a/lib/ironman/config.ex b/lib/ironman/config.ex index 5007026..78bd0f0 100644 --- a/lib/ironman/config.ex +++ b/lib/ironman/config.ex @@ -1,18 +1,79 @@ defmodule Ironman.Config do @moduledoc """ This struct represents the state of the project. It is created at the beginning of a run, passed through - all the checks, where it is updated, and then mix.exs is written out at the end based on its contents. + all the checks, where it is updated, and then files are written out at the end based on its contents. """ + + alias Ironman.Config + alias Ironman.Utils.File, as: IFile + @type t :: %__MODULE__{ mix_exs: String.t(), - changed: boolean() + mix_exs_changed: boolean(), + gitignore: String.t() | nil, + gitignore_changed: boolean(), + dialyzer_ignore: String.t() | nil, + dialyzer_ignore_changed: boolean(), + starting_project_config: keyword() } defstruct mix_exs: nil, - changed: false + mix_exs_changed: false, + gitignore: nil, + gitignore_changed: false, + dialyzer_ignore: nil, + dialyzer_ignore_changed: false, + starting_project_config: nil def new!() do %__MODULE__{ - mix_exs: File.read!("mix.exs") + mix_exs: file_or_nil("mix.exs"), + gitignore: file_or_nil(".gitignore"), + dialyzer_ignore: file_or_nil(".dialyzer_ignore.exs"), + starting_project_config: Mix.Project.config() } end + + ## Getters + def starting_project_config(%Config{starting_project_config: starting_project_config}), do: starting_project_config + def mix_exs(%Config{mix_exs: mix_exs}), do: mix_exs + def gitignore(%Config{gitignore: gitignore}), do: gitignore + def dialyzer_ignore(%Config{dialyzer_ignore: dialyzer_ignore}), do: dialyzer_ignore + + ## Setters when not actually changed + + def set_mix_exs(%Config{mix_exs: mix_exs} = config, new_content) + when new_content == mix_exs, + do: config + + def set_mix_exs(%Config{} = config, new_content), + do: %Config{config | mix_exs: new_content, mix_exs_changed: true} + + def set_gitignore(%Config{gitignore: gitignore} = config, new_content) + when new_content == gitignore, + do: config + + def set_gitignore(%Config{} = config, new_content), + do: %Config{config | gitignore: new_content, gitignore_changed: true} + + def set_dialyzer_ignore(%Config{dialyzer_ignore: dialyzer_ignore} = config, new_content) + when new_content == dialyzer_ignore, + do: config + + def set_dialyzer_ignore(%Config{} = config, new_content), + do: %Config{config | dialyzer_ignore: new_content, dialyzer_ignore_changed: true} + + def mix_exs_changed(%Config{mix_exs_changed: mix_exs_changed}), do: mix_exs_changed + def gitignore_changed(%Config{gitignore_changed: gitignore_changed}), do: gitignore_changed + def dialyzer_ignore_changed(%Config{dialyzer_ignore_changed: dialyzer_ignore_changed}), do: dialyzer_ignore_changed + + def app_name(%Config{starting_project_config: starting_project_config}), + do: Keyword.fetch!(starting_project_config, :app) + + defp file_or_nil(path) do + if IFile.exists?(path) do + IFile.read!(path) + else + nil + end + end end diff --git a/lib/ironman/runner.ex b/lib/ironman/runner.ex index d4f6f06..d98a3bc 100644 --- a/lib/ironman/runner.ex +++ b/lib/ironman/runner.ex @@ -1,18 +1,17 @@ defmodule Ironman.Runner do @moduledoc false - alias Ironman.Checks.SimpleDep + alias Ironman.Checks.{DialyzerConfig, SimpleDep} alias Ironman.{Config, Utils} - @checks [:ex_doc, :earmark, :dialyxir, :mix_test_watch, :credo, :excoveralls, :git_hooks] + @checks [:ex_doc, :earmark, :dialyxir, :mix_test_watch, :credo, :excoveralls, :git_hooks, :dialyzer_config] def run do if Utils.check_self_version() == :exit, do: System.halt() if Utils.check_mix_format() == :exit, do: System.halt() config = Config.new!() - %Config{changed: changed} = config = Enum.reduce(@checks, config, &run_check(&2, &1)) + config = Enum.reduce(@checks, config, &run_check(&2, &1)) - if changed do - Utils.write_mix_exs(config) + if Utils.write_changes(config) do Utils.run_mix_format() Utils.run_mix_deps_get() Utils.run_mix_clean() @@ -43,6 +42,9 @@ defmodule Ironman.Runner do def run_check(%Config{} = config, :git_hooks), do: config |> SimpleDep.run(:git_hooks, only: :test) |> unwrap(:git_hooks) + def run_check(%Config{} = config, :dialyzer_config), + do: config |> DialyzerConfig.run() |> unwrap(:dialyzer_config) + @spec unwrap({atom(), Config.t()} | {:error, any()}, atom()) :: Config.t() def unwrap({:no, config}, _check), do: config def unwrap({:yes, config}, _check), do: config diff --git a/lib/ironman/utils.ex b/lib/ironman/utils.ex index 3082e5a..730ded9 100644 --- a/lib/ironman/utils.ex +++ b/lib/ironman/utils.ex @@ -2,12 +2,11 @@ defmodule Ironman.Utils do @moduledoc false alias Ironman.Config alias Ironman.Utils.{Cmd, Deps, HttpClient} + alias Ironman.Utils.File, as: IFile alias Ironman.Utils.IO, as: IIO def puts(out) do - if Mix.env() != :test do - IO.puts(out) - end + if Mix.env() != :test, do: IO.puts(out) end def check_self_version do @@ -24,8 +23,7 @@ defmodule Ironman.Utils do ask( "Ironman is out of date. Upgrade?", fn -> upgrade_ironman() end, - fn -> :declined end, - fn -> ask_self_upgrade() end + fn -> :declined end ) end @@ -37,10 +35,10 @@ defmodule Ironman.Utils do end def check_mix_format do - start = File.read!("mix.exs") + start = IFile.read!("mix.exs") run_mix_format() - if start == File.read!("mix.exs") do + if start == IFile.read!("mix.exs") do :ok else ask_mix_format() @@ -67,15 +65,34 @@ defmodule Ironman.Utils do ask( "Mix format changed mix.exs, would you like to exit (to commit format changes separately)?", fn -> :exit end, - fn -> :ok end, - fn -> ask_mix_format() end + fn -> :ok end ) end + def write_changes(%Config{} = config) do + if Config.mix_exs_changed(config), do: write_mix_exs(config) + if Config.gitignore_changed(config), do: write_gitignore(config) + if Config.dialyzer_ignore_changed(config), do: write_dialyzer_ignore(config) + + Config.mix_exs_changed(config) or Config.gitignore_changed(config) or Config.dialyzer_ignore_changed(config) + end + @spec write_mix_exs(Config.t()) :: :ok - def write_mix_exs(%Config{mix_exs: mix_exs}) do + defp write_mix_exs(%Config{} = config) do puts("Writing new mix.exs...") - File.write!("mix.exs", mix_exs) + IFile.write!("mix.exs", Config.mix_exs(config)) + end + + @spec write_gitignore(Config.t()) :: :ok + defp write_gitignore(%Config{} = config) do + puts("Writing new gitignore...") + IFile.write!(".gitignore", Config.gitignore(config)) + end + + @spec write_dialyzer_ignore(Config.t()) :: :ok + defp write_dialyzer_ignore(%Config{} = config) do + puts("Writing new dialyzer_ignore...") + IFile.write!(".dialyzer_ignore.exs", Config.dialyzer_ignore(config)) end @spec get_body_as_term(String.t()) :: {:ok, any()} | {:error, any()} @@ -83,12 +100,12 @@ defmodule Ironman.Utils do HttpClient.get_body_as_term(url) end - @spec ask(String.t(), function(), function(), function()) :: any() - def ask(q, yes, no, other) do + @spec ask(String.t(), function(), function()) :: any() + def ask(q, yes, no) do case IIO.get("#{q} [Yn] ") do x when x in ["Y\n", "y\n", "\n"] -> yes.() x when x in ["N\n", "n\n"] -> no.() - _ -> other.() + _ -> ask(q, yes, no) end end end diff --git a/lib/ironman/utils/deps.ex b/lib/ironman/utils/deps.ex index 90d1584..d7ad1db 100644 --- a/lib/ironman/utils/deps.ex +++ b/lib/ironman/utils/deps.ex @@ -37,16 +37,27 @@ defmodule Ironman.Utils.Deps do end @spec get_configured_version(Config.t(), dep()) :: String.t() | nil - def get_configured_version(%Config{mix_exs: mix_exs}, dep) do + def get_configured_version(%Config{} = config, dep) do "defp deps do.*?\\[.*?{:#{dep}, \"(.*?)\"" |> Regex.compile!("s") - |> Regex.run(mix_exs) + |> Regex.run(Config.mix_exs(config)) |> case do [_, version] -> version _ -> nil end end + @spec get_configured_opts(Config.t(), dep()) :: String.t() | nil + def get_configured_opts(%Config{} = config, dep) do + "defp deps do.*?\\[.*?{:#{dep}, \"(.*?)\"(.*?)}" + |> Regex.compile!("s") + |> Regex.run(Config.mix_exs(config)) + |> case do + [_, _, ""] -> nil + [_, _, ", " <> opts] -> "[#{opts}]" |> Code.eval_string() |> elem(0) + end + end + @spec available_version(dep()) :: {:ok, String.t()} | {:error, any()} def available_version(dep) do with( @@ -65,25 +76,27 @@ defmodule Ironman.Utils.Deps do Utils.ask( "Install #{dep} #{available_version}?", fn -> do_install(config, dep, dep_opts, available_version) end, - fn -> skip_install(config, dep) end, - fn -> offer_dep_install(config, dep, dep_opts, available_version) end + fn -> skip_install(config, dep) end ) end @spec do_install(Config.t(), dep(), keyword(), String.t()) :: {:yes, Config.t()} - def do_install(%Config{mix_exs: mix_exs} = config, dep, dep_opts, available_version) do + def do_install(%Config{} = config, dep, dep_opts, available_version) do Utils.puts("Installing #{dep} #{available_version}") new_version = "~> #{available_version}" dep_opts_str = dep_opts_to_str(dep_opts) - mix_exs = - Regex.replace( - ~r/defp deps do.*?\n.*?\[/, - mix_exs, - "defp deps do\n [{:#{dep}, \"#{new_version}\"#{dep_opts_str}}," + config = + Config.set_mix_exs( + config, + Regex.replace( + ~r/defp deps do.*?\n.*?\[/, + Config.mix_exs(config), + "defp deps do\n [{:#{dep}, \"#{new_version}\"#{dep_opts_str}}," + ) ) - {:yes, %Config{config | mix_exs: mix_exs, changed: true}} + {:yes, config} end defp dep_opts_to_str(dep_opts) do @@ -106,20 +119,19 @@ defmodule Ironman.Utils.Deps do Utils.ask( "Upgrade #{dep} from #{configured_version} to #{available_version}?", fn -> do_upgrade(config, dep, configured_version, available_version) end, - fn -> skip_upgrade(config, dep) end, - fn -> offer_dep_upgrade(config, dep, configured_version, available_version) end + fn -> skip_upgrade(config, dep) end ) end @spec do_upgrade(Config.t(), dep(), String.t(), String.t()) :: {:yes, Config.t()} - def do_upgrade(%Config{mix_exs: mix_exs} = config, dep, configured_version, available_version) do + def do_upgrade(%Config{} = config, dep, configured_version, available_version) do # TODO Utils.puts("Upgrading #{dep} from #{configured_version} to #{available_version}") new_version = "~> #{available_version}" regex = Regex.compile!("{:#{dep}, \"~>.*?\"") - mix_exs = Regex.replace(regex, mix_exs, "{:#{dep}, \"#{new_version}\"") + config = Config.set_mix_exs(config, Regex.replace(regex, Config.mix_exs(config), "{:#{dep}, \"#{new_version}\"")) - {:yes, %Config{config | mix_exs: mix_exs, changed: true}} + {:yes, config} end @spec skip_upgrade(Config.t(), any()) :: {:no, Config.t()} diff --git a/lib/ironman/utils/file.ex b/lib/ironman/utils/file.ex new file mode 100644 index 0000000..c1b154e --- /dev/null +++ b/lib/ironman/utils/file.ex @@ -0,0 +1,10 @@ +defmodule Ironman.Utils.File do + @moduledoc false + @behaviour Ironman.Utils.File.Impl + + def exists?(path), do: impl().exists?(path) + def read!(path), do: impl().read!(path) + def write!(path, contents), do: impl().write!(path, contents) + + defp impl, do: Application.get_env(:ironman, :file, Ironman.Utils.File.DefaultImpl) +end diff --git a/lib/ironman/utils/file/default_impl.ex b/lib/ironman/utils/file/default_impl.ex new file mode 100644 index 0000000..f81c9d6 --- /dev/null +++ b/lib/ironman/utils/file/default_impl.ex @@ -0,0 +1,8 @@ +defmodule Ironman.Utils.File.DefaultImpl do + @moduledoc false + @behaviour Ironman.Utils.File.Impl + + def exists?(path), do: File.exists?(path) + def read!(path), do: File.read!(path) + def write!(path, contents), do: File.write!(path, contents) +end diff --git a/lib/ironman/utils/file/impl.ex b/lib/ironman/utils/file/impl.ex new file mode 100644 index 0000000..eeddad5 --- /dev/null +++ b/lib/ironman/utils/file/impl.ex @@ -0,0 +1,7 @@ +defmodule Ironman.Utils.File.Impl do + @moduledoc false + + @callback exists?(path :: String.t()) :: boolean() + @callback read!(path :: String.t()) :: String.t() + @callback write!(path :: String.t(), contents :: String.t()) :: :ok +end diff --git a/mix.exs b/mix.exs index 10486a5..d99157e 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Ironman.MixProject do def project do [ app: :ironman, - version: "0.1.2", + version: "0.2.0", elixir: "~> 1.8", start_permanent: Mix.env() == :prod, deps: deps(), diff --git a/test/ironman/checks/dialyzer_config_test.exs b/test/ironman/checks/dialyzer_config_test.exs new file mode 100644 index 0000000..20d2f53 --- /dev/null +++ b/test/ironman/checks/dialyzer_config_test.exs @@ -0,0 +1,61 @@ +defmodule Ironman.Checks.DialyzerTest do + use ExUnit.Case + + alias Ironman.Checks.DialyzerConfig + alias Ironman.Config + alias Ironman.Test.Helpers.{MixBuilder, MoxHelpers} + + describe "run" do + test "skips when config present" do + config = MixBuilder.with_dialyzer() + {:up_to_date, config2} = DialyzerConfig.run(config) + assert config == config2 + end + + test "skips when config not present but declined by user" do + config = MixBuilder.with_deps(dialyzer: "~> 1.2.3") + MoxHelpers.expect_io("Add dialyzer config to project? [Yn] ", "N") + MoxHelpers.raise_on_io() + {:no, config2} = DialyzerConfig.run(config) + assert config == config2 + end + + test "runs when config not present - nothing else present" do + config = MixBuilder.with_deps(dialyzer: "~> 1.2.3") + refute Config.gitignore(config) + refute Config.dialyzer_ignore(config) + MoxHelpers.expect_io("Add dialyzer config to project? [Yn] ", "Y") + MoxHelpers.raise_on_any_other() + {:yes, config2} = DialyzerConfig.run(config) + assert "# dialyzer plt for CI caching\ntest.plt\n" = Config.gitignore(config2) + assert "[\n\n]" = Config.dialyzer_ignore(config2) + end + + test "runs when config not present - gitignore exists with end newline" do + config = MixBuilder.with_deps(dialyzer: "~> 1.2.3") + config = Config.set_gitignore(config, "existing gitignore\n\nvalues\n\n") + MoxHelpers.expect_io("Add dialyzer config to project? [Yn] ", "Y") + MoxHelpers.raise_on_any_other() + {:yes, config2} = DialyzerConfig.run(config) + assert "existing gitignore\n\nvalues\n\n# dialyzer plt\ntest.plt\n" = Config.gitignore(config2) + end + + test "runs when config not present - gitignore exists no end newline" do + config = MixBuilder.with_deps(dialyzer: "~> 1.2.3") + config = Config.set_gitignore(config, "existing gitignore\n\nvalues") + MoxHelpers.expect_io("Add dialyzer config to project? [Yn] ", "Y") + MoxHelpers.raise_on_any_other() + {:yes, config2} = DialyzerConfig.run(config) + assert "existing gitignore\n\nvalues\n\n# dialyzer plt\ntest.plt\n" = Config.gitignore(config2) + end + + test "runs when config not present - dialyzer ignore exists" do + config = MixBuilder.with_deps(dialyzer: "~> 1.2.3") + config = Config.set_dialyzer_ignore(config, "[\"existing dialzyer filter\"]") + MoxHelpers.expect_io("Add dialyzer config to project? [Yn] ", "Y") + MoxHelpers.raise_on_any_other() + {:yes, config2} = DialyzerConfig.run(config) + assert "[\"existing dialzyer filter\"]" = Config.dialyzer_ignore(config2) + end + end +end diff --git a/test/ironman/checks/simple_dep_test.exs b/test/ironman/checks/simple_dep_test.exs index 11a8a80..316fe34 100644 --- a/test/ironman/checks/simple_dep_test.exs +++ b/test/ironman/checks/simple_dep_test.exs @@ -20,24 +20,20 @@ defmodule Ironman.Checks.SimpleDepTest do MoxHelpers.expect_dep_http(:ex_doc, "1.2.3") MoxHelpers.expect_io("Upgrade ex_doc from ~> 1.2.2 to 1.2.3? [Yn] ", "y") - %Config{mix_exs: mix_exs} = config = MixBuilder.with_deps(ex_doc: "~> 1.2.2") + %Config{} = config = MixBuilder.with_deps(ex_doc: "~> 1.2.2") assert "~> 1.2.2" == Deps.get_configured_version(config, :ex_doc) - assert String.contains?(mix_exs, "{:ex_doc, \"~> 1.2.2\"}") - assert {:yes, %Config{mix_exs: mix_exs} = config} = SimpleDep.run(config, :ex_doc) + assert {:yes, %Config{} = config} = SimpleDep.run(config, :ex_doc) assert "~> 1.2.3" == Deps.get_configured_version(config, :ex_doc) - assert String.contains?(mix_exs, "{:ex_doc, \"~> 1.2.3\"}") end test "doesn't update when n pressed" do MoxHelpers.expect_dep_http(:ex_doc, "1.2.3") MoxHelpers.expect_io("Upgrade ex_doc from ~> 1.2.2 to 1.2.3? [Yn] ", "n") - %Config{mix_exs: mix_exs} = config = MixBuilder.with_deps(ex_doc: "~> 1.2.2") + %Config{} = config = MixBuilder.with_deps(ex_doc: "~> 1.2.2") assert "~> 1.2.2" == Deps.get_configured_version(config, :ex_doc) - assert String.contains?(mix_exs, "{:ex_doc, \"~> 1.2.2\"}") - assert {:no, %Config{mix_exs: mix_exs} = config} = SimpleDep.run(config, :ex_doc) + assert {:no, %Config{} = config} = SimpleDep.run(config, :ex_doc) assert "~> 1.2.2" == Deps.get_configured_version(config, :ex_doc) - assert String.contains?(mix_exs, "{:ex_doc, \"~> 1.2.2\"}") end test "multiple" do @@ -46,19 +42,15 @@ defmodule Ironman.Checks.SimpleDepTest do MoxHelpers.expect_dep_http(:earmark, "2.3.6") MoxHelpers.expect_io("Upgrade earmark from ~> 2.3.4 to 2.3.6? [Yn] ", "y") - %Config{mix_exs: mix_exs} = config = MixBuilder.with_deps(ex_doc: "~> 1.2.2", earmark: "~> 2.3.4") + %Config{} = config = MixBuilder.with_deps(ex_doc: "~> 1.2.2", earmark: "~> 2.3.4") assert "~> 1.2.2" == Deps.get_configured_version(config, :ex_doc) - assert String.contains?(mix_exs, "{:ex_doc, \"~> 1.2.2\"}") assert "~> 2.3.4" == Deps.get_configured_version(config, :earmark) - assert String.contains?(mix_exs, "{:earmark, \"~> 2.3.4\"}") - assert {:yes, %Config{mix_exs: mix_exs} = config} = SimpleDep.run(config, :ex_doc) - assert {:yes, %Config{mix_exs: mix_exs} = config} = SimpleDep.run(config, :earmark) + assert {:yes, %Config{} = config} = SimpleDep.run(config, :ex_doc) + assert {:yes, %Config{} = config} = SimpleDep.run(config, :earmark) assert "~> 1.2.3" == Deps.get_configured_version(config, :ex_doc) - assert String.contains?(mix_exs, "{:ex_doc, \"~> 1.2.3\"}") assert "~> 2.3.6" == Deps.get_configured_version(config, :earmark) - assert String.contains?(mix_exs, "{:earmark, \"~> 2.3.6\"}") end end @@ -67,25 +59,21 @@ defmodule Ironman.Checks.SimpleDepTest do MoxHelpers.expect_dep_http(:ex_doc, "1.2.3") MoxHelpers.expect_io("Install ex_doc 1.2.3? [Yn] ", "y") - %Config{mix_exs: mix_exs} = config = MixBuilder.with_deps() + %Config{} = config = MixBuilder.with_deps() assert nil == Deps.get_configured_version(config, :ex_doc) - refute String.contains?(mix_exs, "{:ex_doc") - assert {:yes, %Config{mix_exs: mix_exs2} = config2} = SimpleDep.run(config, :ex_doc) + assert {:yes, %Config{} = config2} = SimpleDep.run(config, :ex_doc) assert "~> 1.2.3" == Deps.get_configured_version(config2, :ex_doc) - assert String.contains?(mix_exs2, "{:ex_doc, \"~> 1.2.3\"}") end test "doesn't update when n pressed" do MoxHelpers.expect_dep_http(:ex_doc, "1.2.3") MoxHelpers.expect_io("Install ex_doc 1.2.3? [Yn] ", "n") - %Config{mix_exs: mix_exs} = config = MixBuilder.with_deps() + %Config{} = config = MixBuilder.with_deps() assert nil == Deps.get_configured_version(config, :ex_doc) - refute String.contains?(mix_exs, "{:ex_doc,") - assert {:no, %Config{mix_exs: mix_exs2} = config2} = SimpleDep.run(config, :ex_doc) + assert {:no, %Config{} = config2} = SimpleDep.run(config, :ex_doc) assert config == config2 assert nil == Deps.get_configured_version(config2, :ex_doc) - refute String.contains?(mix_exs2, "{:ex_doc,") end test "multiple" do @@ -94,33 +82,27 @@ defmodule Ironman.Checks.SimpleDepTest do MoxHelpers.expect_dep_http(:earmark, "2.3.6") MoxHelpers.expect_io("Install earmark 2.3.6? [Yn] ", "y") - %Config{mix_exs: mix_exs} = config = MixBuilder.with_deps() + %Config{} = config = MixBuilder.with_deps() assert nil == Deps.get_configured_version(config, :ex_doc) - refute String.contains?(mix_exs, "{:ex_doc,") assert nil == Deps.get_configured_version(config, :earmark) - refute String.contains?(mix_exs, "{:earmark,") - assert {:yes, %Config{mix_exs: mix_exs} = config} = SimpleDep.run(config, :ex_doc) - assert {:yes, %Config{mix_exs: mix_exs} = config} = SimpleDep.run(config, :earmark) + assert {:yes, %Config{} = config} = SimpleDep.run(config, :ex_doc) + assert {:yes, %Config{} = config} = SimpleDep.run(config, :earmark) assert "~> 1.2.3" == Deps.get_configured_version(config, :ex_doc) - assert String.contains?(mix_exs, "{:ex_doc, \"~> 1.2.3\"}") assert "~> 2.3.6" == Deps.get_configured_version(config, :earmark) - assert String.contains?(mix_exs, "{:earmark, \"~> 2.3.6\"}") end test "Sets dep_opts" do MoxHelpers.expect_dep_http(:ex_doc, "1.2.3") MoxHelpers.expect_io("Install ex_doc 1.2.3? [Yn] ", "y") - %Config{mix_exs: mix_exs} = config = MixBuilder.with_deps() + %Config{} = config = MixBuilder.with_deps() assert nil == Deps.get_configured_version(config, :ex_doc) - refute String.contains?(mix_exs, "{:ex_doc") - assert {:yes, %Config{mix_exs: mix_exs2} = config2} = SimpleDep.run(config, :ex_doc, only: :dev, runtime: false) + assert {:yes, %Config{} = config2} = SimpleDep.run(config, :ex_doc, only: :dev, runtime: false) - assert "~> 1.2.3" == Deps.get_configured_version(config2, :ex_doc) - assert String.contains?(mix_exs2, "{:ex_doc, \"~> 1.2.3\", only: :dev, runtime: false}") + assert [only: :dev, runtime: false] == Deps.get_configured_opts(config2, :ex_doc) end end end diff --git a/test/ironman/config_test.exs b/test/ironman/config_test.exs new file mode 100644 index 0000000..107e14f --- /dev/null +++ b/test/ironman/config_test.exs @@ -0,0 +1,102 @@ +defmodule Ironman.ConfigTest do + @moduledoc false + use ExUnit.Case + + alias Ironman.Config + alias Ironman.Test.Helpers.MoxHelpers + + def set_new_expectations do + MoxHelpers.expect_file_exists?("mix.exs") + MoxHelpers.expect_file_read!("mix.exs", "This is a mix file") + MoxHelpers.expect_file_exists?(".gitignore") + MoxHelpers.expect_file_read!(".gitignore", "This is a gitignore file") + MoxHelpers.expect_file_exists?(".dialyzer_ignore.exs") + MoxHelpers.expect_file_read!(".dialyzer_ignore.exs", "This is a dialyzer ignore file") + MoxHelpers.raise_on_any_other() + end + + describe "new" do + test "populates all fields correctly" do + set_new_expectations() + config = Config.new!() + assert "This is a mix file" == Config.mix_exs(config) + assert "This is a gitignore file" == Config.gitignore(config) + assert "This is a dialyzer ignore file" == Config.dialyzer_ignore(config) + end + end + + describe "mix_exs" do + test "set updates field" do + set_new_expectations() + config = Config.new!() + new_config = Config.set_mix_exs(config, "a different value") + assert "a different value" == Config.mix_exs(new_config) + end + + test "set doesn't change other fields" do + set_new_expectations() + config = Config.new!() + new_config = Config.set_mix_exs(config, "a different value") + assert Config.gitignore(config) == Config.gitignore(new_config) + assert Config.dialyzer_ignore(config) == Config.dialyzer_ignore(new_config) + end + + test "set updates changed flag" do + set_new_expectations() + config = Config.new!() + refute Config.mix_exs_changed(config) + new_config = Config.set_mix_exs(config, "a different value") + assert Config.mix_exs_changed(new_config) + end + end + + describe "gitignore" do + test "set updates field" do + set_new_expectations() + config = Config.new!() + new_config = Config.set_gitignore(config, "a different value") + assert "a different value" == Config.gitignore(new_config) + end + + test "set doesn't change other fields" do + set_new_expectations() + config = Config.new!() + new_config = Config.set_gitignore(config, "a different value") + assert Config.mix_exs(config) == Config.mix_exs(new_config) + assert Config.dialyzer_ignore(config) == Config.dialyzer_ignore(new_config) + end + + test "set updates changed flag" do + set_new_expectations() + config = Config.new!() + refute Config.gitignore_changed(config) + new_config = Config.set_gitignore(config, "a different value") + assert Config.gitignore_changed(new_config) + end + end + + describe "dialyzer ignore" do + test "set updates field" do + set_new_expectations() + config = Config.new!() + new_config = Config.set_dialyzer_ignore(config, "a different value") + assert "a different value" == Config.dialyzer_ignore(new_config) + end + + test "set doesn't change other fields" do + set_new_expectations() + config = Config.new!() + new_config = Config.set_dialyzer_ignore(config, "a different value") + assert Config.mix_exs(config) == Config.mix_exs(new_config) + assert Config.gitignore(config) == Config.gitignore(new_config) + end + + test "set updates changed flag" do + set_new_expectations() + config = Config.new!() + refute Config.dialyzer_ignore_changed(config) + new_config = Config.set_dialyzer_ignore(config, "a different value") + assert Config.dialyzer_ignore_changed(new_config) + end + end +end diff --git a/test/support/mix_builder.ex b/test/support/mix_builder.ex index f3af247..aa1d9fe 100644 --- a/test/support/mix_builder.ex +++ b/test/support/mix_builder.ex @@ -24,7 +24,39 @@ defmodule Ironman.Test.Helpers.MixBuilder do end """ - %Config{mix_exs: mix_exs} + %Config{starting_project_config: [app: :test]} + |> Config.set_mix_exs(mix_exs) + end + + def with_dialyzer do + mix_exs = """ + defmodule Test.MixProject do + use Mix.Project + + def project do + [ + app: :test, + version: "0.1.0", + elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + deps: deps(), + dialyzer: [ + ignore_warnings: ".dialyzer_ignore.exs", + list_unused_filters: true, + plt_file: {:no_warn, "test.plt"} + ] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [] + end + end + """ + + %Config{starting_project_config: [dialyzer: [:asdf]]} + |> Config.set_mix_exs(mix_exs) end @spec mix_string(list()) :: String.t() diff --git a/test/support/mox_helpers.ex b/test/support/mox_helpers.ex index b634371..8e58024 100644 --- a/test/support/mox_helpers.ex +++ b/test/support/mox_helpers.ex @@ -16,8 +16,50 @@ defmodule Ironman.Test.Helpers.MoxHelpers do |> expect(:get, fn ^expect -> "#{return}\n" end) end + def raise_on_io do + Ironman.MockIO + |> expect(:get, fn x -> raise "IO.get(\"#{x}\")" end) + end + def expect_cmd(expect) do Ironman.MockCmd |> expect(:run, fn ^expect -> :ok end) end + + def expect_file_exists?(file, exists? \\ true) do + Ironman.MockFile + |> expect(:exists?, fn ^file -> exists? end) + end + + def raise_on_file_exists? do + Ironman.MockFile + |> expect(:exists?, fn x -> raise "File.exists?(\"#{x}\")" end) + end + + def expect_file_read!(file, content) do + Ironman.MockFile + |> expect(:read!, fn ^file -> content end) + end + + def raise_on_file_read! do + Ironman.MockFile + |> expect(:read!, fn x -> raise "File.read!(\"#{x}\")" end) + end + + def expect_file_write!(file, contents) do + Ironman.MockFile + |> expect(:write!, fn ^file, ^contents -> :ok end) + end + + def raise_on_file_write! do + Ironman.MockFile + |> expect(:write!, fn x, y -> raise "File.write!(\"#{x}\", \"#{y}\")" end) + end + + def raise_on_any_other do + raise_on_file_exists?() + raise_on_file_read!() + raise_on_file_write!() + raise_on_io() + end end diff --git a/test/test_helper.exs b/test/test_helper.exs index ad49201..769fa6d 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -3,3 +3,4 @@ ExUnit.start() Mox.defmock(Ironman.MockHttpClient, for: Ironman.Utils.HttpClient.Impl) Mox.defmock(Ironman.MockIO, for: Ironman.Utils.IO.Impl) Mox.defmock(Ironman.MockCmd, for: Ironman.Utils.Cmd.Impl) +Mox.defmock(Ironman.MockFile, for: Ironman.Utils.File.Impl)