diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..2819b3a --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,72 @@ +name: CI +on: + pull_request: + push: + branches: main + +jobs: + tests: + runs-on: ubuntu-latest + name: Test (${{matrix.elixir}}/${{matrix.otp}}) + + strategy: + fail-fast: false + matrix: + elixir: [1.17.x, 1.18.x] + otp: [26.x, 27.x] + + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + id: install + with: + otp-version: ${{matrix.otp}} + elixir-version: ${{matrix.elixir}} + - uses: actions/cache@v4 + id: cache + with: + path: | + deps + key: ${{ runner.os }}-mix-${{steps.install.outputs.otp-version}}-${{steps.install.outputs.elixir-version}}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{steps.install.outputs.otp-version}}-${{steps.install.outputs.elixir-version}}- + + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: mix deps.get + + - name: Run Tests + run: mix test + + formatter: + runs-on: ubuntu-latest + name: Formatter (${{matrix.elixir}}/${{matrix.otp}}) + + strategy: + matrix: + otp: [27.x] + elixir: [1.17.x] + + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + id: install + with: + otp-version: ${{matrix.otp}} + elixir-version: ${{matrix.elixir}} + - uses: actions/cache@v4 + id: cache + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{steps.install.outputs.otp-version}}-${{steps.install.outputs.elixir-version}}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{steps.install.outputs.otp-version}}-${{steps.install.outputs.elixir-version}}- + + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: mix deps.get + + - name: Run Formatter + run: mix format --check-formatted diff --git a/.gitignore b/.gitignore index 6aa6753..41b1ea9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ web_dev_utils-*.tar # Temporary files, for example, from tests. /tmp/ + +/test/support/editable.ex diff --git a/lib/web_dev_utils/code_reloader.ex b/lib/web_dev_utils/code_reloader.ex index 7a023ed..5544535 100644 --- a/lib/web_dev_utils/code_reloader.ex +++ b/lib/web_dev_utils/code_reloader.ex @@ -25,12 +25,12 @@ defmodule WebDevUtils.CodeReloader do {:ok, :nostate} end - def handle_call(:reload, from, state) do - froms = all_waiting([from]) - mix_compile(Code.ensure_loaded(Mix.Task)) - Enum.each(froms, &GenServer.reply(&1, :ok)) + def handle_call(:reload, _from, state) do + froms = all_waiting([]) + result = mix_compile(Code.ensure_loaded(Mix.Task)) + Enum.each(froms, &GenServer.reply(&1, result)) - {:noreply, state} + {:reply, result, state} end defp all_waiting(acc) do @@ -43,10 +43,11 @@ defmodule WebDevUtils.CodeReloader do defp mix_compile({:error, _reason}) do Logger.error("Could not find Mix") + :error end defp mix_compile({:module, Mix.Task}) do Mix.Task.reenable("compile.elixir") - Mix.Task.run("compile.elixir") + Mix.Task.run("compile.elixir", ["--return-errors"]) end end diff --git a/mix.exs b/mix.exs index 2aab7f6..cbfd54e 100644 --- a/mix.exs +++ b/mix.exs @@ -10,6 +10,7 @@ defmodule WebDevUtils.MixProject do "Library to enable awesome local development for websites and web applications", version: "0.2.0", elixir: "~> 1.14", + elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, package: package(), deps: deps() @@ -23,6 +24,10 @@ defmodule WebDevUtils.MixProject do ] end + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + # Run "mix help deps" to learn about dependencies. defp deps do [ diff --git a/test/support/.keep b/test/support/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/web_dev_utils/code_reloader_test.exs b/test/web_dev_utils/code_reloader_test.exs new file mode 100644 index 0000000..a2829d4 --- /dev/null +++ b/test/web_dev_utils/code_reloader_test.exs @@ -0,0 +1,47 @@ +defmodule WebDevUtils.CodeReloaderTest do + use ExUnit.Case, async: true + import ExUnit.CaptureIO + + alias WebDevUtils.CodeReloader + + setup do + on_exit(fn -> + File.rm("test/support/editable.ex") + end) + + :ok + end + + test "noops when nothing has changed" do + start_supervised!(CodeReloader) + + assert {_, []} = CodeReloader.reload() + assert {:noop, []} == CodeReloader.reload() + end + + test "recompiles code" do + File.write!("test/support/editable.ex", """ + defmodule Editable do + end + """) + + start_supervised!(CodeReloader) + + capture_io(:stderr, fn -> + assert {:ok, []} == CodeReloader.reload() + end) + end + + test "recompiles code and something fails" do + File.write!("test/support/editable.ex", """ + defmodule Editable d + end + """) + + start_supervised!(CodeReloader) + + capture_io(:stderr, fn -> + assert {:error, [%Mix.Task.Compiler.Diagnostic{}]} = CodeReloader.reload() + end) + end +end diff --git a/test/web_dev_utils_test.exs b/test/web_dev_utils_test.exs deleted file mode 100644 index 33b80a7..0000000 --- a/test/web_dev_utils_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule WebDevUtilsTest do - use ExUnit.Case - doctest WebDevUtils - - test "greets the world" do - assert WebDevUtils.hello() == :world - end -end