Packages
igniter
0.3.45
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.30
0.6.29
0.6.28
0.6.27
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.52
0.5.51
0.5.50
0.5.49
0.5.48
0.5.47
0.5.46
0.5.45
0.5.44
0.5.43
0.5.42
0.5.41
0.5.40
0.5.39
0.5.38
0.5.37
0.5.36
0.5.35
0.5.34
0.5.33
0.5.32
0.5.31
0.5.30
0.5.29
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.78
0.3.77
0.3.76
0.3.75
0.3.74
0.3.73
0.3.72
0.3.71
0.3.70
0.3.69
0.3.68
0.3.67
0.3.66
0.3.65
0.3.64
0.3.63
0.3.62
0.3.61
0.3.60
0.3.59
0.3.58
0.3.57
0.3.56
0.3.55
0.3.54
0.3.53
0.3.52
0.3.51
0.3.50
0.3.49
0.3.48
0.3.47
0.3.46
0.3.45
0.3.44
0.3.43
0.3.42
0.3.41
0.3.40
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A code generation and project patching framework
Current section
Files
Jump to
Current section
Files
lib/igniter/test.ex
defmodule Igniter.Test do
@moduledoc "Tools for testing with igniter."
@doc """
Sets up a test igniter that has only the files passed to it.
## Starting point
All of the files of an empty mix project are added by default.
You can specify more or overwrite the default files by passing a map of
file paths to their contents.
## Limitations
You cannot install new dependencies, or use dependencies your own project does not have.
If you need to do that kind of thing, you will have to do a test that uses tools like
`System.cmd` in a temporary directory.
## Options
* `files` - A map of file paths to file contents. The file paths should be relative to the project root.
* `app_name` - The name of the application. Defaults to `:test`.
## Examples
test_project(files: %{
"lib/foo.ex" => \"\"\"
defmodule MyApp.Foo do
use Ash.Resource
end
\"\"\"
})
"""
@spec test_project(opts :: Keyword.t()) :: Igniter.t()
def test_project(opts \\ []) do
Igniter.new()
|> Igniter.assign(:test_mode?, true)
|> Igniter.assign(:test_files, add_mix_new(opts))
|> Igniter.Project.IgniterConfig.setup()
|> apply_igniter!()
end
@doc """
IO.puts the current igniter diff, and returns the igniter
## Options
* `label` - A label to put before the diff.
* `only` - File path(s) to only show the diff for
"""
@spec puts_diff(Igniter.t(), opts :: Keyword.t()) :: Igniter.t()
def puts_diff(igniter, opts \\ []) do
igniter.rewrite.sources
|> Map.values()
|> then(fn sources ->
if opts[:only] do
Enum.filter(sources, &(&1.path in List.wrap(opts[:only])))
else
sources
end
end)
|> Igniter.diff()
|> String.trim()
|> case do
"" ->
if opts[:label] do
IO.puts("#{opts[:label]} No changes!")
else
IO.puts("No changes!")
end
diff ->
if opts[:label] do
IO.puts("#{opts[:label]}\n\n#{diff}")
else
IO.puts(diff)
end
end
igniter
end
@doc """
Applies an igniter, raising an error if there are any issues.
See `apply_igniter/1` for more.
"""
@spec apply_igniter!(Igniter.t()) :: Igniter.t() | no_return
def apply_igniter!(igniter) do
case apply_igniter(igniter) do
{:ok, igniter, _} ->
igniter
{:error, error} ->
raise "Error applying igniter:\n\n#{Enum.map_join(error, "\n", &"* #{&1}")}"
end
end
@doc """
Fakes applying the changes of an igniter.
This function returns any tasks, errors, warnings.
"""
@spec apply_igniter(Igniter.t()) ::
{:ok, Igniter.t(),
%{
tasks: [{String.t(), list(String.t())}],
warnings: [String.t()],
notices: [String.t()]
}}
| {:error, [String.t()]}
def apply_igniter(igniter) do
case Igniter.prepare_for_write(igniter) do
%{issues: []} = igniter ->
new_igniter =
igniter
|> simulate_write()
|> move_files()
{:ok, new_igniter,
%{tasks: igniter.tasks, warnings: igniter.warnings, notices: igniter.notices}}
%{issues: issues} ->
{:error, issues}
end
end
defmacro assert_has_patch(igniter, path, patch) do
quote bind_quoted: [igniter: igniter, path: path, patch: patch] do
diff =
igniter.rewrite.sources
|> Map.take([path])
|> Igniter.diff(color?: false)
compare_diff =
Igniter.Test.sanitize_diff(diff)
compare_patch =
Igniter.Test.sanitize_diff(patch, diff)
assert String.contains?(compare_diff, compare_patch),
"""
Expected `#{path}` to contain the following patch:
#{patch}
Actual diff:
#{diff}
"""
igniter
end
end
defmacro assert_unchanged(igniter, path_or_paths) do
quote bind_quoted: [igniter: igniter, path_or_paths: path_or_paths] do
for path <- List.wrap(path_or_paths) do
refute Igniter.changed?(igniter, path), """
Expected #{inspect(path)} to be unchanged, but it was changed.
Diff:
#{igniter.rewrite.sources |> Map.take([path]) |> Igniter.diff()}
"""
end
igniter
end
end
defmacro assert_unchanged(igniter) do
quote bind_quoted: [igniter: igniter] do
refute Igniter.changed?(igniter), """
Expected there to be no changes, but there were changes.
Diff:
#{Rewrite.sources(igniter.rewrite) |> Igniter.diff()}
"""
igniter
end
end
@doc false
def sanitize_diff(diff, actual \\ nil) do
diff
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == "...|" || &1 == ""))
|> Enum.flat_map(fn line ->
if String.contains?(line, "|") do
[line]
else
if actual do
raise """
Invalid patch provided.
#{diff}
Each line of the patch should contain at least one | character.
Actual diff:
#{actual}
"""
else
[]
end
end
end)
|> Enum.map_join("\n", fn line ->
[l, r] = String.split(line, "|", parts: 2)
l =
l
|> String.split()
|> Enum.join()
String.trim(String.trim(l) <> String.trim(r))
end)
end
defmacro assert_creates(igniter, path, content \\ nil) do
quote bind_quoted: [igniter: igniter, path: path, content: content] do
assert source = igniter.rewrite.sources[path],
"""
Expected #{inspect(path)} to have been created, but it was not.
#{Igniter.Test.created_files(igniter)}
"""
assert source.from == :string,
"""
Expected #{inspect(path)} to have been created, but it already existed.
#{Igniter.Test.created_files(igniter)}
"""
if content do
actual_content = Rewrite.Source.get(source, :content)
if actual_content != content do
flunk("""
Expected created file #{inspect(path)} to have the following contents:
#{content}
But it actually had the following contents:
#{actual_content}
Diff, showing your assertion against the actual contents:
#{Rewrite.TextDiff.format(actual_content, content)}
""")
end
end
igniter
end
end
@doc false
def created_files(igniter) do
igniter.rewrite
|> Rewrite.sources()
|> Enum.filter(&(&1.from == :string))
|> Enum.map(& &1.path)
|> case do
[] ->
"\nNo files were created."
modules ->
"\nThe following files were created:\n\n#{Enum.map_join(modules, "\n", &"* #{&1}")}"
end
end
defp simulate_write(igniter) do
igniter.rewrite
|> Rewrite.sources()
|> Enum.reduce(igniter.assigns[:test_files], fn source, test_files ->
content = Rewrite.Source.get(source, :content)
Map.put(test_files, source.path, content)
end)
|> then(fn test_files ->
igniter
|> Map.put(:rewrite, Rewrite.new())
|> Map.put(:tasks, [])
|> Map.put(:warnings, [])
|> Map.put(:notices, [])
|> Map.put(:issues, [])
|> Map.put(:assigns, %{
test_mode?: true,
test_files: test_files,
igniter_exs: igniter.assigns[:igniter_exs]
})
end)
end
defp move_files(igniter) do
igniter.moves
|> Enum.reduce(igniter.assigns[:test_files], fn {from, to}, files ->
case Map.pop(files, from) do
{nil, files} -> files
{contents, files} -> Map.put(files, to, contents)
end
end)
|> then(fn test_files ->
igniter
|> Igniter.assign(:test_files, test_files)
|> Map.put(:moves, %{})
end)
end
defp add_mix_new(opts) do
app_name = opts[:app_name] || :test
module_name = Module.concat([Macro.camelize(to_string(app_name))])
opts[:files]
|> Kernel.||(%{})
|> Map.put_new("test/test_helper.exs", "ExUnit.start()")
|> Map.put_new("test/#{app_name}_test.exs", """
defmodule #{module_name}Test do
use ExUnit.Case
doctest #{module_name}
test "greets the world" do
assert #{module_name}.hello() == :world
end
end
""")
|> Map.put_new("lib/#{app_name}.ex", """
defmodule #{module_name} do
@moduledoc \"\"\"
Documentation for `#{module_name}`.
\"\"\"
@doc \"\"\"
Hello world.
## Examples
iex> #{module_name}.hello()
:world
\"\"\"
def hello do
:world
end
end
""")
|> Map.put_new("README.md", """
# #{module_name}
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `thing` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:#{app_name}, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/#{app_name}>.
""")
|> Map.put_new(".formatter.exs", """
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
""")
|> Map.put_new(".gitignore", """
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
#{app_name}-*.tar
# Temporary files, for example, from tests.
/tmp/
""")
|> Map.put_new("mix.exs", """
defmodule #{module_name}.MixProject do
use Mix.Project
def project do
[
app: :#{app_name},
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end
""")
end
end