defmodule TrinityTest do use ExUnit.Case alias Trinity.{Sim, SimProcess, SimPersistentTerm, SimLogger, Scheduler} import Trinity.Scheduler, only: [receive_yield: 1] require SimLogger defmodule Counter do use GenServer alias Trinity.{SimServer, SimFile} def start_link(id, initial_count) do SimServer.start_link(__MODULE__, %{id: id, initial_count: initial_count}) end def add(server, amount) do SimServer.call(server, {:add, amount}) end def init(%{id: id, initial_count: initial_count}) do assert SimFile.exists?("/counters/#{id}/") == false :ok = SimFile.mkdir_p("/counters/#{id}/") assert SimFile.exists?("/counters/#{id}") == true assert SimFile.exists?("/counters/#{id}/") == true {:ok, fd} = SimFile.open("/counters/#{id}/#{id}.count", [:read, :write]) assert SimFile.ls("/counters/#{id}/") == {:ok, ["#{id}.count"]} SimLogger.debug "Init (id=#{id}, fd=#{fd}, initial_count=#{initial_count})" state = %{ fd: fd, size: nil, } state = write_value(state, initial_count) SimLogger.debug "Initial state (id=#{id}): #{inspect(state)}" :ok = SimPersistentTerm.put("counter-#{id}", initial_count) term = SimPersistentTerm.get("counter-#{id}", nil) SimLogger.debug "Persistent term: #{term}" # Note: this intentionally tests a log message with no variables SimLogger.debug "Init complete" {:ok, state} end def handle_call({:add, amount}, _from, state) do value = read_value(state) value = value + amount state = write_value(state, value) {:reply, value, state} end defp read_value(%{fd: fd, size: size}) do {:ok, data} = SimFile.pread(fd, 3, size) "The current value of the counter is: " <> value_str = data String.to_integer(value_str) end defp write_value(%{fd: fd} = state, value) do data = "The current value of the counter is: " <> Integer.to_string(value) SimFile.pwrite(fd, 3, data) %{state | size: byte_size(data)} end end defmodule CounterSupervisor do use GenServer alias Trinity.SimServer def start_link(children, opts), do: SimServer.start_link(__MODULE__, children, opts) def get_children(server), do: SimServer.call(server, :get_children) def init(children) do pids = Enum.map(children, fn {m, f, a} -> {:ok, pid} = apply(m, f, a) pid end) {:ok, pids} end def handle_call(:get_children, _from, pids) do {:reply, pids, pids} end end test "scheduler" do message = Sim.run_simulation(fn -> nodes = [:n1, :n2, :n3] names = Enum.map(nodes, fn node -> name = String.to_atom(Atom.to_string(node) <> "_proc") children = Enum.map(1..10, fn i -> {TrinityTest.Counter, :start_link, [i, i]} end) node_mfa = { TrinityTest.CounterSupervisor, :start_link, [children, [name: name]], } Sim.create_node(node, node_mfa) Sim.start_node(node) {name, node} end) # Wait for nodes to start/register SimProcess.sleep(100) pids = names |> Enum.map(fn name -> CounterSupervisor.get_children(name) |> Enum.with_index(1) end) |> Enum.concat() Enum.each(pids, fn {pid, id} -> result = Counter.add(pid, 10) assert result == (id + 10) end) SimProcess.send_after self(), :finish, 1000 receive_yield do :finish -> :noop end Scheduler.yield(1000) #dbg Scheduler.dump(), limit: :infinity :success hash = SimLogger.get_hash() tail = SimLogger.log_tail(10) """ Simulation complete. Hash: #{hash} Log tail: #{Enum.join(tail, "\n")} """ end, seed: 101) require Logger Logger.info(message) end end