Forecastle.UpgradeCase (forecastle v1.0.0)

Copy Markdown View Source

Case template for tests that upgrade a real release from one version to the next.

The template adds a per-module :scratch path and aliases Forecastle.Deployment. The project supplies the releases and assertions.

Build the target release with a relup for the deployed baseline:

defp releases do
  [
    my_app: fn ->
      [upgrade_from: ["tar:artifacts/my_app-1.0.0.tar.gz"]]
      |> Castle.customize()
    end
  ]
end

Deploy the baseline, start it, then install and commit the target:

defmodule MyApp.UpgradeTest do
  use Forecastle.UpgradeCase

  @moduletag :upgrade

  @shipped "tar:artifacts/my_app-1.0.0.tar.gz"
  @next "_build/prod/my_app-1.1.0.tar.gz"

  setup_all %{scratch: scratch} do
    deployment = Deployment.deploy!(@shipped, Path.join(scratch, "deploy"))
    on_exit(fn -> Deployment.stop(deployment) end)

    Deployment.start!(deployment)
    Deployment.rpc!(deployment, "IO.puts(MyApp.Counter.bump())")

    Deployment.stage!(deployment, @next)
    Deployment.castle!(deployment, ["unpack", "1.1.0"])
    Deployment.castle!(deployment, ["install", "1.1.0"])
    Deployment.castle!(deployment, ["commit"])

    {:ok, deployment: deployment}
  end

  test "moved to 1.1.0 and took the count with it", %{deployment: deployment} do
    assert Deployment.rpc!(deployment, "IO.puts(inspect(MyApp.Counter.info()))") ==
             ~s({"1.1.0", 1})

    assert Deployment.version(deployment) == "1.1.0"
  end
end

castle!/3 raises on a non-zero exit. Assert both retained state and the code version serving calls. A missing appup instruction can leave old code running while the release reports the new version. Compile a version tag into the module under test:

defmodule MyApp.Counter do
  use GenServer

  @vsn_tag Mix.Project.config()[:version]

  @doc "`{the version compiled into the code serving this call, count}`"
  def info, do: GenServer.call(__MODULE__, :info)

  def handle_call(:info, _from, state), do: {:reply, {@vsn_tag, state.count}, state}
end

Deployment.deploy!/3 accepts rel:, tar: and ref: baselines. Prefer the shipped tar: artifact when available.

Use Deployment.install_supervised!/3 for restart_emulator transitions and castle!/3 for hot installs. The supervised form restarts the release while bin/castle install waits for it to return.

The scratch path lives under _build/castle/deployments. The template does not create, clear or remove it. Register Deployment.stop/1 with on_exit/1 for every started deployment.

Mark the whole module with @moduletag :upgrade; these tests start named nodes and should not run asynchronously or as part of the ordinary unit suite.